Ejemplo n.º 1
0
        /// <summary>
        /// Calls the valid handler for game over depending on state
        /// </summary>
        private void GameOverHandler()
        {
            FenParser.DebugFENPosition(board.CurrentFEN);

            // Full verbose output
            string[]      lines = FenParser.FENToStrings(board.CurrentFEN);
            StringBuilder sb    = new StringBuilder();

            foreach (string line in lines)
            {
                sb.Append(line);
            }

            if (selfPlay)
            {
                if (OnChessGameSelfPlayGameOver != null)
                {
                    OnChessGameSelfPlayGameOver(this, null);
                }
            }
            else
            {
                if (OnChessGameNormalPlayGameOver != null)
                {
                    OnChessGameNormalPlayGameOver(this, null);
                }
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Sets up the board using the given FEN string.  It's possible not all
        /// pieces are present.
        /// </summary>
        /// <param name="fen">FEN string that describes the position
        /// (https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation)</param>
        private void CreateAndPlacePieces(string fen)
        {
            Reset();

            // String starts on the back rank on the A file then moves Left->Right
            // Top->Bottom
            string fenString = fen.Trim();

            activePlayer = FenParser.ExtractActivePlayer(fenString);
            FenParser.ExtractCastlingRights(fenString, ref whiteCastlingRights, ref blackCastlingRights);
            enPassantValid = FenParser.ExtractEnPassantTarget(fenString, out enPassantTarget);
            FenParser.ExtractMoveCounts(fenString, ref halfMoveCount, ref fullMoveCount);

            List <ChessPiece> pieces = FenParser.ExtractPieces(fenString);

            foreach (ChessPiece fenPiece in pieces)
            {
                // For pawns and rooks and kings, deployment matters, check if they're on their home
                // rank/square and if not, set it to true
                if (fenPiece.Job == PieceClass.Pawn)
                {
                    if (PawnHomeRank(fenPiece.Color) != fenPiece.Rank)
                    {
                        fenPiece.Deployed = true;
                    }
                }
                else if (fenPiece.Job == PieceClass.Rook)
                {
                    if (((fenPiece.File.ToInt() != 1) && (fenPiece.File.ToInt() != 8)) ||
                        (fenPiece.Rank != RookHomeRank(fenPiece.Color)))
                    {
                        fenPiece.Deployed = true;
                    }
                }
                else if (fenPiece.Job == PieceClass.King)
                {
                    // Both colors should be on the E file and their home rank
                    int homeRank = (fenPiece.Color == PieceColor.White) ? 1 : 8;
                    if ((fenPiece.Rank != homeRank) || (fenPiece.File != new PieceFile('e')))
                    {
                        fenPiece.Deployed = true;
                    }
                }

                if (fenPiece.Color == PieceColor.White)
                {
                    whitePieces.Add(fenPiece);
                }
                else
                {
                    blackPieces.Add(fenPiece);
                }
            }
        }
Ejemplo n.º 3
0
        /// <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);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Produces a new FEN given a FEN and a move string
        /// </summary>
        /// <param name="fen">Starting FEN</param>
        /// <param name="sanMove">move e.g. e2e4 or d7c8q</param>
        /// <returns>Updated FEN for new position</returns>
        public static string ApplyMoveToFEN(string fen, string sanMove)
        {
            // Extract the original pieces of the FEN
            string[] fenTokens = TokenizeFEN(fen);

            // Extract start and end squares and promotion info
            BoardSquare startSquare = new BoardSquare(
                new PieceFile(sanMove[0]), (Convert.ToInt16(sanMove[1]) - Convert.ToInt16('0')));
            BoardSquare endSquare = new BoardSquare(
                new PieceFile(sanMove[2]), (Convert.ToInt16(sanMove[3]) - Convert.ToInt16('0')));
            bool isPromotion = (sanMove.Length == 5);

            PieceColor activePlayer = ExtractActivePlayer(fen);

            // token[2] is castling rights
            BoardSide whiteCastlingRights = BoardSide.None;
            BoardSide blackCastlingRights = BoardSide.None;

            FenParser.ParseCastlingRights(fenTokens[2], ref whiteCastlingRights, ref blackCastlingRights);

            // Get the current move counts
            int halfMoves = 0;
            int fullMoves = 0;

            ExtractMoveCounts(fen, ref halfMoves, ref fullMoves);

            // Needed for move count updates
            bool isCapture            = false;
            bool isPawnMove           = false;
            bool isNewEnPassantNeeded = false;

            // Expand the start and target ranks (one or both if different)
            fen = ExpandRank(fen, startSquare.Rank);
            if (startSquare.Rank != endSquare.Rank)
            {
                fen = ExpandRank(fen, endSquare.Rank);
            }

            // Get the piece moving
            char       fenPiece   = PieceAtBoardPosition(fen, startSquare.File.ToInt(), startSquare.Rank);
            PieceColor pieceColor = char.IsUpper(fenPiece) ? PieceColor.White : PieceColor.Black;

            if (isPromotion)
            {
                isPawnMove = true;
                fenPiece   = (activePlayer == PieceColor.White) ?  char.ToUpper(sanMove[4]) : sanMove[4];// Update the piece type
            }

            // target piece if any
            char fenTargetPiece = PieceAtBoardPosition(fen, endSquare.File.ToInt(), endSquare.Rank);

            // Common lambda for castling updates - used on rook moves and captures
            UpdateCastlingRightsOnEqualRank UpdateCastlingRightsIfNeeded = (rankA, rankB, targetSquare, color) =>
            {
                if (rankA == rankB)
                {
                    // A File?
                    if (targetSquare.File.ToInt() == 1)
                    {
                        if (color == PieceColor.White)
                        {
                            whiteCastlingRights &= ~BoardSide.Queen;
                        }
                        else
                        {
                            blackCastlingRights &= ~BoardSide.Queen;
                        }
                    }
                    // H File?
                    else if (targetSquare.File.ToInt() == 8)
                    {
                        if (color == PieceColor.White)
                        {
                            whiteCastlingRights &= ~BoardSide.King;
                        }
                        else
                        {
                            blackCastlingRights &= ~BoardSide.King;
                        }
                    }
                }
            };

            // generic capture
            // en-passant caught under pawn moves
            if (fenTargetPiece != '1')
            {
                isCapture = true;

                // If captured piece is enemy ROOK, that will potentially remove
                // rights on the opponent's side (if at home) - you can't capture
                // the king, so no need to check there
                PieceColor opponentColor = ChessBoard.OppositeColor(activePlayer);
                UpdateCastlingRightsIfNeeded(ChessBoard.RookHomeRank(opponentColor), endSquare.Rank, endSquare, opponentColor);
            }

            // Check the type of piece moving
            if (Char.ToUpper(fenPiece) == 'P') // PAWN
            {
                isPawnMove = true;             // resets halfmoves

                // en-passant only matters in pawn moves
                if (String.Compare(fenTokens[3], "-") != 0)
                {
                    // There is an en-passant square
                    BoardSquare enPassantSquare = new BoardSquare(
                        new PieceFile(fenTokens[3][0]), (Convert.ToInt16(fenTokens[3][1]) - Convert.ToInt16('0')));

                    // If the en-passant target is the move target, this is a capture
                    if (enPassantSquare == endSquare)
                    {
                        // en-passant capture - must also expand the rank 'behind' the target and
                        // remove that pawn - mark as capture
                        int  captureRank = (pieceColor == PieceColor.White) ? endSquare.Rank - 1 : endSquare.Rank + 1;
                        char captured;
                        fen = FenParser.RemovePiece(fen, endSquare.File.ToInt(), captureRank, out captured);
                        if (captured == '1')
                        {
                            throw new InvalidOperationException();
                        }
                        isCapture = true;
                    }
                }
                else if (Math.Abs(endSquare.Rank - startSquare.Rank) == 2)
                {
                    // If there is an enemy pawn on either side of the endSquare,
                    // then we need to create an enpassant target
                    // rank is already expanded
                    char neighbor = '1';

                    if (endSquare.File.ToInt() > 1)
                    {
                        neighbor = PieceAtBoardPosition(fen, endSquare.File.ToInt() - 1, endSquare.Rank);
                        if (char.ToUpper(neighbor) == 'P' && (ColorFromFen(neighbor) != activePlayer))
                        {
                            isNewEnPassantNeeded = true;
                        }
                    }

                    if (endSquare.File.ToInt() < 8)
                    {
                        neighbor = PieceAtBoardPosition(fen, endSquare.File.ToInt() + 1, endSquare.Rank);
                        if (char.ToUpper(neighbor) == 'P' && (ColorFromFen(neighbor) != activePlayer))
                        {
                            isNewEnPassantNeeded = true;
                        }
                    }
                }
            }

            if (Char.ToUpper(fenPiece) == 'K') // KING
            {
                // Check if this is a castling move - only time king moves 2 squares
                if (Math.Abs(endSquare.File.ToInt() - startSquare.File.ToInt()) == 2)
                {
                    // Move the associated rook... already expanded the rank
                    int rookFileStart = endSquare.File.ToInt() == 7 ? 8 : 1;
                    int rookFileEnd   = rookFileStart == 1 ? 4 : 6;

                    char rook;
                    fen = FenParser.RemovePiece(fen, rookFileStart, endSquare.Rank, out rook);
                    fen = FenParser.InsertPiece(fen, rook, rookFileEnd, endSquare.Rank);
                }

                // Moving the king removes all rights on all sides
                if (activePlayer == PieceColor.White)
                {
                    whiteCastlingRights = BoardSide.None;
                }
                else
                {
                    blackCastlingRights = BoardSide.None;
                }
            }
            if (Char.ToUpper(fenPiece) == 'R') // ROOK
            {
                // Check if at home position at start
                int homeRank = (pieceColor == PieceColor.White) ? 1 : 8;
                UpdateCastlingRightsIfNeeded(homeRank, startSquare.Rank, startSquare, activePlayer);
            }

            // Remove piece
            char fenChar;

            fen = RemovePiece(fen, startSquare.File.ToInt(), startSquare.Rank, out fenChar);
            if ((fenPiece != fenChar) && !isPromotion)
            {
                throw new InvalidOperationException();
            }

            // Place piece (it might not be the same type (promotions) - TODO
            fen = InsertPiece(fen, fenPiece, endSquare.File.ToInt(), endSquare.Rank);

            // Collapse the rows we touched
            fen = CollapseRank(fen, startSquare.Rank);
            if (startSquare.Rank != endSquare.Rank)
            {
                fen = CollapseRank(fen, endSquare.Rank);
            }

            // Re-tokenize
            fenTokens = TokenizeFEN(fen);

            // castling
            string wcr = TokenizeCastlingRights(whiteCastlingRights, PieceColor.White);
            string bcr = TokenizeCastlingRights(blackCastlingRights, PieceColor.Black);

            if ((whiteCastlingRights == BoardSide.None) && (blackCastlingRights == BoardSide.None))
            {
                fenTokens[2] = "-";
            }
            else
            {
                fenTokens[2] = String.Empty;
                if (whiteCastlingRights != BoardSide.None)
                {
                    fenTokens[2] = wcr;
                }

                if (blackCastlingRights != BoardSide.None)
                {
                    fenTokens[2] = String.Concat(fenTokens[2], bcr);
                }
            }

            // Update the other pieces of the FEN
            // active player
            fenTokens[1] = (activePlayer == PieceColor.White) ? "b" : "w";

            // Did we create a new en-passant target?
            if (isNewEnPassantNeeded)
            {
                // Target is behind pawn
                int         enpassantTargetRank = (activePlayer == PieceColor.White) ? endSquare.Rank - 1 : endSquare.Rank + 1;
                BoardSquare ept = new BoardSquare(endSquare.File, enpassantTargetRank);
                fenTokens[3] = ept.ToString();
            }
            else
            {
                fenTokens[3] = "-";
            }

            // half moves
            halfMoves++;
            if (isCapture || isPawnMove)
            {
                halfMoves = 0;
            }
            fenTokens[4] = halfMoves.ToString();

            // full moves
            if (activePlayer == PieceColor.Black)
            {
                fullMoves++;
            }
            fenTokens[5] = fullMoves.ToString();

            fen = string.Join(" ", fenTokens);
            return(fen);
        }