/// <summary> /// Reverts last move applied to the board /// </summary> public void RevertLastMove() { MoveInformation lastMove = Moves.Last(); Moves.RemoveAt(Moves.Count() - 1); ChessPiece lastPieceMoved = FindPieceAt(lastMove.End.File, lastMove.End.Rank); lastPieceMoved.Move(lastMove.Start.File, lastMove.Start.Rank); if (lastMove.FirstMove) { lastPieceMoved.Deployed = false; // Reset first move } // Undo captures lastMoveWasCapture = false; if (lastMove.IsCapture) { lastMove.CapturedPiece.Captured = false; lastMoveWasCapture = true; } if (lastMove.IsPromotion) { lastPieceMoved.Demote(); } // Undo a castling move if (lastMove.IsCastle) { // Move the rook back as well ChessPiece rook = lastMove.CastlingRook; PieceFile castleTargetFile = lastMove.End.File; int rookRank = (rook.Color == PieceColor.White) ? 1 : 8; if (castleTargetFile.ToInt() == 7) // g->h { rook.Move(new PieceFile('h'), rookRank); } else if (castleTargetFile.ToInt() == 3) // c->a { rook.Move(new PieceFile('a'), rookRank); } else { // It has to be one of the above if logic is correct throw new IndexOutOfRangeException(); } rook.Deployed = false; } // Reset castling rights ActivePlayerCastlingRights = lastMove.CastlingRights; // Flip players activePlayer = OppositeColor(activePlayer); // Set last FEN currentFEN = lastMove.PreviousFEN; }
/// <summary> /// Invoked when the chess engine has responded with a move to apply to the /// local board /// </summary> private void OnEngineBestMoveResponse() { thinkingIndex = 0; // reset index counter for simple progress text // Get the best move from the engine string bestMove = engine.BestMove; if ((String.Compare(bestMove, "(none)") == 0) || // Stockfish (and converted ones) (String.Compare(bestMove, "a1a1") == 0) || // Rybka (board.HalfMoveCount >= HalfMovesUntilDraw)) // Propably spinning on self play or just a draw { if (board.HalfMoveCount >= HalfMovesUntilDraw) { Debug.WriteLine("Draw by 50 moves rule..."); } GameOverHandler(); } else if (GetInputState() == InputState.WaitingOnOpponentMove) { // Extract the board location from the move string PieceFile startFile = new PieceFile(bestMove[0]); int startRank = Convert.ToInt16(bestMove[1]) - Convert.ToInt16('0'); PieceFile destFile = new PieceFile(bestMove[2]); int destRank = Convert.ToInt16(bestMove[3]) - Convert.ToInt16('0'); ChessPiece foundPiece = board.FindPieceAt(startFile, startRank); MoveInformation moveInfo = new MoveInformation( new BoardSquare(startFile, startRank), new BoardSquare(destFile, destRank), foundPiece.Deployed, board.CurrentFEN); moveInfo.Color = foundPiece.Color; moveInfo.CastlingRights = board.ActivePlayerCastlingRights; // When coming from the engine, we get the promotion detection for free if (bestMove.Length == 5) { // Applied on the next move PieceClass promotionJob = ChessBoard.PieceClassFromFen(bestMove[4]); board.PromotePiece(startFile, startRank, destFile, destRank, promotionJob, ref moveInfo); } // Move the piece on the board, and add it to the official moves list board.MovePiece(ref moveInfo); // trigger a redraw view.Invalidate(); // Apply the move the engine just gave us with the engine (update it's own move) UpdateEnginePosition(); } }
/// <summary> /// Process the input when waiting for the current player to select a move /// </summary> /// <param name="x">x coordinate in form</param> /// <param name="y">y coordinate in form</param> private void OnWaitingForMoveSelection(int x, int y) { BoardSquare square = ((IChessBoardView)view).GetSquare(x, y); foreach (BoardSquare move in legalMoves) { if (move == square) { // Done - this is the move MoveInformation moveInfo = new MoveInformation( new BoardSquare(selectedPiece.File, selectedPiece.Rank), move, selectedPiece.Deployed, board.CurrentFEN); moveInfo.Color = selectedPiece.Color; moveInfo.CastlingRights = board.ActivePlayerCastlingRights; Debug.WriteLine("Valid Move Detected: [{0},{1}]=>[{2},{3}]", selectedPiece.File, selectedPiece.Rank, move.File, move.Rank); // Need to detect promotion and launch dialog for it... bool isPawnMovingToBackRank = (selectedPiece.Color == PieceColor.White) ? (moveInfo.End.Rank == 8) : (moveInfo.End.Rank == 1); if ((selectedPiece.Job == PieceClass.Pawn) && isPawnMovingToBackRank) { PieceClass promotionJob = view.ChoosePromotionJob(); board.PromotePiece(moveInfo.Start.File, moveInfo.Start.Rank, moveInfo.End.File, moveInfo.End.Rank, promotionJob, ref moveInfo); } // Always returns true now board.MovePiece(ref moveInfo); view.Invalidate(); Debug.WriteLine(String.Format("Fullmoves: {0}", board.FullMoveCount)); Debug.WriteLine(String.Format("Halfmoves: {0}", board.HalfMoveCount)); Debug.WriteLine(String.Format("WhCastle: {0}", board.WhiteCastlingRights.ToString())); Debug.WriteLine(String.Format("BlCastle: {0}", board.BlackCastlingRights.ToString())); // Update the position with the engine UpdateEnginePosition(); break; } } // Either way this gets cleared legalMoves.Clear(); ((IChessBoardView)view).ClearHiglightedSquares(); selectedPiece = null; }
/// <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> /// 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> /// 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); }