Work on magic bitboard generation

This commit is contained in:
Mattia Giambirtone 2024-04-18 21:17:29 +02:00
parent a07e9cc475
commit 244ad1725a
3 changed files with 176 additions and 25 deletions

View File

@ -25,12 +25,13 @@ type
BackwardRight
# Overloaded operators and functions for our bitboard type
func `shl`*(a: Bitboard, x: Positive): Bitboard = Bitboard(a.uint64 shl x)
func `shr`*(a: Bitboard, x: Positive): Bitboard = Bitboard(a.uint64 shr x)
func `shl`*(a: Bitboard, x: Natural): Bitboard = Bitboard(a.uint64 shl x)
func `shr`*(a: Bitboard, x: Natural): Bitboard = Bitboard(a.uint64 shr x)
func `and`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 and b.uint64)
func `or`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 or b.uint64)
func `not`*(a: Bitboard): Bitboard = Bitboard(not a.uint64)
func `shr`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 and b.uint64)
func `xor`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 xor b.uint64)
func `+`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 + b.uint64)
func `-`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 - b.uint64)
func `div`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 div b.uint64)
@ -39,6 +40,7 @@ func `+`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 + b)
func `-`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 - b.uint64)
func `div`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 div b)
func `*`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 * b)
func `*`*(a: SomeUnsignedInt, b: Bitboard): Bitboard = Bitboard(a.uint64 * b)
func `==`*(a, b: Bitboard): bool {.inline.} = a.uint64 == b.uint64
func `==`*(a: Bitboard, b: SomeInteger): bool {.inline.} = a.uint64 == b.uint64
func `!=`*(a, b: Bitboard): bool {.inline.} = a.uint64 != b.uint64
@ -77,6 +79,19 @@ iterator items*(self: Bitboard): Square =
bits = bits and bits - 1
iterator subsets*(self: Bitboard): Bitboard =
## Iterates over all the subsets of the given
## bitboard using the Carry-Rippler trick
# Thanks analog-hors :D
var subset = Bitboard(0)
while true:
subset = (subset - self) and self
if subset == 0:
break
yield subset
iterator pairs*(self: Bitboard): tuple[i: int, sq: Square] =
var i = 0
for item in self:
@ -211,26 +226,6 @@ func shortKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard
# 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
@ -271,8 +266,6 @@ func computeKnightBitboards: array[64, Bitboard] =
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

158
src/Chess/magics.nim Normal file
View File

@ -0,0 +1,158 @@
## Low-level magic bitboard stuff
# Stolen from this amazing article: https://analog-hors.github.io/site/magic-bitboards/
import bitboards
import pieces
import std/random
export pieces
export bitboards
randomize()
type
MagicEntry = object
## A magic bitboard entry
mask: Bitboard
value: uint64
indexBits: uint8
proc generateRookBlockers: array[64, Bitboard] {.compileTime.} =
## Generates all blocker masks for rooks
for rank in 0..7:
for file in 0..7:
let
square = makeSquare(rank, file)
i = square.int
bitboard = square.toBitboard()
var
current = bitboard
last = makeSquare(rank, 7).toBitboard()
while true:
current = current.rightRelativeTo(White)
if current == last or current == 0:
break
result[i] = result[i] or current
current = bitboard
last = makeSquare(rank, 0).toBitboard()
while true:
current = current.leftRelativeTo(White)
if current == last or current == 0:
break
result[i] = result[i] or current
current = bitboard
last = makeSquare(0, file).toBitboard()
while true:
current = current.forwardRelativeTo(White)
if current == last or current == 0:
break
result[i] = result[i] or current
current = bitboard
last = makeSquare(7, file).toBitboard()
while true:
current = current.backwardRelativeTo(White)
if current == last or current == 0:
break
result[i] = result[i] or current
func generateBishopBlockers: array[64, Bitboard] {.compileTime.} =
for rank in 0..7:
for file in 0..7:
# Generate all possible movement masks
let
square = makeSquare(rank, file)
i = square.int
bitboard = square.toBitboard()
var
current = bitboard
while true:
current = current.backwardRightRelativeTo(White)
if current == 0:
break
result[i] = result[i] or current
current = bitboard
while true:
current = current.backwardLeftRelativeTo(White)
if current == 0:
break
result[i] = result[i] or current
current = bitboard
while true:
current = current.forwardLeftRelativeTo(White)
if current == 0:
break
result[i] = result[i] or current
current = bitboard
while true:
current = current.forwardRightRelativeTo(White)
if current == 0:
break
result[i] = result[i] or current
# Mask off the edges
result[i] = result[i] and not getFileMask(0)
result[i] = result[i] and not getFileMask(7)
result[i] = result[i] and not getRankMask(0)
result[i] = result[i] and not getRankMask(7)
func getIndex(magic: MagicEntry, blockers: Bitboard): uint {.inline.} =
## Computes an index into the magic bitboard table using
## the given magic entry and the blockers bitboard
let
blockers = blockers and magic.mask
hash = blockers * magic.value
index = hash shl (64 - magic.indexBits)
return index.uint
# Magic number tables and their corresponding moves
var
ROOK_MAGICS: seq[MagicEntry]
ROOK_MOVES: array[64, seq[Bitboard]]
BISHOP_MAGICS: seq[MagicEntry]
BISHOP_MOVES: array[64, seq[Bitboard]]
proc getRookMoves(square: Square, blockers: Bitboard): Bitboard =
## Returns the move bitboard for the rook at the given
## square with the given blockers bitboard
let
magic = ROOK_MAGICS[square.uint]
moves = ROOK_MOVES[square.uint]
return moves[getIndex(magic, blockers)]
proc getBishopMoves(square: Square, blockers: Bitboard): Bitboard =
## Returns the move bitboard for the bishop at the given
## square with the given blockers bitboard
let
magic = BISHOP_MAGICS[square.uint]
moves = BISHOP_MOVES[square.uint]
return moves[getIndex(magic, blockers)]
# Precomputed blocker masks. Only pieces on these bitboards
# are actually able to block the movement of a sliding piece,
# regardless of color
const
ROOK_BLOCKERS* = generateRookBlockers()
BISHOP_BLOCKERS* = generateBishopBlockers()
# func findMagic(slider: PieceKind, square: Square, indexBits: uint8): tuple[magic: MagicEntry, moves: seq[Bitboard]] =
# ## Given a slider piece, its starting square and the number of desired
# ## index bits, find a magic number that perfectly maps all the possible
# ## sliding moves for that piece at that square into an appropriately sized
# ## perfect hash table with at most 2^indexBits entries
# var mask: Bitboard
# case slider:
# of Rook:
# mask = ROOK_BLOCKERS[square.uint]

View File

@ -48,7 +48,7 @@ func rowFromSquare*(square: Square): int8 = square.int8 div 8 + 1
func makeSquare*(rank, file: SomeInteger): Square = Square(rank * 8 + file)
func makeSquare*(rank, file: SomeInteger): Square = Square((rank * 8) + file)
proc toSquare*(s: string): Square {.discardable.} =