Added knight movegen. Updated attack tracking. Fix bugs

This commit is contained in:
Mattia Giambirtone 2024-04-17 20:27:39 +02:00
parent 3bb2cc7c66
commit a07e9cc475
2 changed files with 142 additions and 48 deletions

View File

@ -112,30 +112,6 @@ func pretty*(self: Bitboard): string =
func `$`*(self: Bitboard): string = self.pretty()
func computeDiagonalBitboards: array[14, Bitboard] {.compileTime.} =
## Precomputes all the bitboards for diagonals
result[0] = Bitboard(0x8040201008040201'u64)
var
col = 1
i = 0
# Left to right
while col < 8:
result[col] = Bitboard(0x8040201008040201'u64) shl (8 * col)
inc(col)
inc(i)
result[i] = Bitboard(0x102040810204080'u64)
inc(i)
col = 1
# Right to left
while col < 7:
result[i] = Bitboard(0x102040810204080'u64) shr (8 * col)
inc(i)
inc(col)
const diagonalBitboards = computeDiagonalBitboards()
func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Direction): Bitboard =
## Get a bitmask relative to the given bitboard
## for the given direction for a piece of the
@ -185,6 +161,8 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
func getLastRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(0) else: getRankMask(7))
func getFirstRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(7) else: getRankMask(0))
func getLeftmostFile*(color: PieceColor): Bitboard = (if color == White: getFileMask(0) else: getFileMask(7))
func getRightmostFile*(color: PieceColor): Bitboard = (if color == White: getFileMask(7) else: getFileMask(0))
func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard =
@ -199,27 +177,104 @@ func doubleForwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self
func backwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Backward)
func doubleBackwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).backwardRelativeTo(side)
func leftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Left) and not getFileMask(0)
func rightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Right) and not getFileMask(7)
func leftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Left) and not getRightmostFile(side)
func rightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Right) and not getLeftmostFile(side)
# We mask off the first and last ranks for
# left and right movements respectively to
# avoid weird wraparounds
# We mask off the opposide files to make sure there are
# no weird wraparounds when moving diagonally
func forwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, ForwardRight) and not getFileMask(7)
getDirectionMask(self, side, ForwardRight) and not getLeftmostFile(side)
func forwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, ForwardLeft) and not getFileMask(0)
getDirectionMask(self, side, ForwardLeft) and not getRightmostFile(side)
func backwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, BackwardRight)
getDirectionMask(self, side, BackwardRight) and not getLeftmostFile(side)
func backwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, BackwardLeft)
getDirectionMask(self, side, BackwardLeft) and not getRightmostFile(side)
# func forwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Forward)
func longKnightUpLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleForwardRelativeTo(side).leftRelativeTo(side)
func longKnightUpRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleForwardRelativeTo(side).rightRelativeTo(side)
func longKnightDownLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleBackwardRelativeTo(side).leftRelativeTo(side)
func longKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleBackwardRelativeTo(side).rightRelativeTo(side)
func shortKnightUpLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.forwardRelativeTo(side).leftRelativeTo(side).leftRelativeTo(side)
func shortKnightUpRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.forwardRelativeTo(side).rightRelativeTo(side).rightRelativeTo(side)
func shortKnightDownLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).leftRelativeTo(side).leftRelativeTo(side)
func shortKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).rightRelativeTo(side).rightRelativeTo(side)
# We precompute as much stuff as possible: lookup tables are fast!
func computeDiagonalBitboards: array[14, Bitboard] {.compileTime.} =
## Precomputes all the bitboards for diagonals
result[0] = Bitboard(0x8040201008040201'u64)
var
col = 1
i = 0
# Left to right
while col < 8:
result[col] = Bitboard(0x8040201008040201'u64) shl (8 * col)
inc(col)
inc(i)
result[i] = Bitboard(0x102040810204080'u64)
inc(i)
col = 1
# Right to left
while col < 7:
result[i] = Bitboard(0x102040810204080'u64) shr (8 * col)
inc(i)
inc(col)
func computeKingBitboards: array[64, Bitboard] =
## Precomputes all the movement bitboards for the king
for i in 0'u64..63:
let king = i.toBitboard()
# It doesn't really matter which side we generate
# the move for, they're identical for both
var movements = king.forwardRelativeTo(White)
movements = movements or king.forwardLeftRelativeTo(White)
movements = movements or king.leftRelativeTo(White)
movements = movements or king.rightRelativeTo(White)
movements = movements or king.backwardRelativeTo(White)
movements = movements or king.forwardLeftRelativeTo(White)
movements = movements or king.backwardRightRelativeTo(White)
movements = movements or king.backwardLeftRelativeTo(White)
# We don't *need* to mask the king off: the engine already masks off
# the board's occupancy when generating moves, but it may be useful for
# other parts of the movegen for this stuff not to say "the king can just
# stay still", so we do it anyway
movements = movements and not king
result[i] = movements
func computeKnightBitboards: array[64, Bitboard] =
## Precomputes all the movement bitboards for knights
for i in 0'u64..63:
let knight = i.toBitboard()
# It doesn't really matter which side we generate
# the move for, they're identical for both
var movements = knight.longKnightDownLeftRelativeTo(White)
movements = movements or knight.longKnightDownRightRelativeTo(White)
movements = movements or knight.longKnightUpLeftRelativeTo(White)
movements = movements or knight.longKnightUpRightRelativeTo(White)
movements = movements or knight.shortKnightDownLeftRelativeTo(White)
movements = movements or knight.shortKnightDownRightRelativeTo(White)
movements = movements or knight.shortKnightUpLeftRelativeTo(White)
movements = movements or knight.shortKnightUpRightRelativeTo(White)
result[i] = movements
# Precomputing stuff is *very* helpful for chess, it turns out
const DIAGONAL_BITBOARDS* = computeDiagonalBitboards()
# For some reason nim freaks out if I try to call computeKingBitboards()
# at compile-time. ¯\_(ツ)_/¯
let
KING_BITBOARDS* = computeKingBitboards()
KNIGHT_BITBOARDS* = computeKnightBitboards()

View File

@ -539,6 +539,29 @@ proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bit
return pawns and (bottomLeft or bottomRight)
proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Returns the attack bitboard for the given square from
## the king of the given side
result = Bitboard(0)
let
sq = square.toBitboard()
king = self.getBitboard(King, attacker)
if (sq and KING_BITBOARDS[square.uint64]) != 0:
result = result or sq
proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Returns the attack bitboard for the given square from
## the knights of the given side
let
sq = square.toBitboard()
knights = self.getBitboard(Knight, attacker)
result = Bitboard(0)
for knight in knights:
if (sq and KNIGHT_BITBOARDS[knight.uint64]) != 0:
result = result or knight.toBitboard()
proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Computes the attack bitboard for the given square from
## the given side
@ -546,8 +569,17 @@ proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitbo
let
squareBitboard = square.toBitboard()
result = result or self.getPawnAttacks(square, attacker)
result = result or self.getKingAttacks(square, attacker)
result = result or self.getKnightAttacks(square, attacker)
proc getCapturablePieces(self: ChessBoard, side: PieceColor): Bitboard {.inline.} =
## Returns the set of pieces of the given color that can
## be captured
# Just a handy helper to filter out the king and avoid code duplication
return self.getOccupancyFor(side) and not self.getBitboard(King, side)
proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
## Helper of generatePawnMoves for generating all non-capture
@ -576,7 +608,7 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
nonSideToMove = sideToMove.opposite()
pawns = self.getBitboard(Pawn, sideToMove)
# We can only capture enemy pieces (except the king)
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
enemyPieces = self.getCapturablePieces(nonSideToMove)
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
epTarget = self.getEnPassantTarget()
@ -625,26 +657,33 @@ proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
let
sideToMove = self.getSideToMove()
king = self.getBitboard(King, sideToMove)
moveIdx = king.toSquare().uint64
allowedSquares = not self.getOccupancy()
nonSideToMove = sideToMove.opposite()
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
var movements = king.forwardRelativeTo(sideToMove)
movements = movements or king.rightRelativeTo(sideToMove)
movements = movements or king.backwardRelativeTo(sideToMove)
movements = movements or king.leftRelativeTo(sideToMove)
movements = movements or king.forwardRightRelativeTo(sideToMove)
movements = movements or king.forwardLeftRelativeTo(sideToMove)
enemyPieces = self.getCapturablePieces(nonSideToMove)
# Regular moves
for square in movements and allowedSquares:
for square in KING_BITBOARDS[moveIdx] and allowedSquares:
moves.add(createMove(king, square))
# Captures
for square in movements and enemyPieces:
for square in KING_BITBOARDS[moveIdx] and enemyPieces:
moves.add(createMove(king, square, Capture))
proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
## Generates all the legal knight moves for the side to move
let
sideToMove = self.getSideToMove()
knights = self.getBitboard(Knight, sideToMove)
allowedSquares = not self.getOccupancy()
nonSideToMove = sideToMove.opposite()
enemyPieces = self.getCapturablePieces(nonSideToMove)
for square in knights:
# Regular moves
for target in KNIGHT_BITBOARDS[square.uint64] and allowedSquares:
moves.add(createMove(square, target))
# Captures
for target in KNIGHT_BITBOARDS[square.uint64] and enemyPieces:
moves.add(createMove(square, target, Capture))
proc checkInsufficientMaterialPieceCount(self: ChessBoard, color: PieceColor): bool =
@ -718,6 +757,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
# TODO: Check for repetitions (requires zobrist hashing + table)
self.generatePawnMoves(moves)
self.generateKingMoves(moves)
self.generateKnightMoves(moves)
# TODO: all pieces
@ -1755,13 +1795,12 @@ when isMainModule:
testPieceBitboard(blackQueens, blackQueenSquares)
testPieceBitboard(blackKing, blackKingSquares)
b = newChessboardFromFEN("B7/P1P1P1P1/1P1P1P1P/7k/8/8/8/3K4 w - - 0 1")
b.doMove(createMove("d1", "e1"))
var m = MoveList()
b.generateMoves(m)
echo &"Legal moves for {b.getSideToMove()} at {b.toFEN()}: "
echo &"There are {len(m)} legal moves for {b.getSideToMove()} at {b.toFEN()}: "
for move in m:
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
echo b.pretty()
echo b.getAttacksTo("f3".toSquare(), White)
# setControlCHook(proc () {.noconv.} = quit(0))
# quit(main())