diff --git a/Chess/nimfish/nimfishpkg/bitboards.nim b/Chess/nimfish/nimfishpkg/bitboards.nim index d8369ad..c652f68 100644 --- a/Chess/nimfish/nimfishpkg/bitboards.nim +++ b/Chess/nimfish/nimfishpkg/bitboards.nim @@ -76,11 +76,12 @@ func countSquares*(self: Bitboard): int {.inline.} = func lowestSquare*(self: Bitboard): Square {.inline.} = - ## Returns the index of the lowest one bit + ## Returns the index of the lowest set bit ## in the given bitboard as a square result = Square(self.countTrailingZeroBits().uint8) + func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64 func getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank) func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64 diff --git a/Chess/nimfish/nimfishpkg/board.nim b/Chess/nimfish/nimfishpkg/board.nim index d43bf61..e894c5e 100644 --- a/Chess/nimfish/nimfishpkg/board.nim +++ b/Chess/nimfish/nimfishpkg/board.nim @@ -185,12 +185,6 @@ func countPieces*(self: Chessboard, kind: PieceKind, color: PieceColor): int {.i return self.position.pieces[color][kind][].countSquares() -func countPieces*(self: Chessboard, piece: Piece): int {.inline.} = - ## Returns the number of pieces on the board that - ## are of the same type and color as the given piece - return self.countPieces(piece.kind, piece.color) - - func getPiece*(self: Chessboard, square: Square): Piece {.inline.} = ## Gets the piece at the given square return self.grid[square] @@ -202,6 +196,61 @@ func getPiece*(self: Chessboard, square: string): Piece {.inline.} = return self.getPiece(square.toSquare()) +proc removePieceFromBitboard*(self: Chessboard, square: Square) = + ## Removes a piece at the given square in the chessboard from + ## its respective bitboard + let piece = self.getPiece(square) + self.position.pieces[piece.color][piece.kind][].clearBit(square) + + +proc addPieceToBitboard*(self: Chessboard, square: Square, piece: Piece) = + ## Adds the given piece at the given square in the chessboard to + ## its respective bitboard + self.position.pieces[piece.color][piece.kind][].setBit(square) + + +proc spawnPiece*(self: Chessboard, square: Square, piece: Piece) = + ## Internal helper to "spawn" a given piece at the given + ## square + when not defined(danger): + doAssert self.getPiece(square).kind == Empty + self.addPieceToBitboard(square, piece) + self.grid[square] = piece + + +proc removePiece*(self: Chessboard, square: Square) = + ## Removes a piece from the board, updating necessary + ## metadata + when not defined(danger): + let Piece = self.getPiece(square) + doAssert piece.kind != Empty and piece.color != None, self.toFEN() + self.removePieceFromBitboard(square) + self.grid[square] = nullPiece() + + +proc movePiece*(self: Chessboard, move: Move) = + ## Internal helper to move a piece from + ## its current square to a target square + let piece = self.getPiece(move.startSquare) + when not defined(danger): + let targetSquare = self.getPiece(move.targetSquare) + if targetSquare.color != None: + raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}") + # Update positional metadata + self.removePiece(move.startSquare) + self.spawnPiece(move.targetSquare, piece) + + +proc movePiece*(self: Chessboard, startSquare, targetSquare: Square) = + self.movePiece(createMove(startSquare, targetSquare)) + + +func countPieces*(self: Chessboard, piece: Piece): int {.inline.} = + ## Returns the number of pieces on the board that + ## are of the same type and color as the given piece + return self.countPieces(piece.kind, piece.color) + + func getOccupancyFor*(self: Chessboard, color: PieceColor): Bitboard = ## Get the occupancy bitboard for every piece of the given color result = Bitboard(0) diff --git a/Chess/nimfish/nimfishpkg/movegen.nim b/Chess/nimfish/nimfishpkg/movegen.nim index 6ddd322..5406002 100644 --- a/Chess/nimfish/nimfishpkg/movegen.nim +++ b/Chess/nimfish/nimfishpkg/movegen.nim @@ -31,11 +31,6 @@ import misc export bitboards, magics, pieces, moves, position, rays, misc, board -proc removePieceFromBitboard(self: Chessboard, square: Square) -proc addPieceToBitboard(self: Chessboard, square: Square, piece: Piece) -proc removePiece(self: Chessboard, square: Square) -proc spawnPiece(self: Chessboard, square: Square, piece: Piece) - proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) = let @@ -71,58 +66,62 @@ proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: B canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy and destinationMask - for pawn in singlePushes and not orthogonalPins: + for pawn in singlePushes: let pawnBB = pawn.toBitboard() if promotionRank.contains(pawn): for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn, promotion)) else: moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn)) - - for pawn in singlePushes and orthogonalPins: - let pawnBB = pawn.toBitboard() - if promotionRank.contains(pawn): - for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: - moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn, promotion)) - else: - moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn)) - - for pawn in canDoublePush and orthogonalPins: - moves.add(createMove(pawn.toBitboard().doubleBackwardRelativeTo(sideToMove), pawn, DoublePush)) - - for pawn in canDoublePush and not orthogonalPins: + + for pawn in canDoublePush: moves.add(createMove(pawn.toBitboard().doubleBackwardRelativeTo(sideToMove), pawn, DoublePush)) - let canCapture = pawns and not orthogonalPins - var - captureLeft = canCapture.forwardLeftRelativeTo(sideToMove) - captureRight = canCapture.forwardRightRelativeTo(sideToMove) - # If a piece is pinned on the right, it can only capture on the right and - # vice versa for the left - if (let capture = diagonalPins and captureLeft; capture) != 0: - captureRight = Bitboard(0) - captureLeft = capture - if (let capture = diagonalPins and captureRight; capture) != 0: - captureLeft = Bitboard(0) - captureRight = capture - # We mask off the non-enemy pieces and destination mask now because we need the unobstructed movement - # mask to check for pins correctly - captureLeft = captureLeft and enemyPieces and destinationMask - captureRight = captureRight and enemyPieces and destinationMask - for pawn in captureRight: + let + canCapture = pawns and not orthogonalPins + canCaptureLeftUnpinned = (canCapture and not diagonalPins).forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask + canCaptureRightUnpinned = (canCapture and not diagonalPins).forwardRightRelativeTo(sideToMove) and enemyPieces and destinationMask + + for pawn in canCaptureRightUnpinned: let pawnBB = pawn.toBitboard() if promotionRank.contains(pawn): for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture, promotion)) else: moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture)) - for pawn in captureLeft: + + for pawn in canCaptureLeftUnpinned: let pawnBB = pawn.toBitboard() if promotionRank.contains(pawn): for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture, promotion)) else: moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture)) + + # Special cases for pawns pinned diagonally that can capture their pinners + + let + canCaptureLeft = canCapture.forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask + canCaptureRight = canCapture.forwardRightRelativeTo(sideToMove) and enemyPieces and destinationMask + leftPinnedCanCapture = (canCaptureLeft and diagonalPins) and not canCaptureLeftUnpinned + rightPinnedCanCapture = ((canCaptureRight and diagonalPins) and not canCaptureRightUnpinned) and not canCaptureRightUnpinned + + for pawn in leftPinnedCanCapture: + let pawnBB = pawn.toBitboard() + if promotionRank.contains(pawn): + for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: + moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture, promotion)) + else: + moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture)) + + for pawn in rightPinnedCanCapture: + let pawnBB = pawn.toBitboard() + if promotionRank.contains(pawn): + for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: + moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture, promotion)) + else: + moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture)) + # En passant captures var epBitboard = if epTarget != nullSquare(): epTarget.toBitboard() else: Bitboard(0) if epBitboard != 0: @@ -150,7 +149,7 @@ proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: B if not self.isOccupancyAttacked(friendlyKing, newOccupancy): # En passant does not create a check on the king: all good moves.add(createMove(friendlyPawn, epBitboard, EnPassant)) - self.addPieceToBitboard(epPawnSquare, Piece(kind: Pawn, color: nonSideToMove)) + self.spawnPiece(epPawnSquare, epPiece) if epRight != 0: # Note that this isn't going to be the same pawn from the previous if block! let @@ -162,7 +161,7 @@ proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: B if not self.isOccupancyAttacked(friendlyKing, newOccupancy): # En passant does not create a check on the king: all good moves.add(createMove(friendlyPawn, epBitboard, EnPassant)) - self.addPieceToBitboard(epPawnSquare, Piece(kind: Pawn, color: nonSideToMove)) + self.spawnPiece(epPawnSquare, epPiece) proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) = @@ -321,56 +320,6 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) = # Queens are just handled rooks + bishops - -proc removePieceFromBitboard(self: Chessboard, square: Square) = - ## Removes a piece at the given square in the chessboard from - ## its respective bitboard - let piece = self.getPiece(square) - self.position.pieces[piece.color][piece.kind][].clearBit(square) - - -proc addPieceToBitboard(self: Chessboard, square: Square, piece: Piece) = - ## Adds the given piece at the given square in the chessboard to - ## its respective bitboard - self.position.pieces[piece.color][piece.kind][].setBit(square) - - -proc spawnPiece(self: Chessboard, square: Square, piece: Piece) = - ## Internal helper to "spawn" a given piece at the given - ## square - when not defined(danger): - doAssert self.getPiece(square).kind == Empty - self.addPieceToBitboard(square, piece) - self.grid[square] = piece - - -proc removePiece(self: Chessboard, square: Square) = - ## Removes a piece from the board, updating necessary - ## metadata - when not defined(danger): - let Piece = self.getPiece(square) - doAssert piece.kind != Empty and piece.color != None, self.toFEN() - self.removePieceFromBitboard(square) - self.grid[square] = nullPiece() - - -proc movePiece(self: Chessboard, move: Move) = - ## Internal helper to move a piece from - ## its current square to a target square - let piece = self.grid[move.startSquare] - when not defined(danger): - let targetSquare = self.getPiece(move.targetSquare) - if targetSquare.color != None: - raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}") - # Update positional metadata - self.removePiece(move.startSquare) - self.spawnPiece(move.targetSquare, piece) - - -proc movePiece(self: Chessboard, startSquare, targetSquare: Square) = - self.movePiece(createMove(startSquare, targetSquare)) - - proc doMove*(self: Chessboard, move: Move) = ## Internal function called by makeMove after ## performing legality checks. Can be used in @@ -381,7 +330,7 @@ proc doMove*(self: Chessboard, move: Move) = self.positions.add(self.position) # Final checks - let piece = self.grid[move.startSquare] + let piece = self.getPiece(move.startSquare) when not defined(danger): doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}" @@ -424,10 +373,10 @@ proc doMove*(self: Chessboard, move: Move) = if move.isCastling(): # Move the rook where it belongs if move.targetSquare == piece.kingSideCastling(): - let rook = self.grid[piece.color.kingSideRook()] + let rook = self.getPiece(piece.color.kingSideRook()) self.movePiece(piece.color.kingSideRook(), rook.kingSideCastling()) if move.targetSquare == piece.queenSideCastling(): - let rook = self.grid[piece.color.queenSideRook()] + let rook = self.getPiece(piece.color.queenSideRook()) self.movePiece(piece.color.queenSideRook(), rook.queenSideCastling()) if piece.kind == Rook: