From 0a415a229218f6b77697d04d00e3e5ef8f8073d3 Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Fri, 5 Sep 2025 18:13:35 -0500 Subject: [PATCH] checkpoint --- BoardRules/BoardRules.cs | 120 +++ BoardRules/BoardRules.csproj | 9 + Shogi.Domain/ValueObjects/Enums.cs | 6 + .../Movement}/Direction.cs | 2 +- .../Movement}/Distance.cs | 2 +- .../ValueObjects/{ => Movement}/Move.cs | 0 .../ValueObjects/{ => Movement}/MoveResult.cs | 0 .../Pathing => ValueObjects/Movement}/Path.cs | 4 +- .../ValueObjects/{ => Pieces}/Bishop.cs | 2 +- .../ValueObjects/{ => Pieces}/GoldGeneral.cs | 2 +- .../ValueObjects/{ => Pieces}/King.cs | 2 +- .../ValueObjects/{ => Pieces}/Knight.cs | 2 +- .../ValueObjects/{ => Pieces}/Lance.cs | 2 +- .../ValueObjects/{ => Pieces}/Pawn.cs | 2 +- .../ValueObjects/{ => Pieces}/Piece.cs | 2 +- .../ValueObjects/{ => Pieces}/Rook.cs | 2 +- .../{ => Pieces}/SilverGeneral.cs | 2 +- .../ValueObjects/{ => Rules}/ShogiBoard.cs | 9 +- .../ValueObjects/{ => Rules}/StandardRules.cs | 0 Shogi.Domain/ValueObjects/WhichPlayer.cs | 8 - Shogi.sln | 6 + Tests/UnitTests/NotationShould.cs | 39 +- Tests/UnitTests/RookShould.cs | 2 +- Tests/UnitTests/ShogiShould.cs | 889 +++++++++--------- 24 files changed, 622 insertions(+), 492 deletions(-) create mode 100644 BoardRules/BoardRules.cs create mode 100644 BoardRules/BoardRules.csproj rename Shogi.Domain/{YetToBeAssimilatedIntoDDD/Pathing => ValueObjects/Movement}/Direction.cs (91%) rename Shogi.Domain/{YetToBeAssimilatedIntoDDD/Pathing => ValueObjects/Movement}/Distance.cs (80%) rename Shogi.Domain/ValueObjects/{ => Movement}/Move.cs (100%) rename Shogi.Domain/ValueObjects/{ => Movement}/MoveResult.cs (100%) rename Shogi.Domain/{YetToBeAssimilatedIntoDDD/Pathing => ValueObjects/Movement}/Path.cs (85%) rename Shogi.Domain/ValueObjects/{ => Pieces}/Bishop.cs (96%) rename Shogi.Domain/ValueObjects/{ => Pieces}/GoldGeneral.cs (93%) rename Shogi.Domain/ValueObjects/{ => Pieces}/King.cs (93%) rename Shogi.Domain/ValueObjects/{ => Pieces}/Knight.cs (94%) rename Shogi.Domain/ValueObjects/{ => Pieces}/Lance.cs (94%) rename Shogi.Domain/ValueObjects/{ => Pieces}/Pawn.cs (94%) rename Shogi.Domain/ValueObjects/{ => Pieces}/Piece.cs (98%) rename Shogi.Domain/ValueObjects/{ => Pieces}/Rook.cs (96%) rename Shogi.Domain/ValueObjects/{ => Pieces}/SilverGeneral.cs (95%) rename Shogi.Domain/ValueObjects/{ => Rules}/ShogiBoard.cs (97%) rename Shogi.Domain/ValueObjects/{ => Rules}/StandardRules.cs (100%) delete mode 100644 Shogi.Domain/ValueObjects/WhichPlayer.cs diff --git a/BoardRules/BoardRules.cs b/BoardRules/BoardRules.cs new file mode 100644 index 0000000..6b98a7b --- /dev/null +++ b/BoardRules/BoardRules.cs @@ -0,0 +1,120 @@ +//using System.Drawing; +//using System.Numerics; + +//namespace BoardRules; + +//public static class PieceMoves +//{ +// public static readonly ICollection GoldGeneralMoves = +// [ +// new(-1, 1), +// new(0, 1), +// new(1, 1), +// new(-1, 0), +// new(1, 0), +// new(0, -1) +// ]; + +// public static readonly ICollection PawnMoves = [new(0, 1)]; + +// public static readonly ICollection KnightMoves = [new(-1, 2), new(1, 2)]; + +// public static readonly ICollection BishopMoves = +// [ +// new(float.NegativeInfinity, float.NegativeInfinity), +// new(float.NegativeInfinity, float.PositiveInfinity), +// new(float.PositiveInfinity, float.PositiveInfinity), +// new(float.PositiveInfinity, float.NegativeInfinity), +// ]; +//} + +//public class BoardRules +//{ +// private readonly Dictionary pieces = []; + +// public BoardRules WithSize(int width, int height) +// { +// this.BoardSize = new Size(width, height); +// return this; +// } + +// public Size BoardSize { get; private set; } + +// public BoardRules AddPiece(IPiece piece) +// { +// pieces.Add(piece.Name, piece); +// return this; +// } +//} + +//public class BoardPieceRules(BoardRules rules, IPiece piece) +//{ +// public IPiece Piece { get; } = piece; +// public BoardRules WithStartingPositions(ICollection positions) +// { +// // Validate positions against board size +// foreach (var pos in positions) +// { +// if (pos.X < 0 || pos.Y < 0 || pos.X >= rules.BoardSize.Width || pos.Y >= rules.BoardSize.Height) +// { +// throw new ArgumentOutOfRangeException(nameof(positions), $"Position {pos} is out of bounds for board size {rules.BoardSize}."); +// } +// } +// // Assuming piece has a way to set starting positions, which it currently does not. +// // This is just a placeholder to show intent. +// // piece.SetStartingPositions(positions); +// return rules; +// } +//} + +//public interface IPiece +//{ +// public string Name { get; } +// public ICollection MoveSet { get; } +// public ICollection PromotedMoveSet { get; } + +// /// +// /// The starting positions for this type of piece on the board. There could be one or many. +// /// +// public ICollection StartingPositions { get; } +//} + +//public class GoldGeneral : IPiece +//{ +// public string Name => nameof(GoldGeneral); +// public ICollection MoveSet => PieceMoves.GoldGeneralMoves; +// public ICollection PromotedMoveSet => PieceMoves.GoldGeneralMoves; + +// public ICollection StartingPositions => [new(3, 0), new(5, 0), new(4, 1)]; +//} + +//public class Pawn : IPiece +//{ +// public string Name => nameof(Pawn); +// public ICollection MoveSet => PieceMoves.PawnMoves; +// public ICollection PromotedMoveSet => PieceMoves.GoldGeneralMoves; +//} + +//public class Knight : IPiece +//{ +// public string Name => nameof(Knight); +// public ICollection MoveSet => PieceMoves.KnightMoves; +// public ICollection PromotedMoveSet => PieceMoves.GoldGeneralMoves; +//} + +//public class Bishop : IPiece +//{ +// public string Name => nameof(Bishop); +// public ICollection MoveSet => PieceMoves.BishopMoves; +// public ICollection PromotedMoveSet => PieceMoves.BishopMoves; +//} + +//public class Luke +//{ +// public void Yep() +// { +// var board = new BoardRules() +// .WithSize(9, 9) +// .AddPiece(new Pawn()) +// } +//} \ No newline at end of file diff --git a/BoardRules/BoardRules.csproj b/BoardRules/BoardRules.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/BoardRules/BoardRules.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Shogi.Domain/ValueObjects/Enums.cs b/Shogi.Domain/ValueObjects/Enums.cs index 0bc6749..e259d0c 100644 --- a/Shogi.Domain/ValueObjects/Enums.cs +++ b/Shogi.Domain/ValueObjects/Enums.cs @@ -32,3 +32,9 @@ public enum WhichPiece Pawn, //PromotedPawn, } + +public enum WhichPlayer +{ + Player1, + Player2 +} diff --git a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Direction.cs b/Shogi.Domain/ValueObjects/Movement/Direction.cs similarity index 91% rename from Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Direction.cs rename to Shogi.Domain/ValueObjects/Movement/Direction.cs index 24dc12e..e2e71c8 100644 --- a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Direction.cs +++ b/Shogi.Domain/ValueObjects/Movement/Direction.cs @@ -1,4 +1,4 @@ -namespace Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +namespace Shogi.Domain.ValueObjects; public static class Direction { diff --git a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Distance.cs b/Shogi.Domain/ValueObjects/Movement/Distance.cs similarity index 80% rename from Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Distance.cs rename to Shogi.Domain/ValueObjects/Movement/Distance.cs index 915f3af..dfa7362 100644 --- a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Distance.cs +++ b/Shogi.Domain/ValueObjects/Movement/Distance.cs @@ -1,4 +1,4 @@ -namespace Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +namespace Shogi.Domain.ValueObjects; public enum Distance { diff --git a/Shogi.Domain/ValueObjects/Move.cs b/Shogi.Domain/ValueObjects/Movement/Move.cs similarity index 100% rename from Shogi.Domain/ValueObjects/Move.cs rename to Shogi.Domain/ValueObjects/Movement/Move.cs diff --git a/Shogi.Domain/ValueObjects/MoveResult.cs b/Shogi.Domain/ValueObjects/Movement/MoveResult.cs similarity index 100% rename from Shogi.Domain/ValueObjects/MoveResult.cs rename to Shogi.Domain/ValueObjects/Movement/MoveResult.cs diff --git a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Path.cs b/Shogi.Domain/ValueObjects/Movement/Path.cs similarity index 85% rename from Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Path.cs rename to Shogi.Domain/ValueObjects/Movement/Path.cs index ffd22f3..14ee1a5 100644 --- a/Shogi.Domain/YetToBeAssimilatedIntoDDD/Pathing/Path.cs +++ b/Shogi.Domain/ValueObjects/Movement/Path.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +namespace Shogi.Domain.ValueObjects.Movement; [DebuggerDisplay("{Step} - {Distance}")] public record Path @@ -17,7 +17,7 @@ public record Path public Path(Vector2 step, Distance distance = Distance.OneStep) { Step = step; - this.Distance = distance; + Distance = distance; } public Path Invert() => new(Vector2.Negate(Step), Distance); diff --git a/Shogi.Domain/ValueObjects/Bishop.cs b/Shogi.Domain/ValueObjects/Pieces/Bishop.cs similarity index 96% rename from Shogi.Domain/ValueObjects/Bishop.cs rename to Shogi.Domain/ValueObjects/Pieces/Bishop.cs index 7f7d315..4e721f4 100644 --- a/Shogi.Domain/ValueObjects/Bishop.cs +++ b/Shogi.Domain/ValueObjects/Pieces/Bishop.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects diff --git a/Shogi.Domain/ValueObjects/GoldGeneral.cs b/Shogi.Domain/ValueObjects/Pieces/GoldGeneral.cs similarity index 93% rename from Shogi.Domain/ValueObjects/GoldGeneral.cs rename to Shogi.Domain/ValueObjects/Pieces/GoldGeneral.cs index e5b0628..a45a0b2 100644 --- a/Shogi.Domain/ValueObjects/GoldGeneral.cs +++ b/Shogi.Domain/ValueObjects/Pieces/GoldGeneral.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects; diff --git a/Shogi.Domain/ValueObjects/King.cs b/Shogi.Domain/ValueObjects/Pieces/King.cs similarity index 93% rename from Shogi.Domain/ValueObjects/King.cs rename to Shogi.Domain/ValueObjects/Pieces/King.cs index d7cf66c..6f66cf5 100644 --- a/Shogi.Domain/ValueObjects/King.cs +++ b/Shogi.Domain/ValueObjects/Pieces/King.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects; diff --git a/Shogi.Domain/ValueObjects/Knight.cs b/Shogi.Domain/ValueObjects/Pieces/Knight.cs similarity index 94% rename from Shogi.Domain/ValueObjects/Knight.cs rename to Shogi.Domain/ValueObjects/Pieces/Knight.cs index d54cb98..acccfb8 100644 --- a/Shogi.Domain/ValueObjects/Knight.cs +++ b/Shogi.Domain/ValueObjects/Pieces/Knight.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects diff --git a/Shogi.Domain/ValueObjects/Lance.cs b/Shogi.Domain/ValueObjects/Pieces/Lance.cs similarity index 94% rename from Shogi.Domain/ValueObjects/Lance.cs rename to Shogi.Domain/ValueObjects/Pieces/Lance.cs index 690a77c..aaf54e9 100644 --- a/Shogi.Domain/ValueObjects/Lance.cs +++ b/Shogi.Domain/ValueObjects/Pieces/Lance.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects diff --git a/Shogi.Domain/ValueObjects/Pawn.cs b/Shogi.Domain/ValueObjects/Pieces/Pawn.cs similarity index 94% rename from Shogi.Domain/ValueObjects/Pawn.cs rename to Shogi.Domain/ValueObjects/Pieces/Pawn.cs index 6494f74..7ce3e22 100644 --- a/Shogi.Domain/ValueObjects/Pawn.cs +++ b/Shogi.Domain/ValueObjects/Pieces/Pawn.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects diff --git a/Shogi.Domain/ValueObjects/Piece.cs b/Shogi.Domain/ValueObjects/Pieces/Piece.cs similarity index 98% rename from Shogi.Domain/ValueObjects/Piece.cs rename to Shogi.Domain/ValueObjects/Pieces/Piece.cs index 51826d8..d57e8ec 100644 --- a/Shogi.Domain/ValueObjects/Piece.cs +++ b/Shogi.Domain/ValueObjects/Pieces/Piece.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Diagnostics; namespace Shogi.Domain.ValueObjects diff --git a/Shogi.Domain/ValueObjects/Rook.cs b/Shogi.Domain/ValueObjects/Pieces/Rook.cs similarity index 96% rename from Shogi.Domain/ValueObjects/Rook.cs rename to Shogi.Domain/ValueObjects/Pieces/Rook.cs index 45ab9d6..5624600 100644 --- a/Shogi.Domain/ValueObjects/Rook.cs +++ b/Shogi.Domain/ValueObjects/Pieces/Rook.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects; diff --git a/Shogi.Domain/ValueObjects/SilverGeneral.cs b/Shogi.Domain/ValueObjects/Pieces/SilverGeneral.cs similarity index 95% rename from Shogi.Domain/ValueObjects/SilverGeneral.cs rename to Shogi.Domain/ValueObjects/Pieces/SilverGeneral.cs index a37becb..82c5519 100644 --- a/Shogi.Domain/ValueObjects/SilverGeneral.cs +++ b/Shogi.Domain/ValueObjects/Pieces/SilverGeneral.cs @@ -1,4 +1,4 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Collections.ObjectModel; namespace Shogi.Domain.ValueObjects diff --git a/Shogi.Domain/ValueObjects/ShogiBoard.cs b/Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs similarity index 97% rename from Shogi.Domain/ValueObjects/ShogiBoard.cs rename to Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs index e0bb92e..b139566 100644 --- a/Shogi.Domain/ValueObjects/ShogiBoard.cs +++ b/Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs @@ -1,4 +1,5 @@ -using Shogi.Domain.YetToBeAssimilatedIntoDDD; +using Shogi.Domain.ValueObjects.Movement; +using Shogi.Domain.YetToBeAssimilatedIntoDDD; namespace Shogi.Domain.ValueObjects; /// @@ -247,7 +248,7 @@ public sealed class ShogiBoard(BoardState initialState) { var list = new List(10); var position = path.Step + piecePosition; - if (path.Distance == YetToBeAssimilatedIntoDDD.Pathing.Distance.MultiStep) + if (path.Distance == Distance.MultiStep) { while (position.IsInsideBoardBoundary()) @@ -340,7 +341,7 @@ public sealed class ShogiBoard(BoardState initialState) else { var multiStepPaths = matchingPaths - .Where(path => path.Distance == YetToBeAssimilatedIntoDDD.Pathing.Distance.MultiStep) + .Where(path => path.Distance == Distance.MultiStep) .ToArray(); if (multiStepPaths.Length == 0) { @@ -371,7 +372,7 @@ public sealed class ShogiBoard(BoardState initialState) return new MoveResult(true); } - private static IEnumerable GetPositionsAlongPath(Vector2 from, Vector2 to, YetToBeAssimilatedIntoDDD.Pathing.Path path) + private static IEnumerable GetPositionsAlongPath(Vector2 from, Vector2 to, Path path) { var next = from; while (next != to && next.X >= 0 && next.X < 9 && next.Y >= 0 && next.Y < 9) diff --git a/Shogi.Domain/ValueObjects/StandardRules.cs b/Shogi.Domain/ValueObjects/Rules/StandardRules.cs similarity index 100% rename from Shogi.Domain/ValueObjects/StandardRules.cs rename to Shogi.Domain/ValueObjects/Rules/StandardRules.cs diff --git a/Shogi.Domain/ValueObjects/WhichPlayer.cs b/Shogi.Domain/ValueObjects/WhichPlayer.cs deleted file mode 100644 index 796c095..0000000 --- a/Shogi.Domain/ValueObjects/WhichPlayer.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Shogi.Domain.ValueObjects -{ - public enum WhichPlayer - { - Player1, - Player2 - } -} diff --git a/Shogi.sln b/Shogi.sln index bba4cc1..349696f 100644 --- a/Shogi.sln +++ b/Shogi.sln @@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shogi.Api", "Shogi.Api\Shog EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETests", "Tests\E2ETests\E2ETests.csproj", "{401120C3-45D6-4A23-8D87-C2BED29F4950}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoardRules", "BoardRules\BoardRules.csproj", "{5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +69,10 @@ Global {401120C3-45D6-4A23-8D87-C2BED29F4950}.Debug|Any CPU.Build.0 = Debug|Any CPU {401120C3-45D6-4A23-8D87-C2BED29F4950}.Release|Any CPU.ActiveCfg = Release|Any CPU {401120C3-45D6-4A23-8D87-C2BED29F4950}.Release|Any CPU.Build.0 = Release|Any CPU + {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B2F47A0-6AD5-4DA9-9CFE-9F52F634DD5E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tests/UnitTests/NotationShould.cs b/Tests/UnitTests/NotationShould.cs index d42f72f..577b8a4 100644 --- a/Tests/UnitTests/NotationShould.cs +++ b/Tests/UnitTests/NotationShould.cs @@ -1,26 +1,25 @@ -using System.Numerics; -using Shogi.Domain.YetToBeAssimilatedIntoDDD; +using Shogi.Domain.YetToBeAssimilatedIntoDDD; +using System.Numerics; -namespace UnitTests +namespace UnitTests; + +public class NotationShould { - public class NotationShould + [Fact] + public void ConvertFromNotationToVector() { - [Fact] - public void ConvertFromNotationToVector() - { - Notation.FromBoardNotation("A1").Should().Be(new Vector2(0, 0)); - Notation.FromBoardNotation("E5").Should().Be(new Vector2(4, 4)); - Notation.FromBoardNotation("I9").Should().Be(new Vector2(8, 8)); - Notation.FromBoardNotation("C3").Should().Be(new Vector2(2, 2)); - } + Notation.FromBoardNotation("A1").Should().Be(new Vector2(0, 0)); + Notation.FromBoardNotation("E5").Should().Be(new Vector2(4, 4)); + Notation.FromBoardNotation("I9").Should().Be(new Vector2(8, 8)); + Notation.FromBoardNotation("C3").Should().Be(new Vector2(2, 2)); + } - [Fact] - public void ConvertFromVectorToNotation() - { - Notation.ToBoardNotation(new Vector2(0, 0)).Should().Be("A1"); - Notation.ToBoardNotation(new Vector2(4, 4)).Should().Be("E5"); - Notation.ToBoardNotation(new Vector2(8, 8)).Should().Be("I9"); - Notation.ToBoardNotation(new Vector2(2, 2)).Should().Be("C3"); - } + [Fact] + public void ConvertFromVectorToNotation() + { + Notation.ToBoardNotation(new Vector2(0, 0)).Should().Be("A1"); + Notation.ToBoardNotation(new Vector2(4, 4)).Should().Be("E5"); + Notation.ToBoardNotation(new Vector2(8, 8)).Should().Be("I9"); + Notation.ToBoardNotation(new Vector2(2, 2)).Should().Be("C3"); } } diff --git a/Tests/UnitTests/RookShould.cs b/Tests/UnitTests/RookShould.cs index c48b0ea..5d0ab72 100644 --- a/Tests/UnitTests/RookShould.cs +++ b/Tests/UnitTests/RookShould.cs @@ -1,5 +1,5 @@ using Shogi.Domain.ValueObjects; -using Shogi.Domain.YetToBeAssimilatedIntoDDD.Pathing; +using Shogi.Domain.ValueObjects.Movement; using System.Numerics; namespace UnitTests; diff --git a/Tests/UnitTests/ShogiShould.cs b/Tests/UnitTests/ShogiShould.cs index 064e303..f35ee88 100644 --- a/Tests/UnitTests/ShogiShould.cs +++ b/Tests/UnitTests/ShogiShould.cs @@ -1,460 +1,457 @@ using Shogi.Domain.ValueObjects; -using System; -using System.Linq; -namespace UnitTests +namespace UnitTests; + +public class ShogiShould { - public class ShogiShould + private readonly ITestOutputHelper console; + public ShogiShould(ITestOutputHelper console) { - private readonly ITestOutputHelper console; - public ShogiShould(ITestOutputHelper console) + this.console = console; + } + + [Fact] + public void MoveAPieceToAnEmptyPosition() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + + board["A4"].Should().BeNull(); + var expectedPiece = board["A3"]; + expectedPiece.Should().NotBeNull(); + + // Act + shogi.Move("A3", "A4", false); + + // Assert + board["A3"].Should().BeNull(); + board["A4"].Should().Be(expectedPiece); + } + + [Fact] + public void AllowValidMoves_AfterCheck() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + // P1 Pawn + shogi.Move("C3", "C4", false); + // P2 Pawn + shogi.Move("G7", "G6", false); + // P1 Bishop puts P2 in check + shogi.Move("B2", "G7", false); + board.InCheck.Should().Be(WhichPlayer.Player2); + + // Act - P2 is able to un-check theirself. + /// P2 King moves out of check + shogi.Move("E9", "E8", false); + + // Assert + using (new AssertionScope()) { - this.console = console; + board.InCheck.Should().BeNull(); } + } - [Fact] - public void MoveAPieceToAnEmptyPosition() + [Fact] + public void PreventInvalidMoves_MoveFromEmptyPosition() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + board["D5"].Should().BeNull(); + + // Act + var moveResult = shogi.Move("D5", "D6", false); + + // Assert + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + board["D5"].Should().BeNull(); + board["D6"].Should().BeNull(); + board.Player1Hand.Should().BeEmpty(); + board.Player2Hand.Should().BeEmpty(); + } + + [Fact] + public void PreventInvalidMoves_MoveToCurrentPosition() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + var expectedPiece = board["A3"]; + + // Act - P1 "moves" pawn to the position it already exists at. + var moveResult = shogi.Move("A3", "A3", false); + + // Assert + using (new AssertionScope()) { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - - board["A4"].Should().BeNull(); - var expectedPiece = board["A3"]; - expectedPiece.Should().NotBeNull(); - - // Act - shogi.Move("A3", "A4", false); - - // Assert - board["A3"].Should().BeNull(); - board["A4"].Should().Be(expectedPiece); - } - - [Fact] - public void AllowValidMoves_AfterCheck() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - // P1 Pawn - shogi.Move("C3", "C4", false); - // P2 Pawn - shogi.Move("G7", "G6", false); - // P1 Bishop puts P2 in check - shogi.Move("B2", "G7", false); - board.InCheck.Should().Be(WhichPlayer.Player2); - - // Act - P2 is able to un-check theirself. - /// P2 King moves out of check - shogi.Move("E9", "E8", false); - - // Assert - using (new AssertionScope()) - { - board.InCheck.Should().BeNull(); - } - } - - [Fact] - public void PreventInvalidMoves_MoveFromEmptyPosition() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - board["D5"].Should().BeNull(); - - // Act - var moveResult = shogi.Move("D5", "D6", false); - - // Assert moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["D5"].Should().BeNull(); - board["D6"].Should().BeNull(); + moveResult.IsSuccess.Should().BeFalse(); board["A3"].Should().Be(expectedPiece); board.Player1Hand.Should().BeEmpty(); board.Player2Hand.Should().BeEmpty(); } - - [Fact] - public void PreventInvalidMoves_MoveToCurrentPosition() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - var expectedPiece = board["A3"]; - - // Act - P1 "moves" pawn to the position it already exists at. - var moveResult = shogi.Move("A3", "A3", false); - - // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["A3"].Should().Be(expectedPiece); - board.Player1Hand.Should().BeEmpty(); - board.Player2Hand.Should().BeEmpty(); - } - } - - [Fact] - public void PreventInvalidMoves_MoveSet() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - var expectedPiece = board["D1"]; - expectedPiece!.WhichPiece.Should().Be(WhichPiece.GoldGeneral); - - // Act - Move General illegally - var moveResult = shogi.Move("D1", "D5", false); - - // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["D1"].Should().Be(expectedPiece); - board["D5"].Should().BeNull(); - board.Player1Hand.Should().BeEmpty(); - board.Player2Hand.Should().BeEmpty(); - } - } - - [Fact] - public void PreventInvalidMoves_Ownership() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - var expectedPiece = board["A7"]; - expectedPiece!.Owner.Should().Be(WhichPlayer.Player2); - board.WhoseTurn.Should().Be(WhichPlayer.Player1); - - // Act - Move Player2 Pawn when it is Player1 turn. - var moveResult = shogi.Move("A7", "A6", false); - - // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["A7"].Should().Be(expectedPiece); - board["A6"].Should().BeNull(); - } - } - - [Fact] - public void PreventInvalidMoves_MoveThroughAllies() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - var lance = board["A1"]; - var pawn = board["A3"]; - lance!.Owner.Should().Be(pawn!.Owner); - - // Act - Move P1 Lance through P1 Pawn. - var moveResult = shogi.Move("A1", "A5", false); - - // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["A1"].Should().Be(lance); - board["A3"].Should().Be(pawn); - board["A5"].Should().BeNull(); - } - } - - [Fact] - public void PreventInvalidMoves_CaptureAlly() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - var knight = board["B1"]; - var pawn = board["C3"]; - knight!.Owner.Should().Be(pawn!.Owner); - - // Act - P1 Knight tries to capture P1 Pawn. - var moveResult = shogi.Move("B1", "C3", false); - - // Arrange - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board["B1"].Should().Be(knight); - board["C3"].Should().Be(pawn); - board.Player1Hand.Should().BeEmpty(); - board.Player2Hand.Should().BeEmpty(); - } - } - - [Fact] - public void PreventInvalidMoves_Check() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - // P1 Pawn - shogi.Move("C3", "C4", false); - // P2 Pawn - shogi.Move("G7", "G6", false); - // P1 Bishop puts P2 in check - shogi.Move("B2", "G7", false); - board.InCheck.Should().Be(WhichPlayer.Player2); - var lance = board["I9"]; - - // Act - P2 moves Lance while in check. - var moveResult = shogi.Move("I9", "I8", false); - - // Assert - using (new AssertionScope()) - { - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); board.InCheck.Should().Be(WhichPlayer.Player2); - board["I9"].Should().Be(lance); - board["I8"].Should().BeNull(); - } - } - - [Fact] - public void PreventInvalidDrops_MoveSet() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - // P1 Pawn - shogi.Move("C3", "C4", false); - // P2 Pawn - shogi.Move("I7", "I6", false); - // P1 Bishop takes P2 Pawn. - shogi.Move("B2", "G7", false); - // P2 Gold, block check from P1 Bishop. - shogi.Move("F9", "F8", false); - // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance - shogi.Move("G7", "H8", true); - // P2 Pawn again - shogi.Move("I6", "I5", false); - // P1 Bishop takes P2 Knight - shogi.Move("H8", "H9", false); - // P2 Pawn again - shogi.Move("I5", "I4", false); - // P1 Bishop takes P2 Lance - shogi.Move("H9", "I9", false); - // P2 Pawn captures P1 Pawn - shogi.Move("I4", "I3", false); - board.Player1Hand.Count.Should().Be(4); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - board.WhoseTurn.Should().Be(WhichPlayer.Player1); - - // Act | Assert - Illegally placing Knight from the hand in farthest rank. - board["H9"].Should().BeNull(); - var moveResult = shogi.Move(WhichPiece.Knight, "H9"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H9"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); - - // Act | Assert - Illegally placing Knight from the hand in second farthest row. - board["H8"].Should().BeNull(); - moveResult = shogi.Move(WhichPiece.Knight, "H8"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H8"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); - - // Act | Assert - Illegally place Lance from the hand. - board["H9"].Should().BeNull(); - moveResult = shogi.Move(WhichPiece.Knight, "H9"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H9"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); - - // Act | Assert - Illegally place Pawn from the hand. - board["H9"].Should().BeNull(); - moveResult = shogi.Move(WhichPiece.Pawn, "H9"); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - board["H9"].Should().BeNull(); - board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); - - // // Act | Assert - Illegally place Pawn from the hand in a row which already has an unpromoted Pawn. - // // TODO - } - - [Fact] - public void PreventInvalidDrop_Check() - { - // Arrange - var shogi = MockShogiBoard(); - // P1 Pawn - shogi.Move("C3", "C4", false).IsSuccess.Should().BeTrue(); - // P2 Pawn - shogi.Move("G7", "G6", false).IsSuccess.Should().BeTrue(); - // P1 Pawn, arbitrary move. - shogi.Move("A3", "A4", false).IsSuccess.Should().BeTrue(); - // P2 Bishop takes P1 Bishop - shogi.Move("H8", "B2", false).IsSuccess.Should().BeTrue(); - // P1 Silver takes P2 Bishop - shogi.Move("C1", "B2", false).IsSuccess.Should().BeTrue(); - // P2 Pawn, arbtrary move - shogi.Move("A7", "A6", false).IsSuccess.Should().BeTrue(); - // P1 drop Bishop, place P2 in check - shogi.Move(WhichPiece.Bishop, "G7").IsSuccess.Should().BeTrue(); - shogi.BoardState.InCheck.Should().Be(WhichPlayer.Player2); - shogi.BoardState.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.BoardState["E5"].Should().BeNull(); - - // Act - P2 places a Bishop while in check. - var moveResult = shogi.Move(WhichPiece.Bishop, "E5"); - - // Assert - using var scope = new AssertionScope(); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - shogi.BoardState["E5"].Should().BeNull(); - shogi.BoardState.InCheck.Should().Be(WhichPlayer.Player2); - shogi.BoardState.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - } - - [Fact] - public void PreventInvalidDrop_Capture() - { - // Arrange - var shogi = MockShogiBoard(); - // P1 Pawn - shogi.Move("C3", "C4", false); - // P2 Pawn - shogi.Move("G7", "G6", false); - // P1 Bishop capture P2 Bishop - shogi.Move("B2", "H8", false); - // P2 Pawn - shogi.Move("G6", "G5", false); - shogi.BoardState.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.BoardState["I9"].Should().NotBeNull(); - shogi.BoardState["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance); - shogi.BoardState["I9"]!.Owner.Should().Be(WhichPlayer.Player2); - - // Act - P1 tries to place a piece where an opponent's piece resides. - var moveResult = shogi.Move(WhichPiece.Bishop, "I9"); - - // Assert - using var scope = new AssertionScope(); - moveResult.Should().NotBeNull(); - moveResult.IsSuccess.Should().BeFalse(); - shogi.BoardState.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); - shogi.BoardState["I9"].Should().NotBeNull(); - shogi.BoardState["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance); - shogi.BoardState["I9"]!.Owner.Should().Be(WhichPlayer.Player2); - } - - [Fact] - public void Check() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - // P1 Pawn - shogi.Move("C3", "C4", false); - // P2 Pawn - shogi.Move("G7", "G6", false); - - // Act - P1 Bishop, check - shogi.Move("B2", "G7", false); - - // Assert - board.InCheck.Should().Be(WhichPlayer.Player2); - } - - [Fact] - public void Promote() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - // P1 Pawn - shogi.Move("C3", "C4", false); - // P2 Pawn - shogi.Move("G7", "G6", false); - - // Act - P1 moves across promote threshold. - shogi.Move("B2", "G7", true); - - // Assert - using (new AssertionScope()) - { - board["B2"].Should().BeNull(); - board["G7"].Should().NotBeNull(); - board["G7"]!.WhichPiece.Should().Be(WhichPiece.Bishop); - board["G7"]!.Owner.Should().Be(WhichPlayer.Player1); - board["G7"]!.IsPromoted.Should().BeTrue(); - } - } - - [Fact] - public void Capture() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - var p1Bishop = board["B2"]; - p1Bishop!.WhichPiece.Should().Be(WhichPiece.Bishop); - shogi.Move("C3", "C4", false); - shogi.Move("G7", "G6", false); - - // Act - P1 Bishop captures P2 Bishop - shogi.Move("B2", "H8", false); - - // Assert - board["B2"].Should().BeNull(); - board["H8"].Should().Be(p1Bishop); - - board - .Player1Hand - .Should() - .ContainSingle(p => p.WhichPiece == WhichPiece.Bishop && p.Owner == WhichPlayer.Player1); - } - - [Fact] - public void CheckMate() - { - // Arrange - var shogi = MockShogiBoard(); - var board = shogi.BoardState; - // P1 Rook - shogi.Move("H2", "E2", false); - // P2 Gold - shogi.Move("F9", "G8", false); - // P1 Pawn - shogi.Move("E3", "E4", false); - // P2 other Gold - shogi.Move("D9", "C8", false); - // P1 same Pawn - shogi.Move("E4", "E5", false); - // P2 Pawn - shogi.Move("E7", "E6", false); - // P1 Pawn takes P2 Pawn - shogi.Move("E5", "E6", false); - // P2 King - shogi.Move("E9", "E8", false); - // P1 Pawn promotes; threatens P2 King - shogi.Move("E6", "E7", true); - // P2 King retreat - shogi.Move("E8", "E9", false); - - // Act - P1 Pawn wins by checkmate. - shogi.Move("E7", "E8", false); - - // Assert - checkmate - board.IsCheckmate.Should().BeTrue(); - board.InCheck.Should().Be(WhichPlayer.Player2); - } - - private static ShogiBoard MockShogiBoard() => new(BoardState.StandardStarting); } + + [Fact] + public void PreventInvalidMoves_MoveSet() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + var expectedPiece = board["D1"]; + expectedPiece!.WhichPiece.Should().Be(WhichPiece.GoldGeneral); + + // Act - Move General illegally + var moveResult = shogi.Move("D1", "D5", false); + + // Assert + using (new AssertionScope()) + { + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + board["D1"].Should().Be(expectedPiece); + board["D5"].Should().BeNull(); + board.Player1Hand.Should().BeEmpty(); + board.Player2Hand.Should().BeEmpty(); + } + } + + [Fact] + public void PreventInvalidMoves_Ownership() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + var expectedPiece = board["A7"]; + expectedPiece!.Owner.Should().Be(WhichPlayer.Player2); + board.WhoseTurn.Should().Be(WhichPlayer.Player1); + + // Act - Move Player2 Pawn when it is Player1 turn. + var moveResult = shogi.Move("A7", "A6", false); + + // Assert + using (new AssertionScope()) + { + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); board["A7"].Should().Be(expectedPiece); + board["A6"].Should().BeNull(); + } + } + + [Fact] + public void PreventInvalidMoves_MoveThroughAllies() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + var lance = board["A1"]; + var pawn = board["A3"]; + lance!.Owner.Should().Be(pawn!.Owner); + + // Act - Move P1 Lance through P1 Pawn. + var moveResult = shogi.Move("A1", "A5", false); + + // Assert + using (new AssertionScope()) + { + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); board["A1"].Should().Be(lance); + board["A3"].Should().Be(pawn); + board["A5"].Should().BeNull(); + } + } + + [Fact] + public void PreventInvalidMoves_CaptureAlly() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + var knight = board["B1"]; + var pawn = board["C3"]; + knight!.Owner.Should().Be(pawn!.Owner); + + // Act - P1 Knight tries to capture P1 Pawn. + var moveResult = shogi.Move("B1", "C3", false); + + // Arrange + using (new AssertionScope()) + { + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); board["B1"].Should().Be(knight); + board["C3"].Should().Be(pawn); + board.Player1Hand.Should().BeEmpty(); + board.Player2Hand.Should().BeEmpty(); + } + } + + [Fact] + public void PreventInvalidMoves_Check() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + // P1 Pawn + shogi.Move("C3", "C4", false); + // P2 Pawn + shogi.Move("G7", "G6", false); + // P1 Bishop puts P2 in check + shogi.Move("B2", "G7", false); + board.InCheck.Should().Be(WhichPlayer.Player2); + var lance = board["I9"]; + + // Act - P2 moves Lance while in check. + var moveResult = shogi.Move("I9", "I8", false); + + // Assert + using (new AssertionScope()) + { + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); board.InCheck.Should().Be(WhichPlayer.Player2); + board["I9"].Should().Be(lance); + board["I8"].Should().BeNull(); + } + } + + [Fact] + public void PreventInvalidDrops_MoveSet() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + // P1 Pawn + shogi.Move("C3", "C4", false); + // P2 Pawn + shogi.Move("I7", "I6", false); + // P1 Bishop takes P2 Pawn. + shogi.Move("B2", "G7", false); + // P2 Gold, block check from P1 Bishop. + shogi.Move("F9", "F8", false); + // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance + shogi.Move("G7", "H8", true); + // P2 Pawn again + shogi.Move("I6", "I5", false); + // P1 Bishop takes P2 Knight + shogi.Move("H8", "H9", false); + // P2 Pawn again + shogi.Move("I5", "I4", false); + // P1 Bishop takes P2 Lance + shogi.Move("H9", "I9", false); + // P2 Pawn captures P1 Pawn + shogi.Move("I4", "I3", false); + board.Player1Hand.Count.Should().Be(4); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + board.WhoseTurn.Should().Be(WhichPlayer.Player1); + + // Act | Assert - Illegally placing Knight from the hand in farthest rank. + board["H9"].Should().BeNull(); + var moveResult = shogi.Move(WhichPiece.Knight, "H9"); + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + board["H9"].Should().BeNull(); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + + // Act | Assert - Illegally placing Knight from the hand in second farthest row. + board["H8"].Should().BeNull(); + moveResult = shogi.Move(WhichPiece.Knight, "H8"); + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + board["H8"].Should().BeNull(); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Knight); + + // Act | Assert - Illegally place Lance from the hand. + board["H9"].Should().BeNull(); + moveResult = shogi.Move(WhichPiece.Knight, "H9"); + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + board["H9"].Should().BeNull(); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Lance); + + // Act | Assert - Illegally place Pawn from the hand. + board["H9"].Should().BeNull(); + moveResult = shogi.Move(WhichPiece.Pawn, "H9"); + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + board["H9"].Should().BeNull(); + board.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Pawn); + + // // Act | Assert - Illegally place Pawn from the hand in a row which already has an unpromoted Pawn. + // // TODO + } + + [Fact] + public void PreventInvalidDrop_Check() + { + // Arrange + var shogi = MockShogiBoard(); + // P1 Pawn + shogi.Move("C3", "C4", false).IsSuccess.Should().BeTrue(); + // P2 Pawn + shogi.Move("G7", "G6", false).IsSuccess.Should().BeTrue(); + // P1 Pawn, arbitrary move. + shogi.Move("A3", "A4", false).IsSuccess.Should().BeTrue(); + // P2 Bishop takes P1 Bishop + shogi.Move("H8", "B2", false).IsSuccess.Should().BeTrue(); + // P1 Silver takes P2 Bishop + shogi.Move("C1", "B2", false).IsSuccess.Should().BeTrue(); + // P2 Pawn, arbtrary move + shogi.Move("A7", "A6", false).IsSuccess.Should().BeTrue(); + // P1 drop Bishop, place P2 in check + shogi.Move(WhichPiece.Bishop, "G7").IsSuccess.Should().BeTrue(); + shogi.BoardState.InCheck.Should().Be(WhichPlayer.Player2); + shogi.BoardState.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.BoardState["E5"].Should().BeNull(); + + // Act - P2 places a Bishop while in check. + var moveResult = shogi.Move(WhichPiece.Bishop, "E5"); + + // Assert + using var scope = new AssertionScope(); + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + shogi.BoardState["E5"].Should().BeNull(); + shogi.BoardState.InCheck.Should().Be(WhichPlayer.Player2); + shogi.BoardState.Player2Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + } + + [Fact] + public void PreventInvalidDrop_Capture() + { + // Arrange + var shogi = MockShogiBoard(); + // P1 Pawn + shogi.Move("C3", "C4", false); + // P2 Pawn + shogi.Move("G7", "G6", false); + // P1 Bishop capture P2 Bishop + shogi.Move("B2", "H8", false); + // P2 Pawn + shogi.Move("G6", "G5", false); + shogi.BoardState.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.BoardState["I9"].Should().NotBeNull(); + shogi.BoardState["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance); + shogi.BoardState["I9"]!.Owner.Should().Be(WhichPlayer.Player2); + + // Act - P1 tries to place a piece where an opponent's piece resides. + var moveResult = shogi.Move(WhichPiece.Bishop, "I9"); + + // Assert + using var scope = new AssertionScope(); + moveResult.Should().NotBeNull(); + moveResult.IsSuccess.Should().BeFalse(); + shogi.BoardState.Player1Hand.Should().ContainSingle(_ => _.WhichPiece == WhichPiece.Bishop); + shogi.BoardState["I9"].Should().NotBeNull(); + shogi.BoardState["I9"]!.WhichPiece.Should().Be(WhichPiece.Lance); + shogi.BoardState["I9"]!.Owner.Should().Be(WhichPlayer.Player2); + } + + [Fact] + public void Check() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + // P1 Pawn + shogi.Move("C3", "C4", false); + // P2 Pawn + shogi.Move("G7", "G6", false); + + // Act - P1 Bishop, check + shogi.Move("B2", "G7", false); + + // Assert + board.InCheck.Should().Be(WhichPlayer.Player2); + } + + [Fact] + public void Promote() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + // P1 Pawn + shogi.Move("C3", "C4", false); + // P2 Pawn + shogi.Move("G7", "G6", false); + + // Act - P1 moves across promote threshold. + shogi.Move("B2", "G7", true); + + // Assert + using (new AssertionScope()) + { + board["B2"].Should().BeNull(); + board["G7"].Should().NotBeNull(); + board["G7"]!.WhichPiece.Should().Be(WhichPiece.Bishop); + board["G7"]!.Owner.Should().Be(WhichPlayer.Player1); + board["G7"]!.IsPromoted.Should().BeTrue(); + } + } + + [Fact] + public void Capture() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + var p1Bishop = board["B2"]; + p1Bishop!.WhichPiece.Should().Be(WhichPiece.Bishop); + shogi.Move("C3", "C4", false); + shogi.Move("G7", "G6", false); + + // Act - P1 Bishop captures P2 Bishop + shogi.Move("B2", "H8", false); + + // Assert + board["B2"].Should().BeNull(); + board["H8"].Should().Be(p1Bishop); + + board + .Player1Hand + .Should() + .ContainSingle(p => p.WhichPiece == WhichPiece.Bishop && p.Owner == WhichPlayer.Player1); + } + + [Fact] + public void CheckMate() + { + // Arrange + var shogi = MockShogiBoard(); + var board = shogi.BoardState; + // P1 Rook + shogi.Move("H2", "E2", false); + // P2 Gold + shogi.Move("F9", "G8", false); + // P1 Pawn + shogi.Move("E3", "E4", false); + // P2 other Gold + shogi.Move("D9", "C8", false); + // P1 same Pawn + shogi.Move("E4", "E5", false); + // P2 Pawn + shogi.Move("E7", "E6", false); + // P1 Pawn takes P2 Pawn + shogi.Move("E5", "E6", false); + // P2 King + shogi.Move("E9", "E8", false); + // P1 Pawn promotes; threatens P2 King + shogi.Move("E6", "E7", true); + // P2 King retreat + shogi.Move("E8", "E9", false); + + // Act - P1 Pawn wins by checkmate. + shogi.Move("E7", "E8", false); + + // Assert - checkmate + board.IsCheckmate.Should().BeTrue(); + board.InCheck.Should().Be(WhichPlayer.Player2); + } + + private static ShogiBoard MockShogiBoard() => new(BoardState.StandardStarting); }