Beispiel #1
0
 private static bool IsMoveValid(ParsedMoveState move)
 {
     return(move.StartFile >= 'a' && move.StartFile <= 'h' &&
            move.EndFile >= 'a' && move.EndFile <= 'h' &&
            move.StartRank >= 1 && move.StartRank <= 8 &&
            move.EndRank >= 1 && move.EndRank <= 8);
 }
Beispiel #2
0
 private static ulong BuildDisambiguityMask(ParsedMoveState result)
 {
     if (result.StartRank != 0)
     {
         int shift = (result.StartRank - 1) * 8;
         return(MoveGenerator.Rank1 << shift);
     }
     else if (result.StartFile != 0)
     {
         int shift = (result.StartFile - 'a');
         return(MoveGenerator.FileA << shift);
     }
     else
     {
         return(0);
     }
 }
Beispiel #3
0
        private static bool TryInferStartSquare(BoardState state, ulong piecesOnCurrentSide, bool isWhiteMove, bool isCapture, char pieceDesignation, ref ParsedMoveState result)
        {
            var endBit           = BitTranslator.TranslateToBit(result.EndFile, result.EndRank);
            var disambiguityMask = BuildDisambiguityMask(result);

            switch (pieceDesignation)
            {
            case Constants.PieceNotation.King:
            {
                var possibleStartBits = MoveGenerator.GetKingMovements(endBit);
                var existingOfPiece   = state.Kings & piecesOnCurrentSide;
                var actualStartPiece  = possibleStartBits & existingOfPiece;

                if (disambiguityMask != 0)
                {
                    actualStartPiece &= disambiguityMask;
                }

                if (actualStartPiece == 0)
                {
                    return(false);
                }

                var startSquare = BitTranslator.TranslateToSquare(actualStartPiece);

                result.StartFile = startSquare.File;
                result.StartRank = startSquare.Rank;
                return(true);
            }

            case Constants.PieceNotation.Knight:
            {
                var possibleStartBits = MoveGenerator.GetKnightMovements(endBit);
                var existingOfPiece   = state.Knights & piecesOnCurrentSide;
                var actualStartPiece  = possibleStartBits & existingOfPiece;

                if (disambiguityMask != 0)
                {
                    actualStartPiece &= disambiguityMask;
                }

                if (actualStartPiece == 0)
                {
                    return(false);
                }

                var startSquare = BitTranslator.TranslateToSquare(actualStartPiece);

                result.StartFile = startSquare.File;
                result.StartRank = startSquare.Rank;
                return(true);
            }

            case Constants.PieceNotation.Queen:
            {
                var possibleStartBits = MoveGenerator.GetQueenMovements(endBit, state);
                var existingOfPiece   = state.Queens & piecesOnCurrentSide;
                var actualStartPiece  = possibleStartBits & existingOfPiece;

                if (disambiguityMask != 0)
                {
                    actualStartPiece &= disambiguityMask;
                }

                if (actualStartPiece == 0)
                {
                    return(false);
                }

                var startSquare = BitTranslator.TranslateToSquare(actualStartPiece);

                result.StartFile = startSquare.File;
                result.StartRank = startSquare.Rank;
                return(true);
            }

            case Constants.PieceNotation.Rook:
            {
                var possibleStartBits = MoveGenerator.GetRookMovements(endBit, state);
                var existingOfPiece   = state.Rooks & piecesOnCurrentSide;
                var actualStartPiece  = possibleStartBits & existingOfPiece;

                if (disambiguityMask != 0)
                {
                    actualStartPiece &= disambiguityMask;
                }

                if (actualStartPiece == 0)
                {
                    return(false);
                }

                var startSquare = BitTranslator.TranslateToSquare(actualStartPiece);

                result.StartFile = startSquare.File;
                result.StartRank = startSquare.Rank;
                return(true);
            }

            case Constants.PieceNotation.Bishop:
            {
                var possibleStartBits = MoveGenerator.GetBishopMovements(endBit, state);
                var existingOfPiece   = state.Bishops & piecesOnCurrentSide;
                var actualStartPiece  = possibleStartBits & existingOfPiece;

                if (disambiguityMask != 0)
                {
                    actualStartPiece &= disambiguityMask;
                }

                if (actualStartPiece == 0)
                {
                    return(false);
                }

                var startSquare = BitTranslator.TranslateToSquare(actualStartPiece);

                result.StartFile = startSquare.File;
                result.StartRank = startSquare.Rank;
                return(true);
            }

            case Constants.PieceNotation.Pawn:
            {
                ulong actualStartPiece;
                if (isWhiteMove)
                {
                    var possibleMoves   = endBit >> 8 | endBit >> 16;
                    var possibleAttacks = (endBit >> 7 & (~MoveGenerator.Rank8)) | (endBit >> 9 & (~MoveGenerator.Rank1));
                    var possibleStart   = isCapture ? possibleAttacks : possibleMoves;
                    var pawns           = state.Pawns & state.WhitePieces;
                    actualStartPiece = pawns & possibleStart;
                }
                else
                {
                    var possibleMoves   = endBit << 8 | endBit << 16;
                    var possibleAttacks = (endBit << 7 & (~MoveGenerator.Rank1)) | (endBit << 9 & (~MoveGenerator.Rank8));
                    var possibleStart   = isCapture ? possibleAttacks : possibleMoves;
                    var pawns           = state.Pawns & state.BlackPieces;
                    actualStartPiece = pawns & possibleStart;
                }

                if (disambiguityMask != 0)
                {
                    actualStartPiece &= disambiguityMask;
                }

                if (actualStartPiece == 0)
                {
                    return(false);
                }

                var startSquare = BitTranslator.TranslateToSquare(actualStartPiece);

                result.StartFile = startSquare.File;
                result.StartRank = startSquare.Rank;
                return(true);
            }

            default:
                return(false);
            }
        }
