/// <summary> /// Returns a list of valid squares a knight 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_Knight(ChessPiece piece, ChessBoard board) { List <BoardSquare> moves = new List <BoardSquare>(); /* Knights are the only piece that can jump over other pieces, and move * in an L-shape (2:1) or (1:2) with a maximum of 8 valid squares. * Because they can jump, the only requirement is the target square * be empty or contain an opposing piece (and lie within the boundaries * of the board) * * +---+---+---+---+---+---+---+---+ * | | | | | | | | | * +---+---+---+---+---+---+---+---+ * | | | T | | T | | | | T = Move or capture * +---+---+---+---+---+---+---+---+ * | | T | | | | T | | | * +---+---+---+---+---+---+---+---+ * | | | | N | | | | | Moves reduced on edges of board * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ * | | T | | | | T | | | | N | | | | | | * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ * | | | T | | T | | | | | | | T | | | | * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ * | | | | | | | | | | | T | | | | | * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ */ // Deltas should be 1 or -1 to indicate direction // Checks one quadrant CheckPieceTargets checkKnightTargets = (p, fileDelta, rankDelta, b, m) => { // Verify targets are reachable (not off edge) int startCol = p.File.ToInt(); int startRow = p.Rank; // #1 int endCol = startCol + (fileDelta * 1); int endRow = startRow + (rankDelta * 2); bool occupied; // ignored here if (SquareIsFreeOrContainsOpponent(endCol, endRow, b, p.Color, out occupied)) { m.Add(new BoardSquare(new PieceFile(endCol), endRow)); } // #2 endCol = startCol + (fileDelta * 2); endRow = startRow + (rankDelta * 1); if (SquareIsFreeOrContainsOpponent(endCol, endRow, b, p.Color, out occupied)) { m.Add(new BoardSquare(new PieceFile(endCol), endRow)); } }; // Check each quadrant checkKnightTargets(piece, 1, 1, board, moves); checkKnightTargets(piece, 1, -1, board, moves); checkKnightTargets(piece, -1, 1, board, moves); checkKnightTargets(piece, -1, -1, board, moves); return(moves); }
/// <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); }