Example #1
0
        public static string ToMoveString(Move move, BoardState board, AttackState attackState)
        {
            // Algo: Move from end, adding tokens in order
            // - Check if move is promotion, add tokens if so
            // - Add end square
            // - Check if move is capture, output 'x' if so
            // - Then check if multiple pieces of same type can move to end square, then output first rank or file of moved piece
            // - Then output piece notation if piece is not a pawn
            //
            // - Now that the move string is built, splice to only used tokens


            var startSquareBit = BitTranslator.TranslateToBit(move.StartFile, move.StartRank);
            var endSquareBit   = BitTranslator.TranslateToBit(move.EndFile, move.EndRank);
            var movedPiece     = board.GetSquareContents(startSquareBit) & ~SquareContents.Colours;

            Span <char> buffer  = stackalloc char[8];
            var         lastIdx = buffer.Length - 1;


            if (attackState != AttackState.None)
            {
                // Check if move ended in attack
                var notation = GetAttackStateMoveNotation(attackState);
                if (notation != 0)
                {
                    buffer[lastIdx--] = notation;
                }
            }

            if (movedPiece == SquareContents.King)
            {
                // Check for castling
                var squareDiff     = (decimal)startSquareBit / endSquareBit;
                var castleNotation = "";

                if (squareDiff == 4) // King has moved 2 squares horizontally towards queenside
                {
                    castleNotation = "0-0-0";
                }
                else if (squareDiff == 0.25M) // King has moved 2 squares horizontally towards kingside
                {
                    castleNotation = "0-0";
                }

                if (!string.IsNullOrEmpty(castleNotation))
                {
                    var copyIdx        = lastIdx - castleNotation.Length + 1;
                    var notationBuffer = buffer.Slice(copyIdx, castleNotation.Length);
                    castleNotation.AsSpan().CopyTo(notationBuffer);
                    lastIdx -= castleNotation.Length;

                    var copyLocation = buffer.Slice(lastIdx + 1);
                    return(copyLocation.ToString());
                }
            }

            if (move.PromotedPiece != SquareContents.Empty)
            {
                var pieceType = move.PromotedPiece & ~SquareContents.Colours;

                buffer[lastIdx--] = PiecesAsLetters[pieceType];
                buffer[lastIdx--] = '=';
            }

            buffer[lastIdx--] = (char)(move.EndRank + '0');
            buffer[lastIdx--] = move.EndFile;

            var isCapture = (endSquareBit & board.AllPieces) != 0;

            if (!isCapture && movedPiece == SquareContents.Pawn)
            {
                // Capture pattern for pawn is diagonal movement
                // Explicitly checking will account for capture by en passant where end square is empty
                var diff = Math.Max(startSquareBit, endSquareBit) / Math.Min(startSquareBit, endSquareBit);
                isCapture = diff == 1 << 7 || diff == 1 << 9;
            }

            if (isCapture)
            {
                buffer[lastIdx--] = 'x';
            }

            if (movedPiece == SquareContents.Pawn)
            {
                if (isCapture)
                {
                    buffer[lastIdx--] = move.StartFile;
                }
            }
            else
            {
                if (movedPiece != SquareContents.King)
                {
                    // Check for disambiguation
                    var colorMask = (startSquareBit & board.WhitePieces) != 0 ? board.WhitePieces : board.BlackPieces;
                    var pieceMask = 0UL;

                    if (movedPiece == SquareContents.Knight)
                    {
                        pieceMask = board.Knights;
                    }
                    else if (movedPiece == SquareContents.Bishop)
                    {
                        pieceMask = board.Bishops;
                    }
                    else if (movedPiece == SquareContents.Rook)
                    {
                        pieceMask = board.Rooks;
                    }
                    else if (movedPiece == SquareContents.Queen)
                    {
                        pieceMask = board.Queens;
                    }

                    pieceMask = pieceMask & colorMask & ~startSquareBit;

                    var otherPossibleStartSquares = new List <Square>();
                    for (var i = 0; i < 64; i++)
                    {
                        var mask = pieceMask & (1UL << i);
                        if (mask != 0)
                        {
                            var moves = MoveGenerator.GenerateStatelessMovesForPiece(board, mask);
                            if ((moves & endSquareBit) != 0)
                            {
                                otherPossibleStartSquares.Add(BitTranslator.TranslateToSquare(mask));
                            }
                        }
                    }

                    // There may be multiple possible start squares to disambiguate from
                    var disambiguateByRank = false;
                    var disambiguateByFile = false;
                    foreach (var otherSquare in otherPossibleStartSquares)
                    {
                        disambiguateByRank = disambiguateByRank || (otherSquare.File == move.StartFile);
                        disambiguateByFile = disambiguateByFile || (otherSquare.Rank == move.StartRank);
                    }

                    // Need to disambiguate, but both rank + file are different. Default to by file.
                    if (!disambiguateByFile && !disambiguateByRank && otherPossibleStartSquares.Count > 0)
                    {
                        disambiguateByFile = true;
                    }

                    if (disambiguateByRank)
                    {
                        buffer[lastIdx--] = (char)(move.StartRank + '0');
                    }
                    if (disambiguateByFile)
                    {
                        buffer[lastIdx--] = move.StartFile;
                    }
                }

                buffer[lastIdx--] = PiecesAsLetters[movedPiece];
            }

            var segment = buffer.Slice(lastIdx + 1);

            return(segment.ToString());
        }
