diff --git a/Gameboard.ShogiUI.BoardState/Direction.cs b/Gameboard.ShogiUI.BoardState/Direction.cs index 5f1307c..d474f79 100644 --- a/Gameboard.ShogiUI.BoardState/Direction.cs +++ b/Gameboard.ShogiUI.BoardState/Direction.cs @@ -8,8 +8,8 @@ namespace Gameboard.ShogiUI.BoardState public static readonly Vector2 Down = new Vector2(0, -1); public static readonly Vector2 Left = new Vector2(-1, 0); public static readonly Vector2 Right = new Vector2(1, 0); - public static readonly Vector2 UpLeft = new Vector2(1, 1); - public static readonly Vector2 UpRight = new Vector2(-1, 1); + public static readonly Vector2 UpLeft = new Vector2(-1, 1); + public static readonly Vector2 UpRight = new Vector2(1, 1); public static readonly Vector2 DownLeft = new Vector2(-1, -1); public static readonly Vector2 DownRight = new Vector2(1, -1); public static readonly Vector2 KnightLeft = new Vector2(-1, 2); diff --git a/Gameboard.ShogiUI.BoardState/Pieces/King.cs b/Gameboard.ShogiUI.BoardState/Pieces/King.cs index 76251e4..e2d4ebd 100644 --- a/Gameboard.ShogiUI.BoardState/Pieces/King.cs +++ b/Gameboard.ShogiUI.BoardState/Pieces/King.cs @@ -28,8 +28,7 @@ namespace Gameboard.ShogiUI.BoardState.Pieces if (IsPromoted) clone.Promote(); return clone; } - public override ICollection GetPaths() => Owner == WhichPlayer.Player1 - ? MoveSet - : MoveSet.Select(_ => new Path(Vector2.Negate(_.Direction), _.Distance)).ToList(); + // The move set for a King is the same regardless of orientation. + public override ICollection GetPaths() => MoveSet; } } diff --git a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs index 9c16fd8..43da70b 100644 --- a/Gameboard.ShogiUI.BoardState/ShogiBoard.cs +++ b/Gameboard.ShogiUI.BoardState/ShogiBoard.cs @@ -15,10 +15,10 @@ namespace Gameboard.ShogiUI.BoardState public class ShogiBoard { private delegate void MoveSetCallback(Piece piece, Vector2 position); - private ShogiBoard validationBoard; + private readonly PathFinder2D pathFinder; + public ShogiBoard validationBoard; private Vector2 player1King; private Vector2 player2King; - private PathFinder2D pathFinder; public IReadOnlyDictionary> Hands { get; } public Array2D Board { get; } public List MoveHistory { get; } @@ -44,15 +44,11 @@ namespace Gameboard.ShogiUI.BoardState { for (var i = 0; i < moves.Count; i++) { - if (!TryMove(moves[i])) + if (!Move(moves[i])) { throw new InvalidOperationException($"Unable to construct ShogiBoard with the given move at index {i}."); } } - if (EvaluateCheckAfterMove(WhoseTurn)) - { - InCheck = WhoseTurn; - } } private ShogiBoard(ShogiBoard toCopy) @@ -75,7 +71,7 @@ namespace Gameboard.ShogiUI.BoardState public bool Move(Move move) { - + var otherPlayer = WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; var moveSuccess = TryMove(move); if (!moveSuccess) @@ -84,13 +80,10 @@ namespace Gameboard.ShogiUI.BoardState } // Evaluate check - if (EvaluateCheckAfterMove(WhoseTurn)) + if (EvaluateCheckAfterMove(move, otherPlayer)) { - InCheck = WhoseTurn; - if (InCheck.HasValue) - { - //IsCheckmate = EvaluateCheckmate(); - } + InCheck = otherPlayer; + IsCheckmate = EvaluateCheckmate(); } return true; } @@ -114,11 +107,14 @@ namespace Gameboard.ShogiUI.BoardState validationBoard = null; return false; } - // Assert that this move does not put the moving player in check. - if (validationBoard.EvaluateCheckAfterMove(WhoseTurn)) + // If already in check, assert the move that resulted in check no longer results in check. + if (InCheck == WhoseTurn) { - // Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn; - return false; + if (validationBoard.EvaluateCheckAfterMove(MoveHistory[^1], WhoseTurn)) + { + // Sneakily using this.WhoseTurn instead of validationBoard.WhoseTurn; + return false; + } } // The move is valid and legal; update board state. @@ -251,13 +247,11 @@ namespace Gameboard.ShogiUI.BoardState Console.WriteLine(builder.ToString()); } #region Rules Validation - private bool EvaluateCheckAfterMove(WhichPlayer whichPlayer) + private bool EvaluateCheckAfterMove(Move move, WhichPlayer whichPlayer) { var isCheck = false; var kingPosition = whichPlayer == WhichPlayer.Player1 ? player1King : player2King; - // Get last move. - var move = MoveHistory[^1]; // Check if the move put the king in check. if (pathFinder.PathTo(move.To, kingPosition)) return true; @@ -327,10 +321,15 @@ namespace Gameboard.ShogiUI.BoardState pathFinder.PathEvery(from, (other, position) => { if (validationBoard == null) validationBoard = new ShogiBoard(this); - var moveSuccess = validationBoard.TryMove(new Move { From = from, To = position }); + var moveToTry = new Move { From = from, To = position }; + var moveSuccess = validationBoard.TryMove(moveToTry); if (moveSuccess) { - isCheckmate = false; + validationBoard = null; + if (!EvaluateCheckAfterMove(moveToTry, InCheck.Value)) + { + isCheckmate = false; + } } }); } diff --git a/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs b/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs index ae234f1..21eed69 100644 --- a/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs +++ b/Gameboard.ShogiUI.UnitTests/BoardState/ShogiBoardShould.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Gameboard.ShogiUI.BoardState; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Linq; using System.Numerics; @@ -304,15 +305,22 @@ namespace Gameboard.ShogiUI.UnitTests.BoardState new Move { From = new Vector2(4, 4), To = new Vector2(4, 5) }, // P2 King new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) }, - // P1 Pawn promotes + // P1 Pawn promotes, threatens P2 King new Move { From = new Vector2(4, 5), To = new Vector2(4, 6), IsPromotion = true }, // P2 King retreat new Move { From = new Vector2(4, 7), To = new Vector2(4, 8) }, }; var shogi = new ShogiBoard(moves); + Console.WriteLine("Prereq"); + shogi.PrintStateAsAscii(); // Act - P1 Pawn wins by checkmate. var moveSuccess = shogi.Move(new Move { From = new Vector2(4, 6), To = new Vector2(4, 7) }); + Console.WriteLine("Checkmate"); + shogi.PrintStateAsAscii(); + + shogi.Move(new Move { From = new Vector2(4, 8), To = new Vector2(4, 7) }); + shogi.PrintStateAsAscii(); // Assert moveSuccess.Should().BeTrue(); diff --git a/PathFinding/Path.cs b/PathFinding/Path.cs index dd3edaf..4b961bb 100644 --- a/PathFinding/Path.cs +++ b/PathFinding/Path.cs @@ -1,7 +1,9 @@ -using System.Numerics; +using System.Diagnostics; +using System.Numerics; namespace PathFinding { + [DebuggerDisplay("{Direction} - {Distance}")] public class Path { public Vector2 Direction { get; } diff --git a/PathFinding/PathFinder2D.cs b/PathFinding/PathFinder2D.cs index 5b5c1e3..539ae7e 100644 --- a/PathFinding/PathFinder2D.cs +++ b/PathFinding/PathFinder2D.cs @@ -72,19 +72,20 @@ namespace PathFinding foreach (var path in element.GetPaths()) { var shouldPath = true; - var next = Vector2.Add(from, path.Direction); + var next = Vector2.Add(from, path.Direction); ; while (shouldPath && next.X < width && next.Y < height && next.X >= 0 && next.Y >= 0) { var collider = collection[(int)next.X, (int)next.Y]; if (collider != null) { callback(collider, next); + shouldPath = false; } - next = Vector2.Add(from, path.Direction); if (path.Distance == Distance.OneStep) { shouldPath = false; } + next = Vector2.Add(next, path.Direction); } } }