Final bug fixes. Test suite is passing

This commit is contained in:
Mattia Giambirtone 2024-04-23 18:57:38 +02:00
parent e62c78e4cc
commit d03b2c2fbf
3 changed files with 98 additions and 99 deletions

View File

@ -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

View File

@ -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)

View File

@ -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: