/// <summary> /// Returns a list of valid squares a king can move to. The list can be empty /// </summary> /// <param name="piece">ChessPiece to examine</param> /// <param name="board">ChessBoard the piece exists within</param> /// <returns>List of valid squares, or empty list</returns> public static List <BoardSquare> GetLegalMoves_King(ChessPiece piece, ChessBoard board) { List <BoardSquare> moves = new List <BoardSquare>(); /* The King is special. He can move one square in any direction * (provided it is on the board) so long as the square is empty or * it has an opponent piece on it. However, the King can never * move into check, even if the square or capture would be legal * otherwise, so it requires some extra checking. * * Further complicating things, if the king is castling, he cannot * move THROUGH check either (basically check those squares as if * they were final destinations) * * +---+---+---+---+---+---+---+---+ * | | | | | | | | | * +---+---+---+---+---+---+---+---+ r = enemy rook (block) * | | | | | | p | | | T = Move or capture * +---+---+---+---+---+---+---+---+ p = enemy pawn (block) * | | | T | T | X | | b | | b = enemy bishop (block) * +---+---+---+---+---+---+---+---+ X = illegal move * | | | T | K | T | | | | * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ * | | | T | T | X | | | | | K | T | | | | | * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ * | | | | | | | | | | X | X | | | | r | * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ * | | | | | | | | | | | | | | | | * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ */ // Cannot castle if in check if (!IsSquareInCheck(board, piece.File, piece.Rank, piece.Color)) { /* Castling may only be done if the king has never moved, the rook involved has never moved, * the squares between the king and the rook involved are unoccupied, the king is not in check, * and the king does not cross over or end on a square in which it would be in check. * * The ChessBoard will keep track of the castling rights when various pieces move, but it * won't constantly update the legality of the move */ // Build a list of squares to check BoardSide castlingRights = (piece.Color == PieceColor.White) ? board.WhiteCastlingRights : board.BlackCastlingRights; BoardSide[] sidesToCheck = new BoardSide[2] { BoardSide.King, BoardSide.Queen }; foreach (BoardSide sideToCheck in sidesToCheck) { // Backrank depends on color int kingRank = (piece.Color == PieceColor.White) ? 1 : 8; BoardSquare[] squares = null; // First check if we still have the right, if not, no need to persue it if (castlingRights.HasFlag(sideToCheck)) { squares = new BoardSquare[2]; // The target Files depend on the side of the board we're checking // put the final target in [0] if (sideToCheck == BoardSide.King) { squares[0] = new BoardSquare(new PieceFile(7), kingRank); squares[1] = new BoardSquare(new PieceFile(6), kingRank); } else // Queenside { squares[0] = new BoardSquare(new PieceFile(3), kingRank); squares[1] = new BoardSquare(new PieceFile(4), kingRank); } } // There should be 2 and only 2 from above if we found potential targets if (squares != null) { // must be empty and not in check - empty is faster so verify it first if ((board.FindPieceAt(squares[0].File, squares[0].Rank) == null) && (board.FindPieceAt(squares[1].File, squares[1].Rank) == null)) { // Now make sure neither square is in check if (!IsSquareInCheck(board, squares[0].File, squares[0].Rank, piece.Color) && !IsSquareInCheck(board, squares[1].File, squares[1].Rank, piece.Color)) { // King can still castle to this side, add the move option moves.Add(squares[0]); } } } } } // Check each of the 8 squares around the king. If it's free or has // an enemy piece, then check if it's targetable by the opponent // (moving into check) If not, then add it to the list CheckPieceTargets checkKingTargets = (p, fileDelta, rankDelta, b, m) => { // Verify targets are reachable (not off edge) int startCol = p.File.ToInt(); int startRow = p.Rank; int endCol = startCol + (fileDelta); int endRow = startRow + (rankDelta); bool occupied; // ignored here if (SquareIsFreeOrContainsOpponent(endCol, endRow, b, p.Color, out occupied)) { m.Add(new BoardSquare(new PieceFile(endCol), endRow)); } }; // Check all 8 squares around the king checkKingTargets(piece, 0, 1, board, moves); checkKingTargets(piece, 0, -1, board, moves); checkKingTargets(piece, 1, 0, board, moves); checkKingTargets(piece, -1, 0, board, moves); checkKingTargets(piece, 1, 1, board, moves); checkKingTargets(piece, -1, -1, board, moves); checkKingTargets(piece, 1, -1, board, moves); checkKingTargets(piece, -1, 1, board, moves); // Check violations are handled by the common caller for regulatr moves return(moves); }
/// <summary> /// Returns a list of valid squares a pawn can move to. The list can be empty /// </summary> /// <param name="piece">ChessPiece to examine</param> /// <param name="board">ChessBoard the piece exists within</param> /// <returns>List of valid squares, or empty list</returns> public static List <BoardSquare> GetLegalMoves_Pawn(ChessPiece piece, ChessBoard board) { List <BoardSquare> moves = new List <BoardSquare>(); /* Pawns can move one space forward on any move provided the square is empty * and 2 squares if it's the first move the pawn has ever made. * A pawn may move diagonally if the square is occupied by an opponent piece (capture) * or if the space behind the diagonal is occuped by an opponent pawn that * just moved 2 spaces (en-passant) * +---+---+---+---+---+---+---+---+ * | | | | | | | | | * +---+---+---+---+---+---+---+---+ * | | | | | | | | | * +---+---+---+---+---+---+---+---+ * | | | | | | | | | C = Capture only * +---+---+---+---+---+---+---+---+ T = Move or capture * | | | | M | | | | | M = Move Only * +---+---+---+---+---+---+---+---+ * | | | C | M | C | | | | * +---+---+---+---+---+---+---+---+ * | | | | P | | | | | * +---+---+---+---+---+---+---+---+ * | | | | | | | | | * +---+---+---+---+---+---+---+---+ */ // One rank "forward" which depends on your color int rank = (piece.Color == PieceColor.White) ? piece.Rank + 1 : piece.Rank - 1; if (null == board.FindPieceAt(piece.File, rank)) { moves.Add(new BoardSquare(piece.File, rank)); // The 2nd move is only valid of the 1st one was (i.e. you can't move through // another piece on your first pawn move) if (piece.Deployed == false) { rank += (piece.Color == PieceColor.White) ? 1 : -1; if (null == board.FindPieceAt(piece.File, rank)) { moves.Add(new BoardSquare(piece.File, rank)); } } } // Get the en-passant target if it exists, most of the time it will not // it only exists the move after an enemy pawn has jumped 2 spaces on // its initial move. BoardSquare enPassantTarget; bool enPassantValid = board.GetEnPassantTarget(out enPassantTarget); // Targets will ALWAYS be 1 rank away (enPassant target is behind piece captured) rank = (piece.Color == PieceColor.White) ? piece.Rank + 1 : piece.Rank - 1; // Lambda helper CheckPieceTargets checkPawnTargets = (p, fileOffset, rankOffset, b, m) => { int newFileIndex = p.File.ToInt() + fileOffset; // Can't have diagonal targets on the back rank, or if we're up // against the edge we want to check towards if (!ChessPiece.IsOnBackRank(piece) && (newFileIndex > 0) && (newFileIndex <= 8)) { PieceFile tFile = new PieceFile(p.File.ToInt() + fileOffset); BoardSquare targetSquare = new BoardSquare(tFile, rank); ChessPiece tPiece = b.FindPieceAt(tFile, rank); // Either there is a piece of the opposing color on this square // or the sqaure is a valid enpassant target if (((tPiece != null) && (tPiece.Color != p.Color)) || ((targetSquare == enPassantTarget) && enPassantValid)) { m.Add(new BoardSquare(tFile, rank)); } } }; // There are 2 possible Files (L,R or Kingside, Queenside, etc) // Diagonal left (lower file) PieceFile.ToInt() is 0-based since the // drawing code used it first...so adjust by 1 here checkPawnTargets(piece, -1, 0, board, moves); checkPawnTargets(piece, 1, 0, board, moves); return(moves); }
/// <summary> /// Checks if a given piece can target a specified square. It is not required /// that the square be occupied by an enemy piece, just potentially reachable /// </summary> /// <param name="board">ChessBoard to check against</param> /// <param name="piece">ChessPiece to check</param> /// <param name="targetFile">file to target</param> /// <param name="targetRank">rank to target</param> /// <returns>true if the piece can target the given square</returns> public static bool CanPieceTargetSquare(ChessBoard board, ChessPiece piece, PieceFile targetFile, int targetRank) { bool result = false; if (piece.Captured == true) { return(false); } BoardSquare targetSquare = new BoardSquare(targetFile, targetRank); List <BoardSquare> moves = new List <BoardSquare>(); switch (piece.Job) { case PieceClass.Pawn: // Can't reuse the normal helper as it requires the space to be occupied // also w/o en-passant and double moves, this can be simpler int pawnTargetRank = (piece.Color == PieceColor.White) ? piece.Rank + 1 : piece.Rank - 1; if (targetRank == pawnTargetRank) { if (((piece.File.ToInt() - 1) == targetFile.ToInt()) || ((piece.File.ToInt() + 1) == targetFile.ToInt())) { result = true; } } break; case PieceClass.Knight: moves = GetLegalMoves_Knight(piece, board); break; case PieceClass.Bishop: moves = GetLegalMoves_Bishop(piece, board); break; case PieceClass.Rook: moves = GetLegalMoves_Rook(piece, board); break; case PieceClass.Queen: moves = GetLegalMoves_Queen(piece, board); break; case PieceClass.King: // don't recurse into the normal call, also alternate method to examine // These are pairs of offsets (-1, 0), (-1, 1),...etc so there are twice // as many of these as squares to check int[] offsets = new int[] { -1, 0, -1, 1, -1, -1, 1, 0, 1, 1, 1, -1, 0, 1, 0, -1 }; for (int index = 0; index < offsets.Length / 2; index++) { int fileOffset = offsets[index * 2]; int rankOffset = offsets[(index * 2) + 1]; // Test the validity of the square offset if (BoardSquare.IsValid(piece.File.ToInt() + fileOffset, piece.Rank + rankOffset)) { BoardSquare testSquare = new BoardSquare(new PieceFile(piece.File.ToInt() + fileOffset), piece.Rank + rankOffset); if (testSquare == targetSquare) { result = true; break; } } } break; default: throw new ArgumentOutOfRangeException(); } // King is special above if (piece.Job != PieceClass.King) { // Check moves for the target square foreach (BoardSquare square in moves) { if (square == targetSquare) { result = true; break; } } } return(result); }
/// <summary> /// Helper to return all legal moves for the given piece. /// </summary> /// <param name="piece">piece to check, assumed to be valid</param> /// <param name="board">board to check agains, also assumed valid</param> /// <returns>List of squares the piece can legally move to</returns> public static List <BoardSquare> GetLegalMoves(ChessPiece piece, ChessBoard board) { List <BoardSquare> preCheckMoves = new List <BoardSquare>(); // Get a list of legal moves ignoring check violations switch (piece.Job) { case PieceClass.Pawn: preCheckMoves = GetLegalMoves_Pawn(piece, board); break; case PieceClass.Knight: preCheckMoves = GetLegalMoves_Knight(piece, board); break; case PieceClass.Bishop: preCheckMoves = GetLegalMoves_Bishop(piece, board); break; case PieceClass.Rook: preCheckMoves = GetLegalMoves_Rook(piece, board); break; case PieceClass.Queen: preCheckMoves = GetLegalMoves_Queen(piece, board); break; case PieceClass.King: preCheckMoves = GetLegalMoves_King(piece, board); break; default: throw new ArgumentOutOfRangeException(); } // Eliminate any move found that would place the King in check List <BoardSquare> legalMoves = new List <BoardSquare>(); ChessPiece playerKing = board.GetKing(piece.Color); foreach (BoardSquare square in preCheckMoves) { // Move the piece for the check - also remove any capture for the test ChessPiece tempCapture = board.FindPieceAt(square.File, square.Rank); piece.TempMove(square.File, square.Rank); if (tempCapture != null) { tempCapture.Captured = true; } if (!IsSquareInCheck(board, playerKing.File, playerKing.Rank, playerKing.Color)) { legalMoves.Add(square); } if (tempCapture != null) { tempCapture.Captured = false; } // reset the piece (this bypasses the ChessBoard class) piece.ResetTempMove(); } return(legalMoves); }