Speed up move generation and position updates (bench 5725647)
Co-authored-by: GPT 5.6 Sol <codex@openai.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
|
||||
## Implements low-level bit operations
|
||||
|
||||
import std/[sugar, bitops, strutils]
|
||||
import std/[bitops, strutils]
|
||||
|
||||
import heimdall/[moves, pieces]
|
||||
|
||||
@@ -147,31 +147,30 @@ func pretty*(self: Bitboard): string =
|
||||
|
||||
func `$`*(self: Bitboard): string {.inline.} = self.pretty()
|
||||
|
||||
func generateShifters: array[White..Black, array[Direction, (Bitboard {.noSideEffect.} -> Bitboard)]] {.compileTime.} =
|
||||
result[White][Forward] = (x: Bitboard) => x shr 8
|
||||
result[White][Backward] = (x: Bitboard) => x shl 8
|
||||
result[White][Left] = (x: Bitboard) => x shr 1
|
||||
result[White][Right] = (x: Bitboard) => x shl 1
|
||||
result[White][ForwardRight] = (x: Bitboard) => x shr 7
|
||||
result[White][ForwardLeft] = (x: Bitboard) => x shr 9
|
||||
result[White][BackwardRight] = (x: Bitboard) => x shl 9
|
||||
result[White][BackwardLeft] = (x: Bitboard) => x shl 7
|
||||
|
||||
result[Black][Backward] = (x: Bitboard) => x shr 8
|
||||
result[Black][Forward] = (x: Bitboard) => x shl 8
|
||||
result[Black][Right] = (x: Bitboard) => x shr 1
|
||||
result[Black][Left] = (x: Bitboard) => x shl 1
|
||||
result[Black][BackwardLeft] = (x: Bitboard) => x shr 7
|
||||
result[Black][BackwardRight] = (x: Bitboard) => x shr 9
|
||||
result[Black][ForwardLeft] = (x: Bitboard) => x shl 9
|
||||
result[Black][ForwardRight] = (x: Bitboard) => x shl 7
|
||||
|
||||
|
||||
const shifters: array[White..Black, array[Direction, (Bitboard) {.noSideEffect.} -> Bitboard]] = generateShifters()
|
||||
|
||||
|
||||
func directionMask*(bitboard: Bitboard, color: PieceColor, direction: Direction): Bitboard {.inline.} =
|
||||
shifters[color][direction](bitboard)
|
||||
case color:
|
||||
of White:
|
||||
case direction:
|
||||
of Forward: result = bitboard shr 8
|
||||
of Backward: result = bitboard shl 8
|
||||
of Left: result = bitboard shr 1
|
||||
of Right: result = bitboard shl 1
|
||||
of ForwardRight: result = bitboard shr 7
|
||||
of ForwardLeft: result = bitboard shr 9
|
||||
of BackwardRight: result = bitboard shl 9
|
||||
of BackwardLeft: result = bitboard shl 7
|
||||
of Black:
|
||||
case direction:
|
||||
of Forward: result = bitboard shl 8
|
||||
of Backward: result = bitboard shr 8
|
||||
of Left: result = bitboard shl 1
|
||||
of Right: result = bitboard shr 1
|
||||
of ForwardRight: result = bitboard shl 7
|
||||
of ForwardLeft: result = bitboard shl 9
|
||||
of BackwardRight: result = bitboard shr 9
|
||||
of BackwardLeft: result = bitboard shr 7
|
||||
of None:
|
||||
result = Bitboard(0)
|
||||
|
||||
func directionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard {.inline.} =
|
||||
directionMask(square.toBitboard(), color, direction)
|
||||
|
||||
@@ -38,6 +38,9 @@ proc generatePawnMoves(self: var Position, moves: var MoveList, destinationMask:
|
||||
promotionRank = sideToMove.eighthRank()
|
||||
startingRank = sideToMove.secondRank()
|
||||
friendlyKing = self.kingSquare(sideToMove)
|
||||
backwardOffset = if sideToMove == White: 8 else: -8
|
||||
backwardLeftOffset = if sideToMove == White: 7 else: -7
|
||||
backwardRightOffset = if sideToMove == White: 9 else: -9
|
||||
|
||||
# If a pawn is pinned diagonally, it cannot push forward
|
||||
let
|
||||
@@ -55,14 +58,14 @@ proc generatePawnMoves(self: var Position, moves: var MoveList, destinationMask:
|
||||
canDoublePush = canDoublePush.forward(sideToMove) and not occupancy and destinationMask
|
||||
|
||||
for pawn in singlePushes and not promotionRank:
|
||||
moves.add(createMove(pawn.toBitboard().backward(sideToMove), pawn))
|
||||
moves.add(createMove(Square(pawn.int + backwardOffset), pawn))
|
||||
|
||||
for pawn in singlePushes and promotionRank:
|
||||
for promotion in [PromotionBishop, PromotionKnight, PromotionRook, PromotionQueen]:
|
||||
moves.add(createMove(pawn.toBitboard().backward(sideToMove), pawn, promotion))
|
||||
moves.add(createMove(Square(pawn.int + backwardOffset), pawn, promotion))
|
||||
|
||||
for pawn in canDoublePush:
|
||||
moves.add(createMove(pawn.toBitboard().doubleBackward(sideToMove), pawn, DoublePush))
|
||||
moves.add(createMove(Square(pawn.int + backwardOffset * 2), pawn, DoublePush))
|
||||
|
||||
let
|
||||
canCapture = pawns and not orthogonalPins
|
||||
@@ -70,18 +73,18 @@ proc generatePawnMoves(self: var Position, moves: var MoveList, destinationMask:
|
||||
canCaptureRightUnpinned = (canCapture and not diagonalPins).forwardRight(sideToMove) and enemyPieces and destinationMask
|
||||
|
||||
for pawn in canCaptureRightUnpinned and not promotionRank:
|
||||
moves.add(createMove(pawn.toBitboard().backwardLeft(sideToMove), pawn, Capture))
|
||||
moves.add(createMove(Square(pawn.int + backwardLeftOffset), pawn, Capture))
|
||||
|
||||
for pawn in canCaptureRightUnpinned and promotionRank:
|
||||
for promotion in [CapturePromotionBishop, CapturePromotionKnight, CapturePromotionRook, CapturePromotionQueen]:
|
||||
moves.add(createMove(pawn.toBitboard().backwardLeft(sideToMove), pawn, promotion))
|
||||
moves.add(createMove(Square(pawn.int + backwardLeftOffset), pawn, promotion))
|
||||
|
||||
for pawn in canCaptureLeftUnpinned and not promotionRank:
|
||||
moves.add(createMove(pawn.toBitboard().backwardRight(sideToMove), pawn, Capture))
|
||||
moves.add(createMove(Square(pawn.int + backwardRightOffset), pawn, Capture))
|
||||
|
||||
for pawn in canCaptureLeftUnpinned and promotionRank:
|
||||
for promotion in [CapturePromotionBishop, CapturePromotionKnight, CapturePromotionRook, CapturePromotionQueen]:
|
||||
moves.add(createMove(pawn.toBitboard().backwardRight(sideToMove), pawn, promotion))
|
||||
moves.add(createMove(Square(pawn.int + backwardRightOffset), pawn, promotion))
|
||||
|
||||
# Special cases for pawns pinned diagonally that can capture their pinners
|
||||
|
||||
@@ -89,21 +92,21 @@ proc generatePawnMoves(self: var Position, moves: var MoveList, destinationMask:
|
||||
canCaptureLeft = canCapture.forwardLeft(sideToMove) and enemyPieces and destinationMask
|
||||
canCaptureRight = canCapture.forwardRight(sideToMove) and enemyPieces and destinationMask
|
||||
leftPinnedCanCapture = (canCaptureLeft and diagonalPins) and not canCaptureLeftUnpinned
|
||||
rightPinnedCanCapture = ((canCaptureRight and diagonalPins) and not canCaptureRightUnpinned) and not canCaptureRightUnpinned
|
||||
rightPinnedCanCapture = (canCaptureRight and diagonalPins) and not canCaptureRightUnpinned
|
||||
|
||||
for pawn in leftPinnedCanCapture and not promotionRank:
|
||||
moves.add(createMove(pawn.toBitboard().backwardRight(sideToMove), pawn, Capture))
|
||||
moves.add(createMove(Square(pawn.int + backwardRightOffset), pawn, Capture))
|
||||
|
||||
for pawn in leftPinnedCanCapture and promotionRank:
|
||||
for promotion in [CapturePromotionBishop, CapturePromotionKnight, CapturePromotionRook, CapturePromotionQueen]:
|
||||
moves.add(createMove(pawn.toBitboard().backwardRight(sideToMove), pawn, promotion))
|
||||
moves.add(createMove(Square(pawn.int + backwardRightOffset), pawn, promotion))
|
||||
|
||||
for pawn in rightPinnedCanCapture and not promotionRank:
|
||||
moves.add(createMove(pawn.toBitboard().backwardLeft(sideToMove), pawn, Capture))
|
||||
moves.add(createMove(Square(pawn.int + backwardLeftOffset), pawn, Capture))
|
||||
|
||||
for pawn in rightPinnedCanCapture and promotionRank:
|
||||
for promotion in [CapturePromotionBishop, CapturePromotionKnight, CapturePromotionRook, CapturePromotionQueen]:
|
||||
moves.add(createMove(pawn.toBitboard().backwardLeft(sideToMove), pawn, promotion))
|
||||
moves.add(createMove(Square(pawn.int + backwardLeftOffset), pawn, promotion))
|
||||
|
||||
let epLegality = self.isEPLegal(friendlyKing, epTarget, occupancy, pawns, sideToMove)
|
||||
if epLegality.left != nullSquare():
|
||||
@@ -200,8 +203,11 @@ proc generateKnightMoves(self: Position, moves: var MoveList, destinationMask: B
|
||||
|
||||
|
||||
proc generateCastling(self: Position, moves: var MoveList) =
|
||||
let sideToMove = self.sideToMove
|
||||
let availability = self.castlingAvailability[sideToMove]
|
||||
if availability.king == nullSquare() and availability.queen == nullSquare():
|
||||
return
|
||||
let
|
||||
sideToMove = self.sideToMove
|
||||
castlingRights = self.canCastle()
|
||||
kingSquare = self.kingSquare(sideToMove)
|
||||
if castlingRights.king != nullSquare():
|
||||
@@ -279,7 +285,12 @@ proc doMove*(self: Chessboard, move: Move) {.gcsafe.} =
|
||||
kingSq = self.position.kingSquare(sideToMove)
|
||||
king = self.on(kingSq)
|
||||
|
||||
self.positions.add(self.position.clone())
|
||||
# Position is a POD value. Grow the stack first and copy the previous state
|
||||
# directly into its final slot, avoiding clone()'s zeroed temporary and the
|
||||
# subsequent second copy performed by seq.add.
|
||||
let previousPosition = self.positions.high()
|
||||
self.positions.setLen(self.positions.len() + 1)
|
||||
copyMem(addr self.positions[^1], addr self.positions[previousPosition], sizeof(Position))
|
||||
|
||||
if piece.kind == Pawn or move.isCapture():
|
||||
self.positions[^1].halfMoveClock = 0
|
||||
@@ -396,7 +407,9 @@ proc makeNullMove*(self: Chessboard) {.inline.} =
|
||||
## to the opponent without making a move. This
|
||||
## is obviously illegal and only to be used during
|
||||
## search. The move can be undone via unmakeMove
|
||||
self.positions.add(self.position.clone())
|
||||
let previousPosition = self.positions.high()
|
||||
self.positions.setLen(self.positions.len() + 1)
|
||||
copyMem(addr self.positions[^1], addr self.positions[previousPosition], sizeof(Position))
|
||||
self.positions[^1].sideToMove = self.position.sideToMove.opposite()
|
||||
let previousEPTarget = self.positions[^2].enPassantSquare
|
||||
if previousEPTarget != nullSquare():
|
||||
|
||||
@@ -136,8 +136,8 @@ proc slidingAttackers*(self: Position, square: Square, attackingSide: PieceColor
|
||||
rooks = self.pieces(Rook, attackingSide) or queens
|
||||
bishops = self.pieces(Bishop, attackingSide) or queens
|
||||
|
||||
result = bishopMoves(square, occupancy) and (bishops or queens)
|
||||
result = result or rookMoves(square, occupancy) and (rooks or queens)
|
||||
result = bishopMoves(square, occupancy) and bishops
|
||||
result = result or rookMoves(square, occupancy) and rooks
|
||||
|
||||
|
||||
proc attackers*(self: Position, square: Square, attackingSide: PieceColor): Bitboard {.inline.} =
|
||||
@@ -438,25 +438,20 @@ proc updateChecksAndPins*(self: var Position) {.inline.} =
|
||||
if (pinningRay and friendlyPieces).count() == 1:
|
||||
self.orthogonalPins = self.orthogonalPins or pinningRay
|
||||
|
||||
self.threats = Bitboard(0)
|
||||
let occupancy = friendlyPieces or enemyPieces
|
||||
for square in enemyPieces:
|
||||
let piece = self.on(square)
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
self.threats = self.threats or pawnAttacks(nonSideToMove, square)
|
||||
of Rook:
|
||||
self.threats = self.threats or rookMoves(square, occupancy)
|
||||
of Bishop:
|
||||
self.threats = self.threats or bishopMoves(square, occupancy)
|
||||
of Knight:
|
||||
self.threats = self.threats or knightMoves(square)
|
||||
of King:
|
||||
self.threats = self.threats or kingMoves(square)
|
||||
of Queen:
|
||||
self.threats = self.threats or (bishopMoves(square, occupancy) or rookMoves(square, occupancy))
|
||||
else:
|
||||
discard
|
||||
let enemyPawns = self.pieces(Pawn, nonSideToMove)
|
||||
# Pawn attacks can be generated for the whole set at once. Iterating them
|
||||
# individually adds mailbox lookups and a case dispatch for the most common
|
||||
# enemy piece without changing the resulting attack map.
|
||||
self.threats = enemyPawns.forwardLeft(nonSideToMove) or enemyPawns.forwardRight(nonSideToMove)
|
||||
let enemyQueens = self.pieces(Queen, nonSideToMove)
|
||||
for square in self.pieces(Rook, nonSideToMove) or enemyQueens:
|
||||
self.threats = self.threats or rookMoves(square, occupancy)
|
||||
for square in self.pieces(Bishop, nonSideToMove) or enemyQueens:
|
||||
self.threats = self.threats or bishopMoves(square, occupancy)
|
||||
for square in self.pieces(Knight, nonSideToMove):
|
||||
self.threats = self.threats or knightMoves(square)
|
||||
self.threats = self.threats or kingMoves(self.kingSquare(nonSideToMove))
|
||||
|
||||
|
||||
proc hash*(self: var Position) =
|
||||
|
||||
Reference in New Issue
Block a user