Files
JoshHeaps.Net/native/chess_engine/src/bitboard.cpp
T
2026-06-08 13:58:22 -06:00

147 lines
4.4 KiB
C++

#include "bitboard.h"
#include <cstdlib>
namespace chess {
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
Bitboard KnightAttacks[SQUARE_NB];
Bitboard KingAttacks[SQUARE_NB];
Magic BishopMagics[SQUARE_NB];
Magic RookMagics[SQUARE_NB];
// Backing storage the magics index into (fancy-magic sizes).
static Bitboard RookTable[102400];
static Bitboard BishopTable[5248];
namespace {
int file_distance(Square a, Square b) {
return std::abs(int(file_of(a)) - int(file_of(b)));
}
// Slow, edge-aware ray attack used only to build the tables.
Bitboard sliding_attack(const int* dirs, Square sq, Bitboard occ) {
Bitboard attacks = 0;
for (int i = 0; i < 4; ++i) {
Square prev = sq;
int t = int(sq) + dirs[i];
while (t >= 0 && t < 64 && file_distance(Square(t), prev) <= 1) {
attacks |= square_bb(Square(t));
if (occ & square_bb(Square(t))) break;
prev = Square(t);
t += dirs[i];
}
}
return attacks;
}
// Relevant-occupancy mask: the ray squares excluding board edges.
Bitboard slider_mask(const int* dirs, Square sq) {
Bitboard edges = ((RANK_1_BB | RANK_8_BB) & ~rank_bb(rank_of(sq)))
| ((FILE_A_BB | FILE_H_BB) & ~file_bb(file_of(sq)));
return sliding_attack(dirs, sq, 0) & ~edges;
}
// Deterministic xorshift PRNG (fixed seed -> reproducible magics).
struct PRNG {
uint64_t s;
explicit PRNG(uint64_t seed) : s(seed) {}
uint64_t next() {
s ^= s >> 12; s ^= s << 25; s ^= s >> 27;
return s * 2685821657736338717ULL;
}
// Few set bits -> better magic candidates.
uint64_t sparse() { return next() & next() & next(); }
};
void init_magics(const int* dirs, Magic magics[], Bitboard table[]) {
PRNG rng(0x9E3779B97F4A7C15ull); // fixed seed -> reproducible magics
Bitboard occupancy[4096];
Bitboard reference[4096];
int epoch[4096] = {};
int currentEpoch = 0;
size_t offset = 0;
for (int sq = 0; sq < 64; ++sq) {
Magic& m = magics[sq];
m.mask = slider_mask(dirs, Square(sq));
m.shift = 64 - popcount(m.mask);
m.attacks = table + offset;
// Enumerate every subset of the mask (Carry-Rippler).
Bitboard b = 0;
int size = 0;
do {
occupancy[size] = b;
reference[size] = sliding_attack(dirs, Square(sq), b);
++size;
b = (b - m.mask) & m.mask;
} while (b);
// Search for a magic that maps subsets to indices collision-free
// (collisions are fine only when the attack set is identical).
for (;;) {
Bitboard magic;
do {
magic = rng.sparse();
} while (popcount((m.mask * magic) >> 56) < 6);
m.magic = magic;
++currentEpoch;
bool ok = true;
for (int i = 0; i < size; ++i) {
unsigned idx = m.index(occupancy[i]);
if (epoch[idx] < currentEpoch) {
epoch[idx] = currentEpoch;
m.attacks[idx] = reference[i];
} else if (m.attacks[idx] != reference[i]) {
ok = false;
break;
}
}
if (ok) break;
}
offset += size;
}
}
} // namespace
void init_bitboards() {
for (int s = 0; s < 64; ++s) {
Bitboard b = square_bb(Square(s));
PawnAttacks[WHITE][s] = ((b & ~FILE_H_BB) << 9) | ((b & ~FILE_A_BB) << 7);
PawnAttacks[BLACK][s] = ((b & ~FILE_A_BB) >> 9) | ((b & ~FILE_H_BB) >> 7);
const int knightDirs[8] = { 17, 15, 10, 6, -6, -10, -15, -17 };
Bitboard kn = 0;
for (int d : knightDirs) {
int t = s + d;
if (t >= 0 && t < 64 && file_distance(Square(t), Square(s)) <= 2)
kn |= square_bb(Square(t));
}
KnightAttacks[s] = kn;
const int kingDirs[8] = { 8, -8, 1, -1, 9, 7, -7, -9 };
Bitboard kg = 0;
for (int d : kingDirs) {
int t = s + d;
if (t >= 0 && t < 64 && file_distance(Square(t), Square(s)) <= 1)
kg |= square_bb(Square(t));
}
KingAttacks[s] = kg;
}
const int rookDirs[4] = { 8, -8, 1, -1 };
const int bishopDirs[4] = { 9, 7, -7, -9 };
init_magics(rookDirs, RookMagics, RookTable);
init_magics(bishopDirs, BishopMagics, BishopTable);
}
} // namespace chess