Beispiel #4
0
        // Parses a move, but does not determine validity
        public static bool TryParseMove(string input, BoardState state, ulong piecesOnCurrentSide, out AnnotatedMove result)
        {
            // ✔ Account for castling (O-O, O-O-O)
            // ✔ Account for regular move (a4, Ng8)
            // ✔ Account for long-form move (Nb1 c3, Nb1-c3)
            // ✔ Account for long-form capture (Ne2xa4)
            // ✔ Account for short-form capture (Nxg4, axb4)
            // ✔ Account for disambiguation (Ngg4)
            // ✔ Account for piece promotions (e8=Q)
            //   ✔ Account for promoted piece must be own colour
            //   ✔ Account for if pawn moves to end without specifying promotion
            //   ✔ Account for if non-pawn specifies promotion
            //   ✔ Account for if promotion piece tries to become pawn
            //   ✔ Account for if specify promotion when not at end
            // ✔ Account for state change marks (Na4+, e2#)
            // ✔ Account for annotations (Na4!, e2??, b5!?)
            // ✔ Account for whitespace (Ne2 x a4) - fail in this case
            // ✔ Account for invalid ranks/files - fail in this case


            var trimmedInput = input.AsSpan().Trim();

            if (trimmedInput.IsEmpty || trimmedInput.IndexOf(' ') != -1)
            {
                result = AnnotatedMove.Empty;
                return(false);
            }

            var isWhiteMove = piecesOnCurrentSide == state.WhitePieces;

            var moveNotation = TrimMoveDescriptors(trimmedInput);

            if (moveNotation.IsEmpty)
            {
                result = AnnotatedMove.Empty;
                return(false);
            }

            var moveDescriptors = moveNotation.Length == trimmedInput.Length
                ? ReadOnlySpan <char> .Empty
                : trimmedInput.Slice(moveNotation.Length);

            var promotion = GetPromotion(moveDescriptors, isWhiteMove);

            if (promotion != SquareContents.Empty)
            {
                moveDescriptors = moveDescriptors.Length > 2 ? moveDescriptors.Slice(2) : ReadOnlySpan <char> .Empty;
            }

            var annotation  = GetAnnotation(moveDescriptors);
            var attackState = GetAttackState(moveDescriptors);

            // Handle castling
            if (TryHandleCastling(moveNotation, isWhiteMove, out var move))
            {
                result = new AnnotatedMove(move, annotation, attackState);
                return(true);
            }

            // Read leading piece designation
            char pieceDesignation;

            if (PieceDesignations.Contains(moveNotation[0]))
            {
                pieceDesignation = moveNotation[0];
                moveNotation     = moveNotation.Slice(1);
            }
            else
            {
                pieceDesignation = Constants.PieceNotation.Pawn;
            }


            int squareIdx = moveNotation.Length - 1;

            // Get end square (must be present)
            var parsedState = new ParsedMoveState();

            parsedState.EndRank = moveNotation[squareIdx--] - '0';
            parsedState.EndFile = Char.ToLower(moveNotation[squareIdx--]);


            var lastRankForColor = isWhiteMove ? Constants.Board.NumberOfRows : 1;

            if (promotion != 0)
            {
                if (
                    pieceDesignation != Constants.PieceNotation.Pawn || // promoted non-pawn
                    (promotion & SquareContents.Pawn) != 0 ||           // promoted to invalid piece (pawn)
                    parsedState.EndRank != lastRankForColor             // promoted but not at end
                    )
                {
                    result = AnnotatedMove.Empty;
                    return(false);
                }

                parsedState.PromotedPiece = promotion;
            }
            else if (pieceDesignation == Constants.PieceNotation.Pawn && parsedState.EndRank == lastRankForColor)
            {
                // Moved pawn to end without promoting
                result = AnnotatedMove.Empty;
                return(false);
            }

            // Ignore delimiter (if present)
            bool isCapture = false;

            if (squareIdx >= 0 && SquareDelimiters.Contains(moveNotation[squareIdx]))
            {
                isCapture = moveNotation[squareIdx] == Constants.MoveType.Capture;
                squareIdx--;
            }

            // Read start squares (if present)
            if (squareIdx >= 0 && Char.IsDigit(moveNotation[squareIdx]))
            {
                parsedState.StartRank = moveNotation[squareIdx--] - '0';
            }

            if (squareIdx >= 0 && Char.IsLetter(moveNotation[squareIdx]))
            {
                parsedState.StartFile = Char.ToLower(moveNotation[squareIdx--]);
            }

            if (parsedState.StartRank != 0 && parsedState.StartFile != 0)
            {
                var valid = IsMoveValid(parsedState);
                result = valid ? new AnnotatedMove(parsedState.ToMove(), annotation, attackState) : AnnotatedMove.Empty;
                return(valid);
            }

            if (TryInferStartSquare(state, piecesOnCurrentSide, isWhiteMove, isCapture, pieceDesignation, ref parsedState))
            {
                var valid = IsMoveValid(parsedState);
                result = valid ? new AnnotatedMove(parsedState.ToMove(), annotation, attackState) : AnnotatedMove.Empty;
                return(valid);
            }

            result = AnnotatedMove.Empty;
            return(false);
        }