699 lines
22 KiB
C#
699 lines
22 KiB
C#
using JoshHeaps.Net.Models;
|
|
using JoshHeaps.Net.Services.Interfaces;
|
|
|
|
namespace JoshHeaps.Net.Services.Implementations;
|
|
|
|
public class ChessService : IChessService
|
|
{
|
|
public GameState CreateNewGame()
|
|
{
|
|
var gameState = new GameState();
|
|
|
|
InitializeBoard(gameState);
|
|
|
|
return gameState;
|
|
}
|
|
|
|
private void InitializeBoard(GameState gameState)
|
|
{
|
|
for (int r = 0; r < 8; r++)
|
|
for (int c = 0; c < 8; c++)
|
|
gameState.Board[r, c] = null;
|
|
|
|
gameState.Pieces.Clear();
|
|
gameState.MoveHistory.Clear();
|
|
|
|
gameState.WhiteCanCastleKingside = true;
|
|
gameState.WhiteCanCastleQueenside = true;
|
|
gameState.BlackCanCastleKingside = true;
|
|
gameState.BlackCanCastleQueenside = true;
|
|
gameState.EnPassantTarget = null;
|
|
|
|
SetupBlackPieces(gameState);
|
|
SetupWhitePieces(gameState);
|
|
|
|
gameState.CurrentPlayer = PieceColor.White;
|
|
|
|
UpdateCheckStatus(gameState);
|
|
}
|
|
|
|
private static void SetupBlackPieces(GameState gs)
|
|
{
|
|
var blackMajors = new[]
|
|
{
|
|
PieceType.Rook, PieceType.Knight, PieceType.Bishop,
|
|
PieceType.Queen, PieceType.King, PieceType.Bishop,
|
|
PieceType.Knight, PieceType.Rook
|
|
};
|
|
|
|
for (int c = 0; c < 8; c++)
|
|
{
|
|
var major = new ChessPiece($"b{blackMajors[c]}{c}", blackMajors[c], PieceColor.Black, new Position(0, c));
|
|
gs.Board[0, c] = major;
|
|
gs.Pieces.Add(major);
|
|
}
|
|
|
|
for (int c = 0; c < 8; c++)
|
|
{
|
|
var pawn = new ChessPiece($"bPawn{c}", PieceType.Pawn, PieceColor.Black, new Position(1, c));
|
|
gs.Board[1, c] = pawn;
|
|
gs.Pieces.Add(pawn);
|
|
}
|
|
}
|
|
|
|
private static void SetupWhitePieces(GameState gs)
|
|
{
|
|
var whiteMajors = new[]
|
|
{
|
|
PieceType.Rook, PieceType.Knight, PieceType.Bishop,
|
|
PieceType.Queen, PieceType.King, PieceType.Bishop,
|
|
PieceType.Knight, PieceType.Rook
|
|
};
|
|
|
|
for (int c = 0; c < 8; c++)
|
|
{
|
|
var major = new ChessPiece($"w{whiteMajors[c]}{c}", whiteMajors[c], PieceColor.White, new Position(7, c));
|
|
gs.Board[7, c] = major;
|
|
gs.Pieces.Add(major);
|
|
}
|
|
|
|
for (int c = 0; c < 8; c++)
|
|
{
|
|
var pawn = new ChessPiece($"wPawn{c}", PieceType.Pawn, PieceColor.White, new Position(6, c));
|
|
gs.Board[6, c] = pawn;
|
|
gs.Pieces.Add(pawn);
|
|
}
|
|
}
|
|
|
|
public List<(ChessPiece piece, List<Position> moves)> GetAllLegalMoves(GameState gameState)
|
|
{
|
|
var result = new List<(ChessPiece piece, List<Position> moves)>();
|
|
|
|
var currentPieces = gameState
|
|
.Pieces
|
|
.Where(p => p.Color == gameState.CurrentPlayer && p.Position.Row >= 0)
|
|
.ToList();
|
|
|
|
foreach (var piece in currentPieces)
|
|
{
|
|
var moves = GetLegalMovesForPiece(gameState, piece.Id);
|
|
|
|
if (moves.Count > 0)
|
|
result.Add((piece, moves));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public List<Position> GetLegalMovesForPiece(GameState gameState, string pieceId)
|
|
{
|
|
var piece = gameState.Pieces.FirstOrDefault(p => p.Id == pieceId);
|
|
|
|
if (piece == null) return [];
|
|
|
|
var candidateMoves = GenerateCandidateMoves(gameState, piece);
|
|
var legalMoves = new List<Position>();
|
|
|
|
foreach (var pos in candidateMoves)
|
|
if (IsMoveLegalConsideringCheck(gameState, piece, pos))
|
|
legalMoves.Add(pos);
|
|
|
|
return legalMoves;
|
|
}
|
|
|
|
public MoveResultDto MakeMove(GameState gameState, MoveDto moveDto)
|
|
{
|
|
var piece = gameState.Pieces.FirstOrDefault(p => p.Id == moveDto.PieceId);
|
|
|
|
if (piece == null)
|
|
return new MoveResultDto { Success = false, Message = "Piece not found." };
|
|
|
|
if (piece.Color != gameState.CurrentPlayer)
|
|
return new MoveResultDto { Success = false, Message = "Not your turn." };
|
|
|
|
var legalMoves = GetLegalMovesForPiece(gameState, piece.Id);
|
|
var targetPos = new Position(moveDto.TargetRow, moveDto.TargetCol);
|
|
|
|
if (!legalMoves.Any(m => m.Row == moveDto.TargetRow && m.Col == moveDto.TargetCol))
|
|
return new MoveResultDto { Success = false, Message = "Illegal move." };
|
|
|
|
PerformMove(gameState, piece, targetPos, moveDto);
|
|
|
|
UpdateCheckStatus(gameState);
|
|
|
|
var notation = $"{piece.Id}:{piece.Position}->{targetPos}";
|
|
gameState.MoveHistory.Add(notation);
|
|
|
|
return new MoveResultDto
|
|
{
|
|
Success = true,
|
|
Message = "Move successful.",
|
|
IsCheck = gameState.IsCheck,
|
|
IsCheckmate = gameState.IsCheckmate,
|
|
IsStalemate = gameState.IsStalemate
|
|
};
|
|
}
|
|
|
|
private static void PerformMove(GameState gs, ChessPiece piece, Position targetPos, MoveDto moveDto)
|
|
{
|
|
var oldPos = piece.Position;
|
|
var captured = gs.Board[targetPos.Row, targetPos.Col];
|
|
|
|
HandleEnPassantIfNeeded(gs, piece, targetPos, ref captured);
|
|
|
|
gs.Board[oldPos.Row, oldPos.Col] = null;
|
|
piece.Position = targetPos;
|
|
gs.Board[targetPos.Row, targetPos.Col] = piece;
|
|
|
|
if (captured != null && captured != piece)
|
|
captured.Position = new Position(-1, -1);
|
|
|
|
bool wasFirstMove = !piece.HasMoved;
|
|
piece.HasMoved = true;
|
|
|
|
HandleCastlingIfNeeded(gs, piece, oldPos, targetPos, wasFirstMove);
|
|
|
|
HandlePawnTwoSquareMove(gs, piece, oldPos, targetPos, wasFirstMove);
|
|
|
|
HandlePawnPromotionIfNeeded(piece, moveDto);
|
|
|
|
UpdateCastlingRights(gs, piece, oldPos);
|
|
|
|
gs.CurrentPlayer = gs.CurrentPlayer == PieceColor.White
|
|
? PieceColor.Black
|
|
: PieceColor.White;
|
|
}
|
|
|
|
private static void HandleEnPassantIfNeeded(GameState gs, ChessPiece piece, Position targetPos, ref ChessPiece? captured)
|
|
{
|
|
if (piece.Type != PieceType.Pawn || !gs.EnPassantTarget.HasValue)
|
|
return;
|
|
|
|
var enPassantSquare = gs.EnPassantTarget.Value;
|
|
|
|
if (targetPos.Row == enPassantSquare.Row && targetPos.Col == enPassantSquare.Col)
|
|
{
|
|
int direction = piece.Color == PieceColor.White ? 1 : -1;
|
|
var capturedPos = new Position(targetPos.Row + direction, targetPos.Col);
|
|
var potentialPawn = gs.Board[capturedPos.Row, capturedPos.Col];
|
|
|
|
if (potentialPawn != null && potentialPawn.Color != piece.Color && potentialPawn.Type == PieceType.Pawn)
|
|
{
|
|
gs.Board[capturedPos.Row, capturedPos.Col] = null;
|
|
potentialPawn.Position = new Position(-1, -1);
|
|
captured = potentialPawn;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void HandleCastlingIfNeeded(GameState gs, ChessPiece piece, Position oldPos, Position targetPos, bool wasFirstMove)
|
|
{
|
|
if (piece.Type != PieceType.King || !wasFirstMove)
|
|
return;
|
|
|
|
var colDiff = targetPos.Col - oldPos.Col;
|
|
|
|
if (Math.Abs(colDiff) == 2)
|
|
{
|
|
bool isKingside = colDiff > 0;
|
|
int rookStartCol = isKingside ? 7 : 0;
|
|
int rookEndCol = isKingside ? 5 : 3;
|
|
var rook = gs.Board[oldPos.Row, rookStartCol];
|
|
|
|
if (rook != null && rook.Type == PieceType.Rook && !rook.HasMoved)
|
|
{
|
|
gs.Board[oldPos.Row, rookStartCol] = null;
|
|
rook.Position = new Position(oldPos.Row, rookEndCol);
|
|
gs.Board[oldPos.Row, rookEndCol] = rook;
|
|
rook.HasMoved = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void HandlePawnTwoSquareMove(GameState gs, ChessPiece piece, Position oldPos, Position targetPos, bool wasFirstMove)
|
|
{
|
|
gs.EnPassantTarget = null;
|
|
|
|
if (piece.Type != PieceType.Pawn || !wasFirstMove)
|
|
return;
|
|
|
|
var rowDiff = Math.Abs(targetPos.Row - oldPos.Row);
|
|
|
|
if (rowDiff == 2)
|
|
{
|
|
var rowBehind = (oldPos.Row + targetPos.Row) / 2;
|
|
gs.EnPassantTarget = new Position(rowBehind, oldPos.Col);
|
|
}
|
|
}
|
|
|
|
private static void HandlePawnPromotionIfNeeded(ChessPiece piece, MoveDto moveDto)
|
|
{
|
|
if (piece.Type != PieceType.Pawn)
|
|
return;
|
|
|
|
bool promotionRow = piece.Color == PieceColor.White
|
|
? piece.Position.Row == 0
|
|
: piece.Position.Row == 7;
|
|
|
|
if (!promotionRow) return;
|
|
|
|
var promotionChoice = moveDto.PromotionChoice ?? PieceType.Queen;
|
|
piece.Type = promotionChoice;
|
|
}
|
|
|
|
private static void UpdateCastlingRights(GameState gs, ChessPiece movedPiece, Position oldPos)
|
|
{
|
|
if (movedPiece.Type == PieceType.King)
|
|
{
|
|
if (movedPiece.Color == PieceColor.White)
|
|
{
|
|
gs.WhiteCanCastleKingside = false;
|
|
gs.WhiteCanCastleQueenside = false;
|
|
}
|
|
else
|
|
{
|
|
gs.BlackCanCastleKingside = false;
|
|
gs.BlackCanCastleQueenside = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (movedPiece.Type == PieceType.Rook)
|
|
{
|
|
if (movedPiece.Color == PieceColor.White)
|
|
{
|
|
if (oldPos.Row == 7 && oldPos.Col == 0)
|
|
gs.WhiteCanCastleQueenside = false;
|
|
|
|
if (oldPos.Row == 7 && oldPos.Col == 7)
|
|
gs.WhiteCanCastleKingside = false;
|
|
}
|
|
else
|
|
{
|
|
if (oldPos.Row == 0 && oldPos.Col == 0)
|
|
gs.BlackCanCastleQueenside = false;
|
|
|
|
if (oldPos.Row == 0 && oldPos.Col == 7)
|
|
gs.BlackCanCastleKingside = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<Position> GenerateCandidateMoves(GameState gs, ChessPiece piece)
|
|
{
|
|
return piece.Type switch
|
|
{
|
|
PieceType.Pawn => GeneratePawnMoves(gs, piece),
|
|
PieceType.Rook => GenerateRookMoves(gs, piece),
|
|
PieceType.Knight => GenerateKnightMoves(gs, piece),
|
|
PieceType.Bishop => GenerateBishopMoves(gs, piece),
|
|
PieceType.Queen => GenerateQueenMoves(gs, piece),
|
|
PieceType.King => GenerateKingMoves(gs, piece),
|
|
_ => []
|
|
};
|
|
}
|
|
|
|
private bool IsMoveLegalConsideringCheck(GameState gs, ChessPiece piece, Position target)
|
|
{
|
|
var clone = CloneGameState(gs);
|
|
var clonedPiece = clone.Pieces.First(p => p.Id == piece.Id);
|
|
var oldPos = clonedPiece.Position;
|
|
|
|
clone.Board[oldPos.Row, oldPos.Col] = null;
|
|
|
|
var captured = clone.Board[target.Row, target.Col];
|
|
|
|
if (captured != null) captured.Position = new Position(-1, -1);
|
|
|
|
clonedPiece.Position = target;
|
|
clone.Board[target.Row, target.Col] = clonedPiece;
|
|
|
|
HandleEnPassantOnClone(clone, clonedPiece, target);
|
|
|
|
HandleCastlingOnClone(clone, clonedPiece, oldPos, target);
|
|
|
|
var myKing = clone
|
|
.Pieces
|
|
.FirstOrDefault(p => p.Color == piece.Color
|
|
&& p.Type == PieceType.King
|
|
&& p.Position.Row >= 0);
|
|
|
|
if (myKing == null) return false;
|
|
|
|
bool inCheck = IsSquareAttacked(clone, myKing.Position, myKing.Color);
|
|
|
|
return !inCheck;
|
|
}
|
|
|
|
private static void HandleEnPassantOnClone(GameState clone, ChessPiece clonedPiece, Position target)
|
|
{
|
|
if (clonedPiece.Type != PieceType.Pawn || !clone.EnPassantTarget.HasValue)
|
|
return;
|
|
|
|
if (target.Row == clone.EnPassantTarget.Value.Row && target.Col == clone.EnPassantTarget.Value.Col)
|
|
{
|
|
int direction = clonedPiece.Color == PieceColor.White ? 1 : -1;
|
|
var capturedPos = new Position(target.Row + direction, target.Col);
|
|
var epCaptured = clone.Board[capturedPos.Row, capturedPos.Col];
|
|
|
|
if (epCaptured != null && epCaptured.Color != clonedPiece.Color && epCaptured.Type == PieceType.Pawn)
|
|
{
|
|
clone.Board[capturedPos.Row, capturedPos.Col] = null;
|
|
epCaptured.Position = new Position(-1, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void HandleCastlingOnClone(GameState clone, ChessPiece clonedPiece, Position oldPos, Position target)
|
|
{
|
|
if (clonedPiece.Type != PieceType.King || clonedPiece.HasMoved)
|
|
return;
|
|
|
|
int colDiff = target.Col - oldPos.Col;
|
|
|
|
if (Math.Abs(colDiff) == 2)
|
|
{
|
|
bool isKingside = colDiff > 0;
|
|
int rookStartCol = isKingside ? 7 : 0;
|
|
int rookEndCol = isKingside ? 5 : 3;
|
|
|
|
var rook = clone.Board[oldPos.Row, rookStartCol];
|
|
|
|
if (rook != null && rook.Type == PieceType.Rook && !rook.HasMoved)
|
|
{
|
|
clone.Board[oldPos.Row, rookStartCol] = null;
|
|
rook.Position = new Position(oldPos.Row, rookEndCol);
|
|
clone.Board[oldPos.Row, rookEndCol] = rook;
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsSquareAttacked(GameState gs, Position square, PieceColor colorOfSquare)
|
|
{
|
|
var enemyColor = colorOfSquare == PieceColor.White ? PieceColor.Black : PieceColor.White;
|
|
|
|
var enemyPieces = gs
|
|
.Pieces
|
|
.Where(p => p.Color == enemyColor && p.Position.Row >= 0)
|
|
.ToList();
|
|
|
|
foreach (var enemy in enemyPieces)
|
|
{
|
|
var moves = GenerateCandidateMoves(gs, enemy);
|
|
|
|
if (moves.Any(m => m.Row == square.Row && m.Col == square.Col))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void UpdateCheckStatus(GameState gs)
|
|
{
|
|
gs.IsCheck = false;
|
|
gs.IsCheckmate = false;
|
|
gs.IsStalemate = false;
|
|
|
|
var king = gs
|
|
.Pieces
|
|
.FirstOrDefault(p => p.Color == gs.CurrentPlayer
|
|
&& p.Type == PieceType.King
|
|
&& p.Position.Row >= 0);
|
|
|
|
// If there's no king, that's effectively checkmate for that side.
|
|
if (king == null)
|
|
{
|
|
gs.IsCheck = true;
|
|
gs.IsCheckmate = true;
|
|
return;
|
|
}
|
|
|
|
bool inCheck = IsSquareAttacked(gs, king.Position, gs.CurrentPlayer);
|
|
|
|
gs.IsCheck = inCheck;
|
|
|
|
var allMoves = GetAllLegalMoves(gs);
|
|
|
|
if (allMoves.Count == 0 && inCheck)
|
|
{
|
|
gs.IsCheckmate = true;
|
|
|
|
return;
|
|
}
|
|
|
|
if (allMoves.Count == 0 && !inCheck)
|
|
{
|
|
gs.IsStalemate = true;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
private static GameState CloneGameState(GameState original)
|
|
{
|
|
var clone = new GameState
|
|
{
|
|
GameId = original.GameId,
|
|
CurrentPlayer = original.CurrentPlayer,
|
|
EnPassantTarget = original.EnPassantTarget,
|
|
WhiteCanCastleKingside = original.WhiteCanCastleKingside,
|
|
WhiteCanCastleQueenside = original.WhiteCanCastleQueenside,
|
|
BlackCanCastleKingside = original.BlackCanCastleKingside,
|
|
BlackCanCastleQueenside = original.BlackCanCastleQueenside,
|
|
MoveHistory = new List<string>(original.MoveHistory),
|
|
Board = new ChessPiece[8,8],
|
|
};
|
|
|
|
foreach (var p in original.Pieces)
|
|
{
|
|
var copy = new ChessPiece(p.Id, p.Type, p.Color, p.Position)
|
|
{
|
|
HasMoved = p.HasMoved
|
|
};
|
|
|
|
clone.Pieces.Add(copy);
|
|
}
|
|
|
|
foreach (var cp in clone.Pieces)
|
|
if (cp.Position.Row >= 0)
|
|
clone.Board[cp.Position.Row, cp.Position.Col] = cp;
|
|
|
|
return clone;
|
|
}
|
|
|
|
private static List<Position> GeneratePawnMoves(GameState gs, ChessPiece piece)
|
|
{
|
|
var moves = new List<Position>();
|
|
int direction = piece.Color == PieceColor.White ? -1 : 1;
|
|
var startRow = piece.Position.Row;
|
|
var startCol = piece.Position.Col;
|
|
|
|
var forward1 = startRow + direction;
|
|
|
|
if (IsOnBoard(forward1, startCol) && gs.Board[forward1, startCol] == null)
|
|
{
|
|
moves.Add(new Position(forward1, startCol));
|
|
|
|
if (!piece.HasMoved)
|
|
{
|
|
var forward2 = startRow + 2 * direction;
|
|
|
|
if (IsOnBoard(forward2, startCol) && gs.Board[forward2, startCol] == null)
|
|
moves.Add(new Position(forward2, startCol));
|
|
}
|
|
}
|
|
|
|
var leftCol = startCol - 1;
|
|
var rightCol = startCol + 1;
|
|
|
|
if (IsOnBoard(forward1, leftCol))
|
|
{
|
|
var occupant = gs.Board[forward1, leftCol];
|
|
|
|
if (occupant != null && occupant.Color != piece.Color)
|
|
moves.Add(new Position(forward1, leftCol));
|
|
}
|
|
|
|
if (IsOnBoard(forward1, rightCol))
|
|
{
|
|
var occupant = gs.Board[forward1, rightCol];
|
|
|
|
if (occupant != null && occupant.Color != piece.Color)
|
|
moves.Add(new Position(forward1, rightCol));
|
|
}
|
|
|
|
if (gs.EnPassantTarget.HasValue)
|
|
{
|
|
var ep = gs.EnPassantTarget.Value;
|
|
|
|
if (ep.Row == forward1 && Math.Abs(ep.Col - startCol) == 1)
|
|
{
|
|
// Verify there's an enemy pawn to capture
|
|
int enemyPawnRow = piece.Color == PieceColor.White ? ep.Row + 1 : ep.Row - 1;
|
|
var enemyPawn = gs.Board[enemyPawnRow, ep.Col];
|
|
|
|
if (enemyPawn != null && enemyPawn.Type == PieceType.Pawn && enemyPawn.Color != piece.Color)
|
|
moves.Add(ep);
|
|
}
|
|
}
|
|
|
|
return moves;
|
|
}
|
|
|
|
private static List<Position> GenerateRookMoves(GameState gs, ChessPiece piece)
|
|
=> GenerateSlidingMoves(gs, piece, [(1, 0), (-1, 0), (0, 1), (0, -1)]);
|
|
|
|
private static List<Position> GenerateBishopMoves(GameState gs, ChessPiece piece)
|
|
=> GenerateSlidingMoves(gs, piece, [(1, 1), (1, -1), (-1, 1), (-1, -1)]);
|
|
|
|
private static List<Position> GenerateQueenMoves(GameState gs, ChessPiece piece)
|
|
=> [..GenerateRookMoves(gs, piece), ..GenerateBishopMoves(gs, piece)];
|
|
|
|
private static List<Position> GenerateSlidingMoves(GameState gs, ChessPiece piece, (int dr, int dc)[] directions)
|
|
{
|
|
var results = new List<Position>();
|
|
var (startRow, startCol) = piece.Position;
|
|
|
|
foreach (var (dr, dc) in directions)
|
|
results.AddRange(GetSlidingMovesInDirection(gs, piece, startRow, startCol, dr, dc));
|
|
|
|
return results;
|
|
}
|
|
|
|
private static List<Position> GetSlidingMovesInDirection(
|
|
GameState gs,
|
|
ChessPiece piece,
|
|
int row,
|
|
int col,
|
|
int dr,
|
|
int dc)
|
|
{
|
|
var moves = new List<Position>();
|
|
var r = row;
|
|
var c = col;
|
|
|
|
while (true)
|
|
{
|
|
r += dr;
|
|
c += dc;
|
|
|
|
if (!IsOnBoard(r, c))
|
|
return moves;
|
|
|
|
var occupant = gs.Board[r, c];
|
|
|
|
if (occupant == null)
|
|
{
|
|
moves.Add(new Position(r, c));
|
|
continue;
|
|
}
|
|
|
|
if (occupant.Color != piece.Color)
|
|
moves.Add(new Position(r, c));
|
|
|
|
return moves;
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<Position> GenerateKnightMoves(GameState gs, ChessPiece piece)
|
|
{
|
|
var offsets = new (int, int)[]
|
|
{
|
|
(2,1), (2,-1), (-2,1), (-2,-1),
|
|
(1,2), (1,-2), (-1,2), (-1,-2)
|
|
};
|
|
|
|
foreach (var (dr, dc) in offsets)
|
|
{
|
|
var r = piece.Position.Row + dr;
|
|
var c = piece.Position.Col + dc;
|
|
|
|
if (!IsOnBoard(r, c))
|
|
continue;
|
|
|
|
var occupant = gs.Board[r, c];
|
|
|
|
if (occupant == null || occupant.Color != piece.Color)
|
|
yield return new Position(r, c);
|
|
}
|
|
}
|
|
|
|
private List<Position> GenerateKingMoves(GameState gs, ChessPiece piece)
|
|
{
|
|
var results = new List<Position>();
|
|
var offsets = new[]
|
|
{
|
|
(1,0), (-1,0), (0,1), (0,-1),
|
|
(1,1), (1,-1), (-1,1), (-1,-1)
|
|
};
|
|
|
|
foreach (var (dr, dc) in offsets)
|
|
{
|
|
int r = piece.Position.Row + dr;
|
|
int c = piece.Position.Col + dc;
|
|
|
|
if (!IsOnBoard(r, c))
|
|
continue;
|
|
|
|
var occupant = gs.Board[r, c];
|
|
|
|
if (occupant == null || occupant.Color != piece.Color)
|
|
results.Add(new Position(r, c));
|
|
}
|
|
|
|
if (!piece.HasMoved && !gs.IsCheck && piece.Color == gs.CurrentPlayer)
|
|
AddCastlingMoves(gs, piece, results);
|
|
|
|
return results;
|
|
}
|
|
|
|
private void AddCastlingMoves(GameState gs, ChessPiece king, List<Position> results)
|
|
{
|
|
bool hasKingsideRight = king.Color == PieceColor.White
|
|
? gs.WhiteCanCastleKingside
|
|
: gs.BlackCanCastleKingside;
|
|
|
|
bool hasQueensideRight = king.Color == PieceColor.White
|
|
? gs.WhiteCanCastleQueenside
|
|
: gs.BlackCanCastleQueenside;
|
|
|
|
var row = king.Position.Row;
|
|
var col = king.Position.Col;
|
|
|
|
bool areKingsideCastleSpacesEmpty = IsEmpty(row, col + 1, gs)
|
|
&& IsEmpty(row, col + 2, gs);
|
|
|
|
bool isKingsideCastleSafe = !IsSquareAttacked(gs, new Position(row, col + 1), king.Color)
|
|
&& !IsSquareAttacked(gs, new Position(row, col + 2), king.Color);
|
|
|
|
bool areQueensideCastleSpacesEmpty = IsEmpty(row, col - 1, gs)
|
|
&& IsEmpty(row, col - 2, gs)
|
|
&& IsEmpty(row, col - 3, gs);
|
|
|
|
bool isQueensideCastleSafe = !IsSquareAttacked(gs, new Position(row, col - 1), king.Color)
|
|
&& !IsSquareAttacked(gs, new Position(row, col - 2), king.Color);
|
|
|
|
bool canCastleKingside = areKingsideCastleSpacesEmpty
|
|
&& isKingsideCastleSafe
|
|
&& hasKingsideRight;
|
|
|
|
bool canCastleQueenside = areQueensideCastleSpacesEmpty
|
|
&& isQueensideCastleSafe
|
|
&& hasQueensideRight;
|
|
|
|
if (canCastleKingside) results.Add(new Position(row, col + 2));
|
|
|
|
if (canCastleQueenside) results.Add(new Position(row, col - 2));
|
|
}
|
|
|
|
private static bool IsEmpty(int r, int c, GameState gs)
|
|
{
|
|
if (!IsOnBoard(r, c)) return false;
|
|
|
|
return gs.Board[r, c] == null;
|
|
}
|
|
|
|
private static bool IsOnBoard(int r, int c)
|
|
=> r >= 0 && r < 8 && c >= 0 && c < 8;
|
|
} |