private ErrorCondition Move(Move move) { if (!BitTranslator.IsValidSquare(move.StartFile, move.StartRank)) { return(ErrorCondition.InvalidSquare); } if (!BitTranslator.IsValidSquare(move.EndFile, move.EndRank)) { return(ErrorCondition.InvalidSquare); } ulong startSquare = BitTranslator.TranslateToBit(move.StartFile, move.StartRank); if ((CurrentTurn & startSquare) == 0) { return(ErrorCondition.MustMoveOwnPiece); // Can't move if not your turn } ulong endSquare = BitTranslator.TranslateToBit(move.EndFile, move.EndRank); if ((CurrentTurn & endSquare) != 0) { return(ErrorCondition.CantTakeOwnPiece); // Can't end move on own piece } ulong allMoves = MoveGenerator.GenerateValidMovesForPiece(CurrentState, startSquare); if ((endSquare & allMoves) == 0) { return(ErrorCondition.InvalidMovement); // End square is not a valid move } // The move is good, so update state // Update current state UpdateState(move, startSquare, endSquare); return(ErrorCondition.None); }
public SquareContents GetSquareContents(char file, int rank) { var result = (SquareContents)0; if (BitTranslator.IsValidSquare(file, rank)) { ulong bit = BitTranslator.TranslateToBit(file, rank); result = CurrentState.Board.GetSquareContents(bit); } return(result); }
public List <Square> GetValidMoves(char file, int rank) { if (!BitTranslator.IsValidSquare(file, rank)) { throw new InvalidOperationException(); } var square = BitTranslator.TranslateToBit(file, rank); ulong allMoves = MoveGenerator.GenerateValidMovesForPiece(CurrentState, square); return(BitTranslator.TranslateToSquares(allMoves)); }
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); } }
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()); }
public static GameState ApplyMove(GameState state, Move move, ulong startSquare, ulong endSquare) { var history = state.PossibleRepeatedHistory; var resetHistory = ( ((state.Board.AllPieces & endSquare) != 0) || // regular capture ((state.Board.Pawns & startSquare) != 0) // pawn movement ); if (resetHistory) { history = history.Clear(); } var newBoard = state.Board.MovePiece(startSquare, endSquare); if ((startSquare & state.Board.Pawns) != 0) { if (endSquare == state.EnPassantSquare) { var opponentPawn = BitTranslator.TranslateToBit(move.EndFile, move.StartRank); newBoard = BoardStateMutator.ClearPiece(newBoard, opponentPawn); } else if (move.PromotedPiece != SquareContents.Empty) { newBoard = newBoard.ClearPiece(endSquare).SetPiece(endSquare, move.PromotedPiece); } } else if ((startSquare & state.Board.Kings) != 0) { // Castling if (Math.Abs(move.EndFile - move.StartFile) == 2) { var rookDestinationRelativeKing = Math.Sign(move.StartFile - move.EndFile); var rookDestinationFile = (char)(move.EndFile + rookDestinationRelativeKing); var rookDestinationBit = BitTranslator.TranslateToBit(rookDestinationFile, move.EndRank); var rookOriginFile = rookDestinationRelativeKing > 0 ? 'a' : 'h'; var rookOriginBit = BitTranslator.TranslateToBit(rookOriginFile, move.EndRank); newBoard = BoardStateMutator.MovePiece(newBoard, rookOriginBit, rookDestinationBit); } } var newPiecesStillOnStartSquare = state.PiecesOnStartSquares & ~(startSquare | endSquare); var castlingPiecesUnmoved = newPiecesStillOnStartSquare & MoveGenerator.StartingKingsAndRooks; var enPassantSquare = 0UL; if ((startSquare & state.Board.Pawns) != 0) { if (startSquare >> 16 == endSquare) { enPassantSquare = startSquare >> 8; } else if (startSquare << 16 == endSquare) { enPassantSquare = startSquare << 8; } } history = history.Push((newBoard, castlingPiecesUnmoved)); return(new GameState(newBoard, move, history, state.AttackState, newPiecesStillOnStartSquare, enPassantSquare, state.SquaresAttackedBy)); }