Example #2
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);
            }
        }
Example #3
0
        private static GameState AnalyzeAndApplyState(GameState newState, ulong endSquare, ulong opponentPieces)
        {
            // All state detections
            // ✔ Account for piece promotions
            // ✔ Detect checks
            // ✔ Detect checkmate
            // ✔ Detect stalemate
            // ✔ Detect draw by repetition
            // ✔ Detect draw by inactivity (50 moves without a capture)

            var newBoard     = newState.Board;
            var didBlackMove = ((endSquare & newBoard.BlackPieces) != 0) ? 1 : 0;

            var whiteAttack = MoveGenerator.GenerateSquaresAttackedBy(newState, newBoard.WhitePieces);
            var blackAttack = MoveGenerator.GenerateSquaresAttackedBy(newState, newBoard.BlackPieces);

            var squaresAttackedBy = new IndexedTuple <ulong>(whiteAttack, blackAttack);

            var ownMovements      = squaresAttackedBy.Get(didBlackMove);
            var opponentMovements = MoveGenerator.GenerateValidMoves(newState, opponentPieces, ownMovements);

            var opponentKingUnderAttack = (opponentPieces & newBoard.Kings & ownMovements) != 0;
            var opponentCanMove         = opponentMovements != 0;

            var attackState = AttackState.None;

            if (opponentKingUnderAttack)
            {
                attackState = opponentCanMove ? AttackState.Check : AttackState.Checkmate;
            }
            else if (!opponentCanMove)
            {
                attackState = AttackState.Stalemate;
            }
            else
            {
                var count          = 0;
                var duplicateCount = 0;
                var newStateUnmovedCastlingPieces = (newState.PiecesOnStartSquares & MoveGenerator.StartingKingsAndRooks);

                foreach (var state in newState.PossibleRepeatedHistory)
                {
                    count++;
                    if (BoardState.Equals(state.Item1, newBoard) && state.Item2 == newStateUnmovedCastlingPieces)
                    {
                        duplicateCount++;
                    }
                }

                if (count == Constants.MoveLimits.InactivityLimit)
                {
                    attackState = AttackState.DrawByInactivity;
                }
                else if (duplicateCount >= Constants.MoveLimits.RepetitionLimit)
                {
                    attackState = AttackState.DrawByRepetition;
                }
            }

            return(newState.SetAttackState(attackState, squaresAttackedBy));
        }