private void DrawBookMoves() { ClearArrows(); arrowIndex = 0; if (book.HasPosition(board.ZobristKey)) { var bookPosition = book.GetBookPosition(board.ZobristKey); var mostPlayed = 0; var leastPlayed = int.MaxValue; foreach (var moveInfo in bookPosition.numTimesMovePlayed) { var numTimesPlayed = moveInfo.Value; mostPlayed = Math.Max(mostPlayed, numTimesPlayed); leastPlayed = Math.Min(leastPlayed, numTimesPlayed); } foreach (var moveInfo in bookPosition.numTimesMovePlayed) { var move = new Move(moveInfo.Key); var numTimesPlayed = moveInfo.Value; Vector2 startPos = boardUI.PositionFromCoord(BoardRepresentation.CoordFromIndex(move.StartSquare)); Vector2 endPos = boardUI.PositionFromCoord(BoardRepresentation.CoordFromIndex(move.TargetSquare)); var t = Mathf.InverseLerp(leastPlayed, mostPlayed, numTimesPlayed); if (mostPlayed == leastPlayed) { t = 1; } var col = Color.Lerp(rarestCol, mostCommonCol, t); DrawArrow2D(startPos, endPos, arrowWidth, arrowHeadSize, col, zPos: -1 - t); } } }
// Initialize lookup data static PrecomputedMoveData() { pawnAttacksWhite = new int[64][]; pawnAttacksBlack = new int[64][]; numSquaresToEdge = new int[8][]; knightMoves = new byte[64][]; kingMoves = new byte[64][]; numSquaresToEdge = new int[64][]; rookMoves = new ulong[64]; bishopMoves = new ulong[64]; queenMoves = new ulong[64]; // Calculate knight jumps and available squares for each square on the board. // See comments by variable definitions for more info. int[] allKnightJumps = { 15, 17, -17, -15, 10, -6, 6, -10 }; knightAttackBitboards = new ulong[64]; kingAttackBitboards = new ulong[64]; pawnAttackBitboards = new ulong[64][]; for (var squareIndex = 0; squareIndex < 64; squareIndex++) { var y = squareIndex / 8; var x = squareIndex - y * 8; var north = 7 - y; var south = y; var west = x; var east = 7 - x; numSquaresToEdge[squareIndex] = new int[8]; numSquaresToEdge[squareIndex][0] = north; numSquaresToEdge[squareIndex][1] = south; numSquaresToEdge[squareIndex][2] = west; numSquaresToEdge[squareIndex][3] = east; numSquaresToEdge[squareIndex][4] = Min(north, west); numSquaresToEdge[squareIndex][5] = Min(south, east); numSquaresToEdge[squareIndex][6] = Min(north, east); numSquaresToEdge[squareIndex][7] = Min(south, west); // Calculate all squares knight can jump to from current square var legalKnightJumps = new List <byte>(); ulong knightBitboard = 0; foreach (var knightJumpDelta in allKnightJumps) { var knightJumpSquare = squareIndex + knightJumpDelta; if (knightJumpSquare >= 0 && knightJumpSquare < 64) { var knightSquareY = knightJumpSquare / 8; var knightSquareX = knightJumpSquare - knightSquareY * 8; // Ensure knight has moved max of 2 squares on x/y axis (to reject indices that have wrapped around side of board) var maxCoordMoveDst = Max(Abs(x - knightSquareX), Abs(y - knightSquareY)); if (maxCoordMoveDst == 2) { legalKnightJumps.Add((byte)knightJumpSquare); knightBitboard |= 1ul << knightJumpSquare; } } } knightMoves[squareIndex] = legalKnightJumps.ToArray(); knightAttackBitboards[squareIndex] = knightBitboard; // Calculate all squares king can move to from current square (not including castling) var legalKingMoves = new List <byte>(); foreach (var kingMoveDelta in directionOffsets) { var kingMoveSquare = squareIndex + kingMoveDelta; if (kingMoveSquare >= 0 && kingMoveSquare < 64) { var kingSquareY = kingMoveSquare / 8; var kingSquareX = kingMoveSquare - kingSquareY * 8; // Ensure king has moved max of 1 square on x/y axis (to reject indices that have wrapped around side of board) var maxCoordMoveDst = Max(Abs(x - kingSquareX), Abs(y - kingSquareY)); if (maxCoordMoveDst == 1) { legalKingMoves.Add((byte)kingMoveSquare); kingAttackBitboards[squareIndex] |= 1ul << kingMoveSquare; } } } kingMoves[squareIndex] = legalKingMoves.ToArray(); // Calculate legal pawn captures for white and black var pawnCapturesWhite = new List <int>(); var pawnCapturesBlack = new List <int>(); pawnAttackBitboards[squareIndex] = new ulong[2]; if (x > 0) { if (y < 7) { pawnCapturesWhite.Add(squareIndex + 7); pawnAttackBitboards[squareIndex][Board.WhiteIndex] |= 1ul << (squareIndex + 7); } if (y > 0) { pawnCapturesBlack.Add(squareIndex - 9); pawnAttackBitboards[squareIndex][Board.BlackIndex] |= 1ul << (squareIndex - 9); } } if (x < 7) { if (y < 7) { pawnCapturesWhite.Add(squareIndex + 9); pawnAttackBitboards[squareIndex][Board.WhiteIndex] |= 1ul << (squareIndex + 9); } if (y > 0) { pawnCapturesBlack.Add(squareIndex - 7); pawnAttackBitboards[squareIndex][Board.BlackIndex] |= 1ul << (squareIndex - 7); } } pawnAttacksWhite[squareIndex] = pawnCapturesWhite.ToArray(); pawnAttacksBlack[squareIndex] = pawnCapturesBlack.ToArray(); // Rook moves for (var directionIndex = 0; directionIndex < 4; directionIndex++) { var currentDirOffset = directionOffsets[directionIndex]; for (var n = 0; n < numSquaresToEdge[squareIndex][directionIndex]; n++) { var targetSquare = squareIndex + currentDirOffset * (n + 1); rookMoves[squareIndex] |= 1ul << targetSquare; } } // Bishop moves for (var directionIndex = 4; directionIndex < 8; directionIndex++) { var currentDirOffset = directionOffsets[directionIndex]; for (var n = 0; n < numSquaresToEdge[squareIndex][directionIndex]; n++) { var targetSquare = squareIndex + currentDirOffset * (n + 1); bishopMoves[squareIndex] |= 1ul << targetSquare; } } queenMoves[squareIndex] = rookMoves[squareIndex] | bishopMoves[squareIndex]; } directionLookup = new int[127]; for (var i = 0; i < 127; i++) { var offset = i - 63; var absOffset = Abs(offset); var absDir = 1; if (absOffset % 9 == 0) { absDir = 9; } else if (absOffset % 8 == 0) { absDir = 8; } else if (absOffset % 7 == 0) { absDir = 7; } directionLookup[i] = absDir * Sign(offset); } // Distance lookup orthogonalDistance = new int[64, 64]; kingDistance = new int[64, 64]; centreManhattanDistance = new int[64]; for (var squareA = 0; squareA < 64; squareA++) { var coordA = BoardRepresentation.CoordFromIndex(squareA); var fileDstFromCentre = Max(3 - coordA.fileIndex, coordA.fileIndex - 4); var rankDstFromCentre = Max(3 - coordA.rankIndex, coordA.rankIndex - 4); centreManhattanDistance[squareA] = fileDstFromCentre + rankDstFromCentre; for (var squareB = 0; squareB < 64; squareB++) { var coordB = BoardRepresentation.CoordFromIndex(squareB); var rankDistance = Abs(coordA.rankIndex - coordB.rankIndex); var fileDistance = Abs(coordA.fileIndex - coordB.fileIndex); orthogonalDistance[squareA, squareB] = fileDistance + rankDistance; kingDistance[squareA, squareB] = Max(fileDistance, rankDistance); } } }
// Make a move on the board // The inSearch parameter controls whether this move should be recorded in the game history (for detecting three-fold repetition) public void MakeMove(Move move, bool inSearch = false) { uint oldEnPassantFile = (currentGameState >> 4) & 15; uint originalCastleState = currentGameState & 15; uint newCastleState = originalCastleState; currentGameState = 0; int opponentColourIndex = 1 - ColourToMoveIndex; int moveFrom = move.StartSquare; int moveTo = move.TargetSquare; int capturedPieceType = Piece.PieceType(Square[moveTo]); int movePiece = Square[moveFrom]; int movePieceType = Piece.PieceType(movePiece); int moveFlag = move.MoveFlag; bool isPromotion = move.IsPromotion; bool isEnPassant = moveFlag == Move.Flag.EnPassantCapture; // Handle captures currentGameState |= (ushort)(capturedPieceType << 8); if (capturedPieceType != 0 && !isEnPassant) { ZobristKey ^= Zobrist.piecesArray[capturedPieceType, opponentColourIndex, moveTo]; GetPieceList(capturedPieceType, opponentColourIndex).RemovePieceAtSquare(moveTo); } // Move pieces in piece lists if (movePieceType == Piece.King) { KingSquare[ColourToMoveIndex] = moveTo; newCastleState &= (WhiteToMove) ? whiteCastleMask : blackCastleMask; } else { GetPieceList(movePieceType, ColourToMoveIndex).MovePiece(moveFrom, moveTo); } int pieceOnTargetSquare = movePiece; // Handle promotion if (isPromotion) { int promoteType = 0; switch (moveFlag) { case Move.Flag.PromoteToQueen: promoteType = Piece.Queen; queens[ColourToMoveIndex].AddPieceAtSquare(moveTo); break; case Move.Flag.PromoteToRook: promoteType = Piece.Rook; rooks[ColourToMoveIndex].AddPieceAtSquare(moveTo); break; case Move.Flag.PromoteToBishop: promoteType = Piece.Bishop; bishops[ColourToMoveIndex].AddPieceAtSquare(moveTo); break; case Move.Flag.PromoteToKnight: promoteType = Piece.Knight; knights[ColourToMoveIndex].AddPieceAtSquare(moveTo); break; } pieceOnTargetSquare = promoteType | ColourToMove; pawns[ColourToMoveIndex].RemovePieceAtSquare(moveTo); } else { // Handle other special moves (en-passant, and castling) switch (moveFlag) { case Move.Flag.EnPassantCapture: int epPawnSquare = moveTo + ((ColourToMove == Piece.White) ? -8 : 8); currentGameState |= (ushort)(Square[epPawnSquare] << 8); // add pawn as capture type Square[epPawnSquare] = 0; // clear ep capture square pawns[opponentColourIndex].RemovePieceAtSquare(epPawnSquare); ZobristKey ^= Zobrist.piecesArray[Piece.Pawn, opponentColourIndex, epPawnSquare]; break; case Move.Flag.Castling: bool kingside = moveTo == BoardRepresentation.g1 || moveTo == BoardRepresentation.g8; int castlingRookFromIndex = (kingside) ? moveTo + 1 : moveTo - 2; int castlingRookToIndex = (kingside) ? moveTo - 1 : moveTo + 1; Square[castlingRookFromIndex] = Piece.None; Square[castlingRookToIndex] = Piece.Rook | ColourToMove; rooks[ColourToMoveIndex].MovePiece(castlingRookFromIndex, castlingRookToIndex); ZobristKey ^= Zobrist.piecesArray[Piece.Rook, ColourToMoveIndex, castlingRookFromIndex]; ZobristKey ^= Zobrist.piecesArray[Piece.Rook, ColourToMoveIndex, castlingRookToIndex]; break; } } // Update the board representation: Square[moveTo] = pieceOnTargetSquare; Square[moveFrom] = 0; // Pawn has moved two forwards, mark file with en-passant flag if (moveFlag == Move.Flag.PawnTwoForward) { int file = BoardRepresentation.FileIndex(moveFrom) + 1; currentGameState |= (ushort)(file << 4); ZobristKey ^= Zobrist.enPassantFile[file]; } // Piece moving to/from rook square removes castling right for that side if (originalCastleState != 0) { if (moveTo == BoardRepresentation.h1 || moveFrom == BoardRepresentation.h1) { newCastleState &= whiteCastleKingsideMask; } else if (moveTo == BoardRepresentation.a1 || moveFrom == BoardRepresentation.a1) { newCastleState &= whiteCastleQueensideMask; } if (moveTo == BoardRepresentation.h8 || moveFrom == BoardRepresentation.h8) { newCastleState &= blackCastleKingsideMask; } else if (moveTo == BoardRepresentation.a8 || moveFrom == BoardRepresentation.a8) { newCastleState &= blackCastleQueensideMask; } } // Update zobrist key with new piece position and side to move ZobristKey ^= Zobrist.sideToMove; ZobristKey ^= Zobrist.piecesArray[movePieceType, ColourToMoveIndex, moveFrom]; ZobristKey ^= Zobrist.piecesArray[Piece.PieceType(pieceOnTargetSquare), ColourToMoveIndex, moveTo]; if (oldEnPassantFile != 0) { ZobristKey ^= Zobrist.enPassantFile[oldEnPassantFile]; } if (newCastleState != originalCastleState) { ZobristKey ^= Zobrist.castlingRights[originalCastleState]; // remove old castling rights state ZobristKey ^= Zobrist.castlingRights[newCastleState]; // add new castling rights state } currentGameState |= newCastleState; currentGameState |= (uint)fiftyMoveCounter << 14; gameStateHistory.Push(currentGameState); // Change side to move WhiteToMove = !WhiteToMove; ColourToMove = (WhiteToMove) ? Piece.White : Piece.Black; OpponentColour = (WhiteToMove) ? Piece.Black : Piece.White; ColourToMoveIndex = 1 - ColourToMoveIndex; plyCount++; fiftyMoveCounter++; if (!inSearch) { if (movePieceType == Piece.Pawn || capturedPieceType != Piece.None) { RepetitionPositionHistory.Clear(); fiftyMoveCounter = 0; } else { RepetitionPositionHistory.Push(ZobristKey); } } }
static PrecomputedMoveData() { pawnAttacksWhite = new int[64][]; pawnAttacksBlack = new int[64][]; numSquaresToEdge = new int[8][]; knightMoves = new byte[64][]; kingMoves = new byte[64][]; numSquaresToEdge = new int[64][]; rookMoves = new ulong[64]; bishopMoves = new ulong[64]; queenMoves = new ulong[64]; int[] allKnightJumps = { 15, 17, -17, -15, 10, -6, 6, -10 }; knightAttackBitboards = new ulong[64]; kingAttackBitboards = new ulong[64]; pawnAttackBitboards = new ulong[64][]; for (int squareIndex = 0; squareIndex < 64; squareIndex++) { int y = squareIndex / 8; int x = squareIndex - y * 8; int north = 7 - y; int south = y; int west = x; int east = 7 - x; numSquaresToEdge[squareIndex] = new int[8]; numSquaresToEdge[squareIndex][0] = north; numSquaresToEdge[squareIndex][1] = south; numSquaresToEdge[squareIndex][2] = west; numSquaresToEdge[squareIndex][3] = east; numSquaresToEdge[squareIndex][4] = System.Math.Min(north, west); numSquaresToEdge[squareIndex][5] = System.Math.Min(south, east); numSquaresToEdge[squareIndex][6] = System.Math.Min(north, east); numSquaresToEdge[squareIndex][7] = System.Math.Min(south, west); // Wszystkie pole na które moe skoczyć skoczek z danego pola var legalKnightJumps = new List <byte> (); ulong knightBitboard = 0; foreach (int knightJumpDelta in allKnightJumps) { int knightJumpSquare = squareIndex + knightJumpDelta; if (knightJumpSquare >= 0 && knightJumpSquare < 64) { int knightSquareY = knightJumpSquare / 8; int knightSquareX = knightJumpSquare - knightSquareY * 8; // Zapewnienie, że skoczek pokona co najmniej 2 pola (żeby uniknąć wyjścia poza mape i glitchowania) int maxCoordMoveDst = System.Math.Max(System.Math.Abs(x - knightSquareX), System.Math.Abs(y - knightSquareY)); if (maxCoordMoveDst == 2) { legalKnightJumps.Add((byte)knightJumpSquare); knightBitboard |= 1ul << knightJumpSquare; } } } knightMoves[squareIndex] = legalKnightJumps.ToArray(); knightAttackBitboards[squareIndex] = knightBitboard; // To co wyżej tylko dla króla (nie wliczając roszady) var legalKingMoves = new List <byte> (); foreach (int kingMoveDelta in directionOffsets) { int kingMoveSquare = squareIndex + kingMoveDelta; if (kingMoveSquare >= 0 && kingMoveSquare < 64) { int kingSquareY = kingMoveSquare / 8; int kingSquareX = kingMoveSquare - kingSquareY * 8; // Zapewnienie, że król pokona co najmniej 1 pole (żeby uniknąć wyjścia poza mape i glitchowania) int maxCoordMoveDst = System.Math.Max(System.Math.Abs(x - kingSquareX), System.Math.Abs(y - kingSquareY)); if (maxCoordMoveDst == 1) { legalKingMoves.Add((byte)kingMoveSquare); kingAttackBitboards[squareIndex] |= 1ul << kingMoveSquare; } } } kingMoves[squareIndex] = legalKingMoves.ToArray(); List <int> pawnCapturesWhite = new List <int> (); List <int> pawnCapturesBlack = new List <int> (); pawnAttackBitboards[squareIndex] = new ulong[2]; if (x > 0) { if (y < 7) { pawnCapturesWhite.Add(squareIndex + 7); pawnAttackBitboards[squareIndex][Board.WhiteIndex] |= 1ul << (squareIndex + 7); } if (y > 0) { pawnCapturesBlack.Add(squareIndex - 9); pawnAttackBitboards[squareIndex][Board.BlackIndex] |= 1ul << (squareIndex - 9); } } if (x < 7) { if (y < 7) { pawnCapturesWhite.Add(squareIndex + 9); pawnAttackBitboards[squareIndex][Board.WhiteIndex] |= 1ul << (squareIndex + 9); } if (y > 0) { pawnCapturesBlack.Add(squareIndex - 7); pawnAttackBitboards[squareIndex][Board.BlackIndex] |= 1ul << (squareIndex - 7); } } pawnAttacksWhite[squareIndex] = pawnCapturesWhite.ToArray(); pawnAttacksBlack[squareIndex] = pawnCapturesBlack.ToArray(); // Ruch wieży for (int directionIndex = 0; directionIndex < 4; directionIndex++) { int currentDirOffset = directionOffsets[directionIndex]; for (int n = 0; n < numSquaresToEdge[squareIndex][directionIndex]; n++) { int targetSquare = squareIndex + currentDirOffset * (n + 1); rookMoves[squareIndex] |= 1ul << targetSquare; } } // Ruch gońca for (int directionIndex = 4; directionIndex < 8; directionIndex++) { int currentDirOffset = directionOffsets[directionIndex]; for (int n = 0; n < numSquaresToEdge[squareIndex][directionIndex]; n++) { int targetSquare = squareIndex + currentDirOffset * (n + 1); bishopMoves[squareIndex] |= 1ul << targetSquare; } } queenMoves[squareIndex] = rookMoves[squareIndex] | bishopMoves[squareIndex]; } directionLookup = new int[127]; for (int i = 0; i < 127; i++) { int offset = i - 63; int absOffset = System.Math.Abs(offset); int absDir = 1; if (absOffset % 9 == 0) { absDir = 9; } else if (absOffset % 8 == 0) { absDir = 8; } else if (absOffset % 7 == 0) { absDir = 7; } directionLookup[i] = absDir * System.Math.Sign(offset); } // Wyszukiwanie dystansu orthogonalDistance = new int[64, 64]; kingDistance = new int[64, 64]; centreManhattanDistance = new int[64]; for (int squareA = 0; squareA < 64; squareA++) { Coord coordA = BoardRepresentation.CoordFromIndex(squareA); int fileDstFromCentre = Max(3 - coordA.fileIndex, coordA.fileIndex - 4); int rankDstFromCentre = Max(3 - coordA.rankIndex, coordA.rankIndex - 4); centreManhattanDistance[squareA] = fileDstFromCentre + rankDstFromCentre; for (int squareB = 0; squareB < 64; squareB++) { Coord coordB = BoardRepresentation.CoordFromIndex(squareB); int rankDistance = Abs(coordA.rankIndex - coordB.rankIndex); int fileDistance = Abs(coordA.fileIndex - coordB.fileIndex); orthogonalDistance[squareA, squareB] = fileDistance + rankDistance; kingDistance[squareA, squareB] = Max(fileDistance, rankDistance); } } }
static Move MoveFromAlgebraic(Board board, string algebraicMove) { MoveGenerator moveGenerator = new MoveGenerator(); // Remove unrequired info from move string algebraicMove = algebraicMove.Replace("+", "").Replace("#", "").Replace("x", "").Replace("-", ""); var allMoves = moveGenerator.GenerateMoves(board); Move move = new Move(); foreach (Move moveToTest in allMoves) { move = moveToTest; int moveFromIndex = move.StartSquare; int moveToIndex = move.TargetSquare; int movePieceType = Piece.PieceType(board.Square[moveFromIndex]); Coord fromCoord = BoardRepresentation.CoordFromIndex(moveFromIndex); Coord toCoord = BoardRepresentation.CoordFromIndex(moveToIndex); if (algebraicMove == "OO") // castle kingside { if (movePieceType == Piece.King && moveToIndex - moveFromIndex == 2) { return(move); } } else if (algebraicMove == "OOO") // castle queenside { if (movePieceType == Piece.King && moveToIndex - moveFromIndex == -2) { return(move); } } // Is pawn move if starts with any file indicator (e.g. 'e'4. Note that uppercase B is used for bishops) else if (fileNames.Contains(algebraicMove[0].ToString())) { if (movePieceType != Piece.Pawn) { continue; } if (fileNames.IndexOf(algebraicMove[0]) == fromCoord.fileIndex) // correct starting file { if (algebraicMove.Contains("=")) // is promotion { if (toCoord.rankIndex == 0 || toCoord.rankIndex == 7) { if (algebraicMove.Length == 5) // pawn is capturing to promote { char targetFile = algebraicMove[1]; if (BoardRepresentation.fileNames.IndexOf(targetFile) != toCoord.fileIndex) { // Skip if not moving to correct file continue; } } char promotionChar = algebraicMove[algebraicMove.Length - 1]; if (move.PromotionPieceType != GetPieceTypeFromSymbol(promotionChar)) { continue; // skip this move, incorrect promotion type } return(move); } } else { char targetFile = algebraicMove[algebraicMove.Length - 2]; char targetRank = algebraicMove[algebraicMove.Length - 1]; if (BoardRepresentation.fileNames.IndexOf(targetFile) == toCoord.fileIndex) // correct ending file { if (targetRank.ToString() == (toCoord.rankIndex + 1).ToString()) // correct ending rank { break; } } } } } else // regular piece move { char movePieceChar = algebraicMove[0]; if (GetPieceTypeFromSymbol(movePieceChar) != movePieceType) { continue; // skip this move, incorrect move piece type } char targetFile = algebraicMove[algebraicMove.Length - 2]; char targetRank = algebraicMove[algebraicMove.Length - 1]; if (BoardRepresentation.fileNames.IndexOf(targetFile) == toCoord.fileIndex) // correct ending file { if (targetRank.ToString() == (toCoord.rankIndex + 1).ToString()) // correct ending rank { if (algebraicMove.Length == 4) // addition char present for disambiguation (e.g. Nbd7 or R7e2) { char disambiguationChar = algebraicMove[1]; if (BoardRepresentation.fileNames.Contains(disambiguationChar.ToString())) // is file disambiguation { if (BoardRepresentation.fileNames.IndexOf(disambiguationChar) != fromCoord.fileIndex) // incorrect starting file { continue; } } else // is rank disambiguation { if (disambiguationChar.ToString() != (fromCoord.rankIndex + 1).ToString()) // incorrect starting rank { continue; } } } break; } } } } return(move); }