From 244ad1725af17ebff7187b31435c22a532325679 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Thu, 18 Apr 2024 21:17:29 +0200 Subject: [PATCH] Work on magic bitboard generation --- src/Chess/bitboards.nim | 41 +++++------ src/Chess/magics.nim | 158 ++++++++++++++++++++++++++++++++++++++++ src/Chess/pieces.nim | 2 +- 3 files changed, 176 insertions(+), 25 deletions(-) create mode 100644 src/Chess/magics.nim diff --git a/src/Chess/bitboards.nim b/src/Chess/bitboards.nim index 5c48b74..cb26b53 100644 --- a/src/Chess/bitboards.nim +++ b/src/Chess/bitboards.nim @@ -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 diff --git a/src/Chess/magics.nim b/src/Chess/magics.nim new file mode 100644 index 0000000..8b7748a --- /dev/null +++ b/src/Chess/magics.nim @@ -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] diff --git a/src/Chess/pieces.nim b/src/Chess/pieces.nim index 1d404cb..91cd47a 100644 --- a/src/Chess/pieces.nim +++ b/src/Chess/pieces.nim @@ -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.} =