diff --git a/Shogi.Domain.UnitTests/ShogiShould.cs b/Shogi.Domain.UnitTests/ShogiShould.cs index bde591c..c73d0ee 100644 --- a/Shogi.Domain.UnitTests/ShogiShould.cs +++ b/Shogi.Domain.UnitTests/ShogiShould.cs @@ -8,10 +8,10 @@ namespace Shogi.Domain.UnitTests { public class ShogiShould { - private readonly ITestOutputHelper logger; - public ShogiShould(ITestOutputHelper logger) + private readonly ITestOutputHelper console; + public ShogiShould(ITestOutputHelper console) { - this.logger = logger; + this.console = console; } [Fact] @@ -455,6 +455,7 @@ namespace Shogi.Domain.UnitTests shogi.Move("E7", "E8", false); // Assert - checkmate + console.WriteLine(shogi.ToStringStateAsAscii()); boardState.IsCheckmate.Should().BeTrue(); boardState.InCheck.Should().Be(WhichPlayer.Player2); } diff --git a/Shogi.Domain/DomainExtensions.cs b/Shogi.Domain/DomainExtensions.cs index 7471221..c530271 100644 --- a/Shogi.Domain/DomainExtensions.cs +++ b/Shogi.Domain/DomainExtensions.cs @@ -6,5 +6,14 @@ namespace Shogi.Domain { public static bool IsKing(this Piece self) => self.WhichPiece == WhichPiece.King; + public static bool IsBetween(this float self, float min, float max) + { + return self >= min && self <= max; + } + + public static bool IsInsideBoardBoundary(this Vector2 self) + { + return self.X.IsBetween(0, 8) && self.Y.IsBetween(0, 8); + } } } diff --git a/Shogi.Domain/Pathing/Path.cs b/Shogi.Domain/Pathing/Path.cs index 5c24275..9678721 100644 --- a/Shogi.Domain/Pathing/Path.cs +++ b/Shogi.Domain/Pathing/Path.cs @@ -29,6 +29,7 @@ namespace Shogi.Domain.Pathing { throw new ArgumentException("No paths to get nearest path from."); } + var shortestPath = paths.First(); foreach (var path in paths.Skip(1)) { diff --git a/Shogi.Domain/Pieces/GoldGeneral.cs b/Shogi.Domain/Pieces/GoldGeneral.cs index c6dc85e..1ec4308 100644 --- a/Shogi.Domain/Pieces/GoldGeneral.cs +++ b/Shogi.Domain/Pieces/GoldGeneral.cs @@ -26,6 +26,6 @@ namespace Shogi.Domain.Pieces { } - public override IEnumerable MoveSet => Player1Paths; + public override IEnumerable MoveSet => Owner == WhichPlayer.Player1 ? Player1Paths : Player2Paths; } } diff --git a/Shogi.Domain/Pieces/Piece.cs b/Shogi.Domain/Pieces/Piece.cs index f2e8076..7135edd 100644 --- a/Shogi.Domain/Pieces/Piece.cs +++ b/Shogi.Domain/Pieces/Piece.cs @@ -72,6 +72,8 @@ namespace Shogi.Domain.Pieces { position += path.Direction; steps.Add(position); + + if (path.Distance == Distance.OneStep) break; } if (position == end) diff --git a/Shogi.Domain/Shogi.cs b/Shogi.Domain/Shogi.cs index f4e6bd1..028bb3b 100644 --- a/Shogi.Domain/Shogi.cs +++ b/Shogi.Domain/Shogi.cs @@ -58,11 +58,10 @@ namespace Shogi.Domain if (rules.IsOpponentInCheckAfterMove()) { boardState.InCheck = otherPlayer; - // TODO: evaluate checkmate. - //if (rules.IsOpponentInCheckMate()) - //{ - // boardState.IsCheckmate = true; - //} + if (rules.IsOpponentInCheckMate()) + { + boardState.IsCheckmate = true; + } } else { diff --git a/Shogi.Domain/StandardRules.cs b/Shogi.Domain/StandardRules.cs index 51d9bf8..2699d16 100644 --- a/Shogi.Domain/StandardRules.cs +++ b/Shogi.Domain/StandardRules.cs @@ -1,4 +1,5 @@ using Shogi.Domain.Pathing; +using Shogi.Domain.Pieces; using BoardTile = System.Collections.Generic.KeyValuePair; namespace Shogi.Domain @@ -9,7 +10,7 @@ namespace Shogi.Domain internal StandardRules(ShogiBoardState board) { - this.boardState = board; + boardState = board; } /// @@ -179,19 +180,17 @@ namespace Shogi.Domain return threatenedPiece.WhichPiece == WhichPiece.King; } - internal bool IsPlayerInCheckMate(WhichPlayer whichPlayer) + internal bool IsOpponentInCheckMate() { + // Assume checkmate, then try to disprove. if (!boardState.InCheck.HasValue) return false; - - // Get all pieces from "other player" who threaten the king in question. - var otherPlayer = whichPlayer == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; - var tilesOccupiedByOtherPlayer = boardState.GetTilesOccupiedBy(otherPlayer); - var kingPosition = whichPlayer == WhichPlayer.Player1 + // Get all pieces from opponent who threaten the king in question. + var opponent = boardState.WhoseTurn == WhichPlayer.Player1 ? WhichPlayer.Player2 : WhichPlayer.Player1; + var tilesOccupiedByOpponent = boardState.GetTilesOccupiedBy(opponent); + var kingPosition = boardState.WhoseTurn == WhichPlayer.Player1 ? boardState.Player1KingPosition : boardState.Player2KingPosition; - var threats = tilesOccupiedByOtherPlayer.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList(); - - + var threats = tilesOccupiedByOpponent.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList(); if (threats.Count == 1) { /* If there is exactly one threat it is possible to block the check. @@ -200,7 +199,7 @@ namespace Shogi.Domain */ var threat = threats.Single(); var pathFromThreatToKing = threat.Value.GetPathFromStartToEnd(threat.Key, kingPosition); - var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(whichPlayer); + var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(boardState.WhoseTurn); foreach (var threatBlockingPosition in pathFromThreatToKing) { var tilesThatDoBlockThreat = tilesThatCouldBlockTheThreat @@ -218,25 +217,35 @@ namespace Shogi.Domain /* * If no ability to block the check, maybe the king can evade check by moving. */ - - // TODO: Implement this in the Piece class instead. - var possibleSafePositions = new[] + foreach (var maybeSafePosition in GetPossiblePositionsForKing(this.boardState.WhoseTurn)) { - Direction.Up, Direction.Down, Direction.Left, Direction.Right, Direction.UpLeft, Direction.UpRight, Direction.DownLeft, Direction.DownRight - } - .Select(direction => kingPosition + direction); - - foreach (var maybeSafePosition in possibleSafePositions) - { - + threats = tilesOccupiedByOpponent + .Where(tile => PieceHasLineOfSight(tile, maybeSafePosition)) + .ToList(); + if (!threats.Any()) + { + return false; + } } } + return true; + } + private IList GetPossiblePositionsForKing(WhichPlayer whichPlayer) + { + var kingPosition = whichPlayer == WhichPlayer.Player1 + ? boardState.Player1KingPosition + : boardState.Player2KingPosition; - return false; + return King.KingPaths + .Select(path => path.Direction + kingPosition) + .Where(newPosition => newPosition.IsInsideBoardBoundary()) + // Where tile at position is empty, meaning the king could move there. + .Where(newPosition => boardState[newPosition] == null) + .ToList(); } private bool PieceHasLineOfSight(BoardTile tile, Vector2 lineOfSightTarget) @@ -244,41 +253,7 @@ namespace Shogi.Domain var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget); return path .SkipLast(1) - .All(position => this.boardState[ShogiBoardState.ToBoardNotation(position)] == null); - } - - public bool EvaluateCheckmate_Old() - { - if (!boardState.InCheck.HasValue) return false; - - // Assume true and try to disprove. - var isCheckmate = true; - //boardState.ForEachNotNull((piece, from) => // For each piece... - //{ - // Short circuit - //if (!isCheckmate) return; - - //if (piece.Owner == boardState.InCheck) // ...owned by the player in check... - //{ - // ...evaluate if any move gets the player out of check. - //PathEvery(from, (other, position) => - //{ - // var simulationBoard = new StandardRules(new ShogiBoardState(board)); - // var fromNotation = ShogiBoardState.ToBoardNotation(from); - // var toNotation = ShogiBoardState.ToBoardNotation(position); - // var simulationResult = simulationBoard.Move(fromNotation, toNotation, false); - // if (simulationResult.Success) - // { - // //if (!IsPlayerInCheckAfterMove(from, position, board.InCheck.Value)) - // //{ - // // isCheckmate = false; - // //} - // } - //}); - //} - // TODO: Assert that a player could not place a piece from their hand to avoid check. - //}); - return isCheckmate; + .All(position => boardState[ShogiBoardState.ToBoardNotation(position)] == null); } } }