checkpoint
This commit is contained in:
390
Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs
Normal file
390
Shogi.Domain/ValueObjects/Rules/ShogiBoard.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using Shogi.Domain.ValueObjects.Movement;
|
||||
using Shogi.Domain.YetToBeAssimilatedIntoDDD;
|
||||
namespace Shogi.Domain.ValueObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Facilitates Shogi board state transitions, cognisant of Shogi rules.
|
||||
/// The board is always from Player1's perspective.
|
||||
/// [0,0] is the lower-left position, [8,8] is the higher-right position
|
||||
/// </summary>
|
||||
public sealed class ShogiBoard(BoardState initialState)
|
||||
{
|
||||
private static readonly int[] zeroToEight = [0, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
private static readonly Vector2 BoardSize = new(9, 9);
|
||||
|
||||
public BoardState BoardState { get; } = initialState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The strategy involves simulating a move on a throw-away board state that can be used to
|
||||
/// validate legal vs illegal moves without having to worry about reverting board state.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public MoveResult Move(string from, string to, bool isPromotion)
|
||||
{
|
||||
// Validate the move
|
||||
var moveResult = IsMoveValid(Notation.FromBoardNotation(from), Notation.FromBoardNotation(to));
|
||||
if (!moveResult.IsSuccess)
|
||||
{
|
||||
return moveResult;
|
||||
}
|
||||
|
||||
// Move is valid, but is it legal?
|
||||
// Check for correct player's turn.
|
||||
if (BoardState.WhoseTurn != BoardState[from]!.Owner)
|
||||
{
|
||||
return new MoveResult(false, "Not allowed to move the opponent's pieces.");
|
||||
}
|
||||
|
||||
// Simulate the move on a throw-away state and look for "check" and "check-mate".
|
||||
var simState = new BoardState(BoardState);
|
||||
moveResult = simState.Move(from, to, isPromotion);
|
||||
if (!moveResult.IsSuccess)
|
||||
{
|
||||
return moveResult;
|
||||
}
|
||||
|
||||
// Look for threats against the kings.
|
||||
InCheckResult inCheckResult = IsEitherPlayerInCheck(simState);
|
||||
|
||||
var playerPutThemselfInCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
||||
? inCheckResult.HasFlag(InCheckResult.Player1InCheck)
|
||||
: inCheckResult.HasFlag(InCheckResult.Player2InCheck);
|
||||
|
||||
if (playerPutThemselfInCheck)
|
||||
{
|
||||
return new MoveResult(false, "This move puts the moving player in check, which is illegal.");
|
||||
}
|
||||
var playerPutOpponentInCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
||||
? inCheckResult.HasFlag(InCheckResult.Player2InCheck)
|
||||
: inCheckResult.HasFlag(InCheckResult.Player1InCheck);
|
||||
|
||||
// Move is legal; mutate the real state.
|
||||
if (playerPutOpponentInCheck)
|
||||
{
|
||||
BoardState.InCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
||||
? WhichPlayer.Player2
|
||||
: WhichPlayer.Player1;
|
||||
}
|
||||
else if (inCheckResult == InCheckResult.NobodyInCheck)
|
||||
{
|
||||
BoardState.InCheck = null;
|
||||
}
|
||||
BoardState.Move(from, to, isPromotion);
|
||||
|
||||
if (BoardState.InCheck.HasValue)
|
||||
{
|
||||
var gameOverResult = EvaluateGameOver();
|
||||
BoardState.IsCheckmate = gameOverResult switch
|
||||
{
|
||||
GameOverResult.GameIsNotOver => false,
|
||||
GameOverResult.Player1Wins => true,
|
||||
GameOverResult.Player2Wins => true,
|
||||
_ => throw new InvalidOperationException("Unexpected GameOverResult value.")
|
||||
};
|
||||
}
|
||||
|
||||
return new MoveResult(true);
|
||||
}
|
||||
|
||||
public MoveResult Move(WhichPiece pieceInHand, string to)
|
||||
{
|
||||
if (BoardState.IsCheckmate)
|
||||
{
|
||||
return new MoveResult(false, "The game is over. A winner has been decided.");
|
||||
}
|
||||
|
||||
var index = BoardState.ActivePlayerHand.FindIndex(p => p.WhichPiece == pieceInHand);
|
||||
if (index == -1)
|
||||
{
|
||||
return new MoveResult(false, $"{pieceInHand} does not exist in the hand.");
|
||||
}
|
||||
|
||||
if (BoardState[to] != null)
|
||||
{
|
||||
return new MoveResult(false, $"Tried to play a piece from the hand to an occupied position.");
|
||||
}
|
||||
|
||||
var toVector = Notation.FromBoardNotation(to);
|
||||
switch (pieceInHand)
|
||||
{
|
||||
case WhichPiece.Knight:
|
||||
{
|
||||
// Knight cannot be placed onto the farthest two ranks from the hand.
|
||||
if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y > 6
|
||||
|| BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y < 2)
|
||||
{
|
||||
return new MoveResult(false, "Illegal move. Knight has no valid moves after placement.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WhichPiece.Lance:
|
||||
case WhichPiece.Pawn:
|
||||
{
|
||||
// Lance and Pawn cannot be placed onto the farthest rank from the hand.
|
||||
if (BoardState.WhoseTurn == WhichPlayer.Player1 && toVector.Y == 8
|
||||
|| BoardState.WhoseTurn == WhichPlayer.Player2 && toVector.Y == 0)
|
||||
{
|
||||
return new MoveResult(false, $"Illegal move. {pieceInHand} has no valid moves after placement.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pieceInHand == WhichPiece.Pawn)
|
||||
{
|
||||
// Pawns cannot be placed into a column with another unpromoted pawn controlled by the moving player.
|
||||
var columnAlreadyHasPawn = zeroToEight
|
||||
.Select(y => BoardState[new Vector2(toVector.X, y)])
|
||||
.Where(piece => piece?.WhichPiece == WhichPiece.Pawn)
|
||||
.Where(piece => piece?.Owner == BoardState.WhoseTurn)
|
||||
.Where(piece => piece?.IsPromoted == false)
|
||||
.Any();
|
||||
|
||||
if (columnAlreadyHasPawn)
|
||||
{
|
||||
return new MoveResult(false, "A player may not have two unpromoted pawns in the same file.");
|
||||
}
|
||||
}
|
||||
|
||||
var simState = new BoardState(BoardState);
|
||||
var moveResult = simState.Move(pieceInHand, to);
|
||||
if (!moveResult.IsSuccess)
|
||||
{
|
||||
return moveResult;
|
||||
}
|
||||
|
||||
var inCheckResult = IsEitherPlayerInCheck(simState);
|
||||
var playerPutThemselfInCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
||||
? inCheckResult.HasFlag(InCheckResult.Player1InCheck)
|
||||
: inCheckResult.HasFlag(InCheckResult.Player2InCheck);
|
||||
|
||||
if (playerPutThemselfInCheck)
|
||||
{
|
||||
return new MoveResult(false, "This move puts the moving player in check, which is illegal.");
|
||||
}
|
||||
var playerPutOpponentInCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
||||
? inCheckResult.HasFlag(InCheckResult.Player2InCheck)
|
||||
: inCheckResult.HasFlag(InCheckResult.Player1InCheck);
|
||||
|
||||
// Move is legal; mutate the real state.
|
||||
if (playerPutOpponentInCheck)
|
||||
{
|
||||
BoardState.InCheck = BoardState.WhoseTurn == WhichPlayer.Player1
|
||||
? WhichPlayer.Player2
|
||||
: WhichPlayer.Player1;
|
||||
}
|
||||
else if (inCheckResult == InCheckResult.NobodyInCheck)
|
||||
{
|
||||
BoardState.InCheck = null;
|
||||
}
|
||||
|
||||
BoardState.Move(pieceInHand, to);
|
||||
|
||||
// A pawn, placed from the hand, cannot be the cause of checkmate.
|
||||
if (BoardState.InCheck.HasValue && pieceInHand != WhichPiece.Pawn)
|
||||
{
|
||||
var gameOverResult = EvaluateGameOver();
|
||||
BoardState.IsCheckmate = gameOverResult switch
|
||||
{
|
||||
GameOverResult.GameIsNotOver => false,
|
||||
GameOverResult.Player1Wins => true,
|
||||
GameOverResult.Player2Wins => true,
|
||||
_ => throw new InvalidOperationException("Unexpected GameOverResult value.")
|
||||
};
|
||||
}
|
||||
|
||||
return new MoveResult(true);
|
||||
}
|
||||
|
||||
private GameOverResult EvaluateGameOver()
|
||||
{
|
||||
if (!BoardState.InCheck.HasValue)
|
||||
{
|
||||
return GameOverResult.GameIsNotOver;
|
||||
}
|
||||
var kingInCheck = BoardState.State
|
||||
.Single(kvp => kvp.Value?.WhichPiece == WhichPiece.King && kvp.Value?.Owner == BoardState.InCheck);
|
||||
var kingInCheckVectorPosition = Notation.FromBoardNotation(kingInCheck.Key);
|
||||
|
||||
var piecesOfPlayerInCheck = BoardState.State
|
||||
.Where(kvp => kvp.Value?.Owner == BoardState.InCheck)
|
||||
.Cast<KeyValuePair<string, Piece>>();
|
||||
|
||||
foreach (var (notation, piece) in piecesOfPlayerInCheck)
|
||||
{
|
||||
// Get possible locations this piece could move to.
|
||||
var allPossibleMoves = GetPossiblePositionsForPiece(Notation.FromBoardNotation(notation), piece);
|
||||
// Try to make a legal move, disproving checkmate.
|
||||
foreach (var move in allPossibleMoves)
|
||||
{
|
||||
var simState = new BoardState(BoardState);
|
||||
simState.Move(notation, Notation.ToBoardNotation(move), false);
|
||||
var inCheckResult = IsEitherPlayerInCheck(simState);
|
||||
var isStillInCheck = BoardState.InCheck == WhichPlayer.Player1
|
||||
? inCheckResult.HasFlag(InCheckResult.Player1InCheck)
|
||||
: inCheckResult.HasFlag(InCheckResult.Player2InCheck);
|
||||
|
||||
if (!isStillInCheck)
|
||||
{
|
||||
return GameOverResult.GameIsNotOver;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BoardState.InCheck == WhichPlayer.Player1
|
||||
? GameOverResult.Player2Wins
|
||||
: GameOverResult.Player1Wins;
|
||||
|
||||
Vector2[] GetPossiblePositionsForPiece(Vector2 piecePosition, Piece piece)
|
||||
{
|
||||
var paths = piece.MoveSet;
|
||||
return
|
||||
paths
|
||||
.SelectMany(path =>
|
||||
{
|
||||
var list = new List<Vector2>(10);
|
||||
var position = path.Step + piecePosition;
|
||||
if (path.Distance == Distance.MultiStep)
|
||||
{
|
||||
|
||||
while (position.IsInsideBoardBoundary())
|
||||
{
|
||||
list.Add(position);
|
||||
position += path.Step;
|
||||
}
|
||||
}
|
||||
else if (position.IsInsideBoardBoundary())
|
||||
{
|
||||
list.Add(position);
|
||||
}
|
||||
|
||||
return list;
|
||||
})
|
||||
// Where tile at position is empty, meaning the piece could move there.
|
||||
.Where(newPosition => BoardState[newPosition] == null)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static InCheckResult IsEitherPlayerInCheck(BoardState simState)
|
||||
{
|
||||
var kings = simState.State
|
||||
.Where(kvp => kvp.Value?.WhichPiece == WhichPiece.King)
|
||||
.Cast<KeyValuePair<string, Piece>>()
|
||||
.ToArray();
|
||||
|
||||
if (kings.Length != 2) throw new InvalidOperationException("Unexpected scenario: board does not have two kings in play.");
|
||||
|
||||
return simState.State
|
||||
.Where(kvp => kvp.Value != null)
|
||||
.Cast<KeyValuePair<string, Piece>>()
|
||||
.Aggregate(InCheckResult.NobodyInCheck, (inCheckResult, kvp) =>
|
||||
{
|
||||
var newInCheckResult = inCheckResult;
|
||||
var threatPiece = kvp.Value;
|
||||
var opposingKingPosition = Notation.FromBoardNotation(kings.Single(king => king.Value.Owner != threatPiece.Owner).Key);
|
||||
var positionsThreatened = threatPiece.GetPathFromStartToEnd(Notation.FromBoardNotation(kvp.Key), opposingKingPosition);
|
||||
|
||||
foreach (var position in positionsThreatened)
|
||||
{
|
||||
// No piece at this position, so pathing is unobstructed. Continue pathing.
|
||||
if (simState[position] == null) continue;
|
||||
|
||||
var threatenedPiece = simState[position]!;
|
||||
if (threatenedPiece.WhichPiece == WhichPiece.King && threatenedPiece.Owner != threatPiece.Owner)
|
||||
{
|
||||
newInCheckResult |= threatenedPiece.Owner == WhichPlayer.Player1 ? InCheckResult.Player1InCheck : InCheckResult.Player2InCheck;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newInCheckResult;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The purpose is to ensure a proposed board move is valid with regard to the moved piece's rules.
|
||||
/// This event does not worry about check or check-mate, or if a move is legal according to all Shogi rules.
|
||||
/// It asserts that a proposed move is possible and worthy of further validation (check, check-mate, etc).
|
||||
/// </summary>
|
||||
private MoveResult IsMoveValid(Vector2 from, Vector2 to)
|
||||
{
|
||||
if (IsWithinBounds(from) && IsWithinBounds(to))
|
||||
{
|
||||
if (BoardState[to]?.WhichPiece == WhichPiece.King)
|
||||
{
|
||||
return new MoveResult(false, "Kings may not be captured.");
|
||||
}
|
||||
|
||||
var piece = BoardState[from];
|
||||
if (piece == null)
|
||||
{
|
||||
return new MoveResult(false, $"There is no piece at position {from}.");
|
||||
}
|
||||
|
||||
var clampedFromTo = Vector2.Clamp(to - from, -Vector2.One, Vector2.One);
|
||||
var matchingPaths = piece.MoveSet.Where(p => p.Step == clampedFromTo);
|
||||
if (Vector2.Distance(to, from) < 2)
|
||||
{
|
||||
if (!matchingPaths.Any())
|
||||
{
|
||||
return new MoveResult(false, "Piece cannot move like that.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var multiStepPaths = matchingPaths
|
||||
.Where(path => path.Distance == Distance.MultiStep)
|
||||
.ToArray();
|
||||
if (multiStepPaths.Length == 0)
|
||||
{
|
||||
return new MoveResult(false, "Piece cannot move like that");
|
||||
}
|
||||
|
||||
foreach (var path in multiStepPaths)
|
||||
{
|
||||
// Assert that no pieces exist along the from -> to path.
|
||||
var isPathObstructed = GetPositionsAlongPath(from, to, path)
|
||||
.SkipLast(1)
|
||||
.Any(pos => BoardState[pos] != null);
|
||||
if (isPathObstructed)
|
||||
{
|
||||
return new MoveResult(false, "Piece cannot move through other pieces.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var pieceAtTo = BoardState[to];
|
||||
if (pieceAtTo?.Owner == piece.Owner)
|
||||
{
|
||||
return new MoveResult(false, "Cannot capture your own pieces.");
|
||||
}
|
||||
|
||||
}
|
||||
return new MoveResult(true);
|
||||
}
|
||||
|
||||
private static IEnumerable<Vector2> 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)
|
||||
{
|
||||
next += path.Step;
|
||||
yield return next;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWithinBounds(Vector2 position)
|
||||
{
|
||||
var isPositive = position - position == Vector2.Zero;
|
||||
return isPositive && position.X <= BoardSize.X && position.Y <= BoardSize.Y;
|
||||
}
|
||||
}
|
||||
166
Shogi.Domain/ValueObjects/Rules/StandardRules.cs
Normal file
166
Shogi.Domain/ValueObjects/Rules/StandardRules.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using Shogi.Domain.YetToBeAssimilatedIntoDDD;
|
||||
using BoardTile = System.Collections.Generic.KeyValuePair<System.Numerics.Vector2, Shogi.Domain.ValueObjects.Piece>;
|
||||
|
||||
namespace Shogi.Domain.ValueObjects
|
||||
{
|
||||
internal class StandardRules
|
||||
{
|
||||
private readonly BoardState boardState;
|
||||
|
||||
internal StandardRules(BoardState board)
|
||||
{
|
||||
boardState = board;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the last move put the player who moved in check.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This strategy recognizes that a "discover check" could only occur from a subset of pieces: Rook, Bishop, Lance.
|
||||
/// In this way, only those pieces need to be considered when evaluating if a move placed the moving player in check.
|
||||
/// </remarks>
|
||||
internal bool DidPlayerPutThemselfInCheck()
|
||||
{
|
||||
if (boardState.PreviousMove.From == null)
|
||||
{
|
||||
// You can't place yourself in check by placing a piece from your hand.
|
||||
return false;
|
||||
}
|
||||
|
||||
var previousMovedPiece = boardState[boardState.PreviousMove.To];
|
||||
if (previousMovedPiece == null) throw new ArgumentNullException(nameof(previousMovedPiece), $"No piece exists at position {boardState.PreviousMove.To}.");
|
||||
var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player1KingPosition : boardState.Player2KingPosition;
|
||||
|
||||
|
||||
var isDiscoverCheck = false;
|
||||
// Get line equation from king through the now-unoccupied location.
|
||||
var direction = Vector2.Subtract(kingPosition, boardState.PreviousMove.From.Value);
|
||||
var slope = Math.Abs(direction.Y / direction.X);
|
||||
var path = BoardState.GetPathAlongDirectionFromStartToEdgeOfBoard(boardState.PreviousMove.From.Value, Vector2.Normalize(direction));
|
||||
var threat = boardState.QueryFirstPieceInPath(path);
|
||||
if (threat == null || threat.Owner == previousMovedPiece.Owner) return false;
|
||||
// If absolute slope is 45°, look for a bishop along the line.
|
||||
// If absolute slope is 0° or 90°, look for a rook along the line.
|
||||
// if absolute slope is 0°, look for lance along the line.
|
||||
if (float.IsInfinity(slope))
|
||||
{
|
||||
isDiscoverCheck = threat.WhichPiece switch
|
||||
{
|
||||
WhichPiece.Lance => !threat.IsPromoted,
|
||||
WhichPiece.Rook => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
else if (slope == 1)
|
||||
{
|
||||
isDiscoverCheck = threat.WhichPiece switch
|
||||
{
|
||||
WhichPiece.Bishop => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
else if (slope == 0)
|
||||
{
|
||||
isDiscoverCheck = threat.WhichPiece switch
|
||||
{
|
||||
WhichPiece.Rook => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
return isDiscoverCheck;
|
||||
}
|
||||
|
||||
internal bool IsOpponentInCheckAfterMove() => IsOpposingKingThreatenedByPosition(boardState.PreviousMove.To);
|
||||
|
||||
internal bool IsOpposingKingThreatenedByPosition(Vector2 position)
|
||||
{
|
||||
var previousMovedPiece = boardState[position];
|
||||
if (previousMovedPiece == null) return false;
|
||||
|
||||
var kingPosition = previousMovedPiece.Owner == WhichPlayer.Player1 ? boardState.Player2KingPosition : boardState.Player1KingPosition;
|
||||
var path = previousMovedPiece.GetPathFromStartToEnd(position, kingPosition);
|
||||
var threatenedPiece = boardState.QueryFirstPieceInPath(path);
|
||||
if (!path.Any() || threatenedPiece == null) return false;
|
||||
|
||||
return threatenedPiece.WhichPiece == WhichPiece.King;
|
||||
}
|
||||
|
||||
internal bool IsOpponentInCheckMate()
|
||||
{
|
||||
// Assume checkmate, then try to disprove.
|
||||
if (!boardState.InCheck.HasValue) return false;
|
||||
// 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 = tilesOccupiedByOpponent.Where(tile => PieceHasLineOfSight(tile, kingPosition)).ToList();
|
||||
if (threats.Count == 1)
|
||||
{
|
||||
/* If there is exactly one threat it is possible to block the check.
|
||||
* Foreach piece owned by whichPlayer
|
||||
* if piece can intercept check, return false;
|
||||
*/
|
||||
var threat = threats.Single();
|
||||
var pathFromThreatToKing = threat.Value.GetPathFromStartToEnd(threat.Key, kingPosition);
|
||||
var tilesThatCouldBlockTheThreat = boardState.GetTilesOccupiedBy(boardState.WhoseTurn);
|
||||
foreach (var threatBlockingPosition in pathFromThreatToKing)
|
||||
{
|
||||
var tilesThatDoBlockThreat = tilesThatCouldBlockTheThreat
|
||||
.Where(tile => PieceHasLineOfSight(tile, threatBlockingPosition))
|
||||
.ToList();
|
||||
|
||||
if (tilesThatDoBlockThreat.Any())
|
||||
{
|
||||
return false; // Cannot be check-mate if a piece can intercept the threat.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* If no ability to block the check, maybe the king can evade check by moving.
|
||||
*/
|
||||
|
||||
foreach (var maybeSafePosition in GetPossiblePositionsForKing(boardState.WhoseTurn))
|
||||
{
|
||||
threats = tilesOccupiedByOpponent
|
||||
.Where(tile => PieceHasLineOfSight(tile, maybeSafePosition))
|
||||
.ToList();
|
||||
if (!threats.Any())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Vector2> GetPossiblePositionsForKing(WhichPlayer whichPlayer)
|
||||
{
|
||||
var kingPosition = whichPlayer == WhichPlayer.Player1
|
||||
? boardState.Player1KingPosition
|
||||
: boardState.Player2KingPosition;
|
||||
|
||||
var paths = boardState[kingPosition]!.MoveSet;
|
||||
return paths
|
||||
.Select(path => path.Step + kingPosition)
|
||||
// Because the king could be on the edge of the board, where some of its paths do not make sense.
|
||||
.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)
|
||||
{
|
||||
var path = tile.Value.GetPathFromStartToEnd(tile.Key, lineOfSightTarget);
|
||||
return path
|
||||
.SkipLast(1)
|
||||
.All(position => boardState[Notation.ToBoardNotation(position)] == null);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user