From 9ec91615a3bbde714cc5ac95a94ce8f4393ee8bb Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Wed, 29 Dec 2021 16:27:43 -0600 Subject: [PATCH] yep --- .../Properties/launchSettings.json | 6 +- Shogi.Domain.UnitTests/ShogiShould.cs | 170 +++++++++--------- Shogi.Domain.UnitTests/StandardRulesShould.cs | 31 ++++ Shogi.Domain/Shogi.cs | 10 +- Shogi.Domain/StandardRules.cs | 42 ++++- 5 files changed, 157 insertions(+), 102 deletions(-) create mode 100644 Shogi.Domain.UnitTests/StandardRulesShould.cs diff --git a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json index 230be61..05ff5bf 100644 --- a/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json +++ b/Gameboard.ShogiUI.Sockets/Properties/launchSettings.json @@ -1,13 +1,13 @@ { + "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:63676", - "sslPort": 44396 + "applicationUrl": "http://localhost:50728/", + "sslPort": 44315 } }, - "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "IIS Express": { "commandName": "IISExpress", diff --git a/Shogi.Domain.UnitTests/ShogiShould.cs b/Shogi.Domain.UnitTests/ShogiShould.cs index fc07cb5..745e28f 100644 --- a/Shogi.Domain.UnitTests/ShogiShould.cs +++ b/Shogi.Domain.UnitTests/ShogiShould.cs @@ -17,10 +17,12 @@ namespace Shogi.Domain.UnitTests //[Fact] //public void InitializeBoardStateWithMoves() //{ + // var board = new ShogiBoardState(); + // var rules = new StandardRules(board); // var moves = new[] // { - // // P1 Pawn - // new Move("A3", "A4") + // // P1 Pawn + // new Move("A3", "A4") // }; // var shogi = new Shogi(moves); // shogi.Board["A3"].Should().BeNull(); @@ -33,12 +35,12 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Pawn - // new Move("C3", "C4"), - // // P2 Pawn - // new Move("G7", "G6"), - // // P1 Bishop puts P2 in check - // new Move("B2", "G7"), + // // P1 Pawn + // new Move("C3", "C4"), + // // P2 Pawn + // new Move("G7", "G6"), + // // P1 Bishop puts P2 in check + // new Move("B2", "G7"), // }; // var shogi = new Shogi(moves); // shogi.InCheck.Should().Be(WhichPlayer.Player2); @@ -158,12 +160,12 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Pawn - // new Move("C3", "C4"), - // // P2 Pawn - // new Move("G7", "G6"), - // // P1 Bishop puts P2 in check - // new Move("B2", "G7") + // // P1 Pawn + // new Move("C3", "C4"), + // // P2 Pawn + // new Move("G7", "G6"), + // // P1 Bishop puts P2 in check + // new Move("B2", "G7") // }; // var shogi = new Shogi(moves); // shogi.InCheck.Should().Be(WhichPlayer.Player2); @@ -184,26 +186,26 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Pawn - // new Move("C3", "C4"), - // // P2 Pawn - // new Move("I7", "I6"), - // // P1 Bishop takes P2 Pawn. - // new Move("B2", "G7"), - // // P2 Gold, block check from P1 Bishop. - // new Move("F9", "F8"), - // // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance - // new Move("G7", "H8", true), - // // P2 Pawn again - // new Move("I6", "I5"), - // // P1 Bishop takes P2 Knight - // new Move("H8", "H9"), - // // P2 Pawn again - // new Move("I5", "I4"), - // // P1 Bishop takes P2 Lance - // new Move("H9", "I9"), - // // P2 Pawn captures P1 Pawn - // new Move("I4", "I3") + // // P1 Pawn + // new Move("C3", "C4"), + // // P2 Pawn + // new Move("I7", "I6"), + // // P1 Bishop takes P2 Pawn. + // new Move("B2", "G7"), + // // P2 Gold, block check from P1 Bishop. + // new Move("F9", "F8"), + // // P1 Bishop takes P2 Bishop, promotes so it can capture P2 Knight and P2 Lance + // new Move("G7", "H8", true), + // // P2 Pawn again + // new Move("I6", "I5"), + // // P1 Bishop takes P2 Knight + // new Move("H8", "H9"), + // // P2 Pawn again + // new Move("I5", "I4"), + // // P1 Bishop takes P2 Lance + // new Move("H9", "I9"), + // // P2 Pawn captures P1 Pawn + // new Move("I4", "I3") // }; // var shogi = new Shogi(moves); // shogi.Player1Hand.Count.Should().Be(4); @@ -251,20 +253,20 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Pawn - // new Move("C3", "C4"), - // // P2 Pawn - // new Move("G7", "G6"), - // // P1 Pawn, arbitrary move. - // new Move("A3", "A4"), - // // P2 Bishop takes P1 Bishop - // new Move("H8", "B2"), - // // P1 Silver takes P2 Bishop - // new Move("C1", "B2"), - // // P2 Pawn, arbtrary move - // new Move("A7", "A6"), - // // P1 drop Bishop, place P2 in check - // new Move(WhichPiece.Bishop, "G7") + // // P1 Pawn + // new Move("C3", "C4"), + // // P2 Pawn + // new Move("G7", "G6"), + // // P1 Pawn, arbitrary move. + // new Move("A3", "A4"), + // // P2 Bishop takes P1 Bishop + // new Move("H8", "B2"), + // // P1 Silver takes P2 Bishop + // new Move("C1", "B2"), + // // P2 Pawn, arbtrary move + // new Move("A7", "A6"), + // // P1 drop Bishop, place P2 in check + // new Move(WhichPiece.Bishop, "G7") // }; // var shogi = new Shogi(moves); // shogi.InCheck.Should().Be(WhichPlayer.Player2); @@ -287,14 +289,14 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Pawn - // new Move("C3", "C4"), - // // P2 Pawn - // new Move("G7", "G6"), - // // P1 Bishop capture P2 Bishop - // new Move("B2", "H8"), - // // P2 Pawn - // new Move("G6", "G5") + // // P1 Pawn + // new Move("C3", "C4"), + // // P2 Pawn + // new Move("G7", "G6"), + // // P1 Bishop capture P2 Bishop + // new Move("B2", "H8"), + // // P2 Pawn + // new Move("G6", "G5") // }; // var shogi = new Shogi(moves); // using (new AssertionScope()) @@ -325,10 +327,10 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Pawn - // new Move("C3", "C4"), - // // P2 Pawn - // new Move("G7", "G6"), + // // P1 Pawn + // new Move("C3", "C4"), + // // P2 Pawn + // new Move("G7", "G6"), // }; // var shogi = new Shogi(moves); @@ -345,10 +347,10 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Pawn - // new Move("C3", "C4" ), - // // P2 Pawn - // new Move("G7", "G6" ) + // // P1 Pawn + // new Move("C3", "C4" ), + // // P2 Pawn + // new Move("G7", "G6" ) // }; // var shogi = new Shogi(moves); @@ -373,26 +375,26 @@ namespace Shogi.Domain.UnitTests // // Arrange // var moves = new[] // { - // // P1 Rook - // new Move("H2", "E2"), - // // P2 Gold - // new Move("F9", "G8"), - // // P1 Pawn - // new Move("E3", "E4"), - // // P2 other Gold - // new Move("D9", "C8"), - // // P1 same Pawn - // new Move("E4", "E5"), - // // P2 Pawn - // new Move("E7", "E6"), - // // P1 Pawn takes P2 Pawn - // new Move("E5", "E6"), - // // P2 King - // new Move("E9", "E8"), - // // P1 Pawn promotes, threatens P2 King - // new Move("E6", "E7", true), - // // P2 King retreat - // new Move("E8", "E9"), + // // P1 Rook + // new Move("H2", "E2"), + // // P2 Gold + // new Move("F9", "G8"), + // // P1 Pawn + // new Move("E3", "E4"), + // // P2 other Gold + // new Move("D9", "C8"), + // // P1 same Pawn + // new Move("E4", "E5"), + // // P2 Pawn + // new Move("E7", "E6"), + // // P1 Pawn takes P2 Pawn + // new Move("E5", "E6"), + // // P2 King + // new Move("E9", "E8"), + // // P1 Pawn promotes, threatens P2 King + // new Move("E6", "E7", true), + // // P2 King retreat + // new Move("E8", "E9"), // }; // var shogi = new Shogi(moves); // output.WriteLine(shogi.PrintStateAsAscii()); diff --git a/Shogi.Domain.UnitTests/StandardRulesShould.cs b/Shogi.Domain.UnitTests/StandardRulesShould.cs new file mode 100644 index 0000000..7f132f3 --- /dev/null +++ b/Shogi.Domain.UnitTests/StandardRulesShould.cs @@ -0,0 +1,31 @@ +using FluentAssertions; +using FluentAssertions.Execution; +using Xunit; + +namespace Shogi.Domain.UnitTests +{ + public class StandardRulesShould + { + [Fact] + public void AllowValidMoves_AfterCheck() + { + // Arrange + var board = new ShogiBoardState(); + var rules = new StandardRules(board); + rules.Move("C3", "C4"); // P1 Pawn + rules.Move("G7", "G6"); // P2 Pawn + rules.Move("B2", "G7"); // P1 Bishop puts P2 in check + board.InCheck.Should().Be(WhichPlayer.Player2); + + // Act - P2 is able to un-check theirself. + /// P2 King moves out of check + var moveSuccess = rules.Move("E9", "E8"); + + // Assert + using var _ = new AssertionScope(); + moveSuccess.Success.Should().BeTrue(); + moveSuccess.Reason.Should().BeEmpty(); + board.InCheck.Should().BeNull(); + } + } +} diff --git a/Shogi.Domain/Shogi.cs b/Shogi.Domain/Shogi.cs index cb0f990..1aca58a 100644 --- a/Shogi.Domain/Shogi.cs +++ b/Shogi.Domain/Shogi.cs @@ -31,25 +31,21 @@ public MoveResult CanMove(string from, string to, bool isPromotion) { - // TODO: ShogiBoardState.FromBoardNotation should not throw an execption in this query method. - var fromVector = ShogiBoardState.FromBoardNotation(from); - var toVector = ShogiBoardState.FromBoardNotation(to); var simulator = new StandardRules(new ShogiBoardState(board)); - return simulator.Move(fromVector, toVector, isPromotion); + return simulator.Move(from, to, isPromotion); } public MoveResult CanMove(WhichPiece pieceInHand, string to) { - var toVector = ShogiBoardState.FromBoardNotation(to); var simulator = new StandardRules(new ShogiBoardState(board)); - return simulator.Move(pieceInHand, toVector); + return simulator.Move(pieceInHand, to); } public void Move(string from, string to, bool isPromotion) { var fromVector = ShogiBoardState.FromBoardNotation(from); var toVector = ShogiBoardState.FromBoardNotation(to); - var moveResult = rules.Move(fromVector, toVector, isPromotion); + var moveResult = rules.Move(from, to, isPromotion); if (!moveResult.Success) { throw new InvalidOperationException(moveResult.Reason); diff --git a/Shogi.Domain/StandardRules.cs b/Shogi.Domain/StandardRules.cs index aa9816c..8b89f5d 100644 --- a/Shogi.Domain/StandardRules.cs +++ b/Shogi.Domain/StandardRules.cs @@ -1,5 +1,7 @@ using System.Numerics; +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Shogi.Domain.UnitTests")] namespace Shogi.Domain { internal class StandardRules @@ -39,11 +41,13 @@ namespace Shogi.Domain /// /// Move a piece from a board tile to another board tile. /// - /// The position of the piece being moved expressed in board notation. - /// The target position expressed in board notation. + /// The position of the piece being moved expressed in board notation. + /// The target position expressed in board notation. /// A describing the success or failure of the simulation. - public MoveResult Move(Vector2 from, Vector2 to, bool isPromotion = false) + public MoveResult Move(string fromNotation, string toNotation, bool isPromotion = false) { + var from = ShogiBoardState.FromBoardNotation(fromNotation); + var to = ShogiBoardState.FromBoardNotation(toNotation); var fromPiece = board[from]; if (fromPiece == null) { @@ -55,7 +59,7 @@ namespace Shogi.Domain return new MoveResult(false, "Not allowed to move the opponents piece"); } - if (IsPathable(from, to) == false) + if (ShogiIsPathable(from, to) == false) { return new MoveResult(false, $"Proposed move is not part of the move-set for piece {fromPiece.WhichPiece}."); } @@ -106,8 +110,9 @@ namespace Shogi.Domain /// /// The target position expressed in board notation. /// A describing the success or failure of the simulation. - public MoveResult Move(WhichPiece pieceInHand, Vector2 to) + public MoveResult Move(WhichPiece pieceInHand, string toNotation) { + var to = ShogiBoardState.FromBoardNotation(toNotation); var index = board.Hand.FindIndex(p => p.WhichPiece == pieceInHand); if (index == -1) { @@ -150,7 +155,7 @@ namespace Shogi.Domain return new MoveResult(true); } - private bool IsPathable(Vector2 from, Vector2 to) + private bool ShogiIsPathable(Vector2 from, Vector2 to) { var piece = board[from]; if (piece == null) return false; @@ -255,7 +260,9 @@ namespace Shogi.Domain PathEvery(from, (other, position) => { var simulationBoard = new StandardRules(new ShogiBoardState(board)); - var simulationResult = simulationBoard.Move(from, position, false); + var fromNotation = ShogiBoardState.ToBoardNotation(from); + var toNotation = ShogiBoardState.ToBoardNotation(position); + var simulationResult = simulationBoard.Move(fromNotation, toNotation, false); if (simulationResult.Success) { if (!EvaluateCheckAfterMove(from, position, board.InCheck.Value)) @@ -288,7 +295,7 @@ namespace Shogi.Domain if (piece == null) return false; var path = FindDirectionTowardsDestination(GetMoveSet(piece.WhichPiece).GetMoves(piece.IsUpsideDown), origin, destination); - if (!IsPathable(origin, destination)) + if (!IsPathable(origin, destination, path.Direction)) { // Assumption: if a single best-choice step towards the destination cannot happen, no pathing can happen. return false; @@ -341,6 +348,25 @@ namespace Shogi.Domain } } + public static bool IsPathable(Vector2 origin, Vector2 destination, Vector2 direction) + { + var next = Vector2.Add(origin, direction); + if (Vector2.Distance(next, destination) >= Vector2.Distance(origin, destination)) return false; + + var slope = (destination.Y - origin.Y) / (destination.X - origin.X); + if (float.IsInfinity(slope)) + { + return next.X == destination.X; + } + else + { + // b = -mx + y + var yIntercept = -slope * origin.X + origin.Y; + // y = mx + b + return next.Y == slope * next.X + yIntercept; + } + } + /// /// Path the line from origin to destination, ignoring any Paths defined by the element at origin. ///