/// <summary> /// Returns a ChessPiece at a given file:rank. It's very possible there /// is no piece and if so returns null /// </summary> /// <param name="file">ChessFile to check against</param> /// <param name="rank">Rank to check against</param> /// <returns>ChessPiece object at the given file:rank or null if not found</returns> public ChessPiece FindPieceAt(PieceFile file, int rank) { ChessPiece result = null; // Check each piece in the list (32 max) List <ChessPiece> pieces = AllPieces; foreach (ChessPiece piece in pieces) { // Should only ever be 1 in the list at any given location visible if (IsPieceAtLocation(piece, file, rank)) { result = piece; // Again we can stop on the 1st hit break; } } return(result); }
/// <summary> /// Moves the rook for a castle move of a King. At this point the castling /// has (or should have) been detected. This is called to bring the rook /// along for the ride when the king is moved. See IsCastling for detection /// </summary> /// <param name="targetFile">ChessFile the King is moving to</param> /// <param name="moveInfo">Extended move information</param> private void PerformCastle(PieceFile targetFile, ref MoveInformation moveInfo) { // Find the corresponding Rook and move it too int rookRank = (activePlayer == PieceColor.White) ? 1 : 8; PieceFile gTargetFile = new PieceFile('g'); PieceFile cTargetFile = new PieceFile('c'); PieceFile rookStartFile; PieceFile rookTargetFile; // These are the only 2 legal files to move a king when castling // and they are the same for both players (only the rank differs above) if (targetFile == gTargetFile) { rookStartFile = new PieceFile('h'); rookTargetFile = new PieceFile('f'); } else if (targetFile == cTargetFile) { rookStartFile = new PieceFile('a'); rookTargetFile = new PieceFile('d'); } else { // If the above chess logic was sound, this should not happen. throw new ArgumentOutOfRangeException(); } // Get the rook (which should exist if logic is sound) ChessPiece castleRook = FindPieceAt(rookStartFile, rookRank); // Move it castleRook.Move(rookTargetFile, rookRank); // Save the castling info moveInfo.CastlingRook = castleRook; // Remove all castling rights for the active player if (activePlayer == PieceColor.White) { whiteCastlingRights = BoardSide.None; } else { blackCastlingRights = BoardSide.None; } }
/// <summary> /// Find the rect for a given location on the board /// </summary> /// <param name="file">File for the piece</param> /// <param name="rank">Rank for the piece</param> /// <returns>Rectangle for the board location in client coordinates</returns> private Rectangle GetRect(PieceFile file, int rank) { // This simple diagram helps demonstrate the inverted relationship // (not pictured is the overall board offset) // // Black White // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 8 | |X| |X| |X| |X| | |X| |X| |X| |X| 1 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 7 |X| |X| |X| |X| | |X| |X| |X| |X| | 2 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 6 | |X| |X| |X| |X| | |X| |X| |X| |X| 3 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 5 |X| |X| |X| |X| | |X| |X| |X| |X| | 4 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 4 | |X| |X| |X| |X| | |X| |X| |X| |X| 5 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 3 |X| |X| |X| |X| | |X| |X| |X| |X| | 6 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 2 | |X| |X| |X| |X| | |X| |X| |X| |X| 7 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // 1 |X| |X| |X| |X| | |X| |X| |X| |X| | 8 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ // a b c d e f g h h g f e d c b a // White Black int X; int Y; // PieceFile is 1-based, same as the physical board if (data.Orientation == BoardOrientation.WhiteOnBottom) { X = (file.ToInt() - 1) * squareSizeInPixels; Y = (8 - rank) * squareSizeInPixels; } else { X = (8 - file.ToInt()) * squareSizeInPixels; Y = (rank - 1) * squareSizeInPixels; } // Return the calculated rect offset from the overall topLeft location return(new Rectangle(X + topLeft.X, Y + topLeft.Y, squareSizeInPixels, squareSizeInPixels)); }
/// <summary> /// For pawn moves, this check if the move was "en-passant" (in-passing) and if /// so, it will remove the captured piece. It is assumed the move being /// checked is for a pawn /// </summary> /// <param name="startFile">ChessFile the piece originated from</param> /// <param name="startRank">Rank the piece originated from </param> /// <param name="targetFile">ChessFile the piece is moving to</param> /// <param name="targetRank">Rank the piece is moving to</param> /// <returns>captured piece or null</returns> private ChessPiece HandleEnPassant(PieceFile startFile, int startRank, PieceFile targetFile, int targetRank) { ChessPiece enPassantVictim = null; if ((startFile != targetFile) && // Diagonal move (!IsAnyPieceAtLocation(targetFile, targetRank))) // There is some piece behind us { int enPassantTargetRank = startRank; // Our potential target rank // This should never return a null object if the chess logic around it is sound // as a diagonal move (already detected) is not possible otherwise for a pawn // that did not make a capture (already detected) enPassantVictim = FindPieceAt(targetFile, enPassantTargetRank); // Capture the piece enPassantVictim.Captured = true; } return(enPassantVictim); }
/// <summary> /// When a pawn has made it to the back rank, it can be promoted. This method /// will mark a piece as needing promotion on the next move. We don't want to /// change the job until it has moved to keep inline with the rest of the logic /// </summary> /// <param name="startFile"></param> /// <param name="startRank"></param> /// <param name="targetFile"></param> /// <param name="targetRank"></param> /// <param name="promotionClass"></param> /// <param name="moveInfo">Detailed move info</param> public void PromotePiece(PieceFile startFile, int startRank, PieceFile targetFile, int targetRank, PieceClass promotionClass, ref MoveInformation moveInfo) { ChessPiece piece = FindPieceAt(startFile, startRank); int validRank = (piece.Color == PieceColor.White) ? 8 : 1; if (targetRank != validRank) { throw new ArgumentOutOfRangeException(); } // Find the pawn and mark it if (piece.Job != PieceClass.Pawn) { // Logic check throw new InvalidOperationException(); } moveInfo.PromotionJob = promotionClass; piece.PromoteOnNextMove(promotionClass); }
/// <summary> /// Returns true if any ChessPiece is located at the given file:rank /// </summary> /// <param name="file">ChessFile to check against</param> /// <param name="rank">Rank to check against</param> /// <returns>true if any piece is found</returns> private bool IsAnyPieceAtLocation(PieceFile file, int rank) { bool result = false; // Check each piece in the list (32 max) List <ChessPiece> pieces = AllPieces; foreach (ChessPiece piece in pieces) { // Only 1 piece in the list should ever return true for this // if the chess logic is sound (non-visible or captured pieces // will still be in the list though) so we can stop on the 1st hit if (IsPieceAtLocation(piece, file, rank)) { result = true; break; } } return(result); }
/// <summary> /// Highlight a single square /// </summary> /// <param name="file">[a-h] file</param> /// <param name="rank">[1-8] rank</param> void IChessBoardView.HighlightSquare(PieceFile file, int rank) { highlightedSquares.Add(new BoardSquare(file, rank)); }
/// <summary> /// Save the file, rank for the location /// </summary> /// <param name="file">[a-h]</param> /// <param name="rank">[1-8]</param> public BoardSquare(PieceFile file, int rank) { pieceFile = file; pieceRank = rank; }
/// <summary> /// Copy constructor /// </summary> /// <param name="oldFile"></param> public PieceFile(PieceFile oldFile) { pieceFile = oldFile.pieceFile; }
/// <summary> /// Returns true if the given ChessPiece is located at the given file:rank /// and that piece is not captured (must be visible) /// </summary> /// <param name="piece">ChessPiece object to check</param> /// <param name="file">ChessFile to check against</param> /// <param name="rank">Rank to check against</param> /// <returns>true if piece is found</returns> private bool IsPieceAtLocation(ChessPiece piece, PieceFile file, int rank) { return(!piece.Captured && (piece.Rank == rank) && (piece.File == file)); }
/// <summary> /// Move a piece from startFile:startRank -> targetFile:targetRank. Because /// self-play is the only mode enabled right now, these moves are always /// going to be considered valid, since they came from the chess engine /// (and we will assume it is correct). In the future, this will likely /// remain, and validation of the legallity for the player can be handled /// above this call /// </summary> /// <param name="moveInfo">detailed move information struct</param> public void MovePiece(ref MoveInformation moveInfo) { // Get the player piece at the starting location // Piece should never be null if chess logic is sound PieceFile startFile = moveInfo.Start.File; int startRank = moveInfo.Start.Rank; PieceFile targetFile = moveInfo.End.File; int targetRank = moveInfo.End.Rank; ChessPiece playerPiece = FindPieceAt(startFile, startRank); if (playerPiece.Color != activePlayer) { // This also should not be able to happen with correct game logic throw new InvalidOperationException(); } // Get each side's pieces List <ChessPiece> playerPieces = ActivePlayerPieces; List <ChessPiece> opponentPieces = OpponentPlayerPieces; // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // PRE-MOVE CHECKS // We have to detect castling. It does not come across the wire as O-O or O-O-O // but rather as a regular move like e1g1. Separate the detection from the move // of the rook bool isCastling = IsCastling(playerPiece, targetFile); // We also need to check for an en-passant capture if the pieces is a pawn if (playerPiece.Job == PieceClass.Pawn) { moveInfo.CapturedPiece = HandleEnPassant(startFile, startRank, targetFile, targetRank); } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // RAW MOVE(S) playerPiece.Move(targetFile, targetRank); if (isCastling) { PerformCastle(targetFile, ref moveInfo); // Also move the rook if needed } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // POST-MOVE CHECKS/UPDATES // For normal captures, just do a quick iteration of the opponent pieces // there are only 16 of these total in normal chess foreach (ChessPiece enemyPiece in opponentPieces) { if ((enemyPiece.Rank == targetRank) && // Enemy piece is located in (enemyPiece.File == targetFile) && // the square we just moved to !enemyPiece.Captured) // and it's not already captured { enemyPiece.Captured = true; // Stop drawing it (capture) moveInfo.CapturedPiece = enemyPiece; // Record the capture break; // exit the search loop } } // save the last capture state for external callers lastMoveWasCapture = moveInfo.IsCapture; Moves.Add(moveInfo); // Update our FEN currentFEN = FenParser.ApplyMoveToFEN(currentFEN, moveInfo.ToString()); enPassantValid = FenParser.ExtractEnPassantTarget(currentFEN, out enPassantTarget); FenParser.ExtractCastlingRights(CurrentFEN, ref whiteCastlingRights, ref blackCastlingRights); FenParser.ExtractMoveCounts(CurrentFEN, ref halfMoveCount, ref fullMoveCount); // Flip players - easy to just do here rather than parse the FEN again activePlayer = OppositeColor(activePlayer); }
/// <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> /// 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); }