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()); }
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); } }
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)); }