chessfriend/bitboard/src/library.rs
Eryn Wells 4b148529a1 [bitboard] Fix the warning about shared references to mutable static data
I've lived with this warning for a long time because I didn't really understand
it.

```
warning: `chessfriend_core` (lib) generated 1 warning (run `cargo fix --lib -p chessfriend_core` to apply 1 suggestion)
warning: creating a shared reference to mutable static is discouraged
  --> bitboard/src/library.rs:66:9
```

I was able to fix this by creating a new type with a single OnceLock attribute.
The OnceLock acts as a cell, making it mutable, even if self is not. So, you can
declare the MoveLibraryWrapper non-mutable static, but still initialize the
library inside the Cell.
2025-06-08 17:34:42 -07:00

269 lines
8.8 KiB
Rust

// Eryn Wells <eryn@erynwells.me>
//! # The Bitboard Library
//!
//! This module implements a collection of commonly used bitboards that can be
//! looked up efficiently.
//!
//! The `library()` method returns a static instance of a `Library`, which
//! provides getters for all available bitboards.
use crate::BitBoard;
use chessfriend_core::{Color, Direction, File, Rank, Square};
use std::sync::OnceLock;
#[allow(clippy::identity_op)]
#[allow(clippy::erasing_op)]
pub(crate) const RANKS: [BitBoard; Rank::NUM] = [
BitBoard(0xFF << (0 * 8)),
BitBoard(0xFF << (1 * 8)),
BitBoard(0xFF << (2 * 8)),
BitBoard(0xFF << (3 * 8)),
BitBoard(0xFF << (4 * 8)),
BitBoard(0xFF << (5 * 8)),
BitBoard(0xFF << (6 * 8)),
BitBoard(0xFF << (7 * 8)),
];
#[allow(clippy::identity_op)]
#[allow(clippy::erasing_op)]
pub(crate) const FILES: [BitBoard; File::NUM] = [
BitBoard(0x0101_0101_0101_0101 << 0),
BitBoard(0x0101_0101_0101_0101 << 1),
BitBoard(0x0101_0101_0101_0101 << 2),
BitBoard(0x0101_0101_0101_0101 << 3),
BitBoard(0x0101_0101_0101_0101 << 4),
BitBoard(0x0101_0101_0101_0101 << 5),
BitBoard(0x0101_0101_0101_0101 << 6),
BitBoard(0x0101_0101_0101_0101 << 7),
];
/// Bitboards representing the kingside of the board, per color.
pub(crate) const KINGSIDES: [BitBoard; Color::NUM] = [
BitBoard(0xF0F0_F0F0_F0F0_F0F0),
BitBoard(0x0F0F_0F0F_0F0F_0F0F),
];
/// Bitboards representing the queenside of the board, per color.
pub(crate) const QUEENSIDES: [BitBoard; Color::NUM] = [
BitBoard(0x0F0F_0F0F_0F0F_0F0F),
BitBoard(0xF0F0_F0F0_F0F0_F0F0),
];
/// A bitboard representing the light squares.
#[allow(dead_code)]
pub(crate) const LIGHT_SQUARES: BitBoard =
BitBoard(0x5555 | 0x5555 << 16 | 0x5555 << 32 | 0x5555 << 48);
/// A bitboad representing the dark squares
#[allow(dead_code)]
pub(crate) const DARK_SQUARES: BitBoard = BitBoard(!LIGHT_SQUARES.0);
pub(super) fn library() -> &'static MoveLibrary {
static MOVE_LIBRARY: MoveLibraryWrapper = MoveLibraryWrapper::new();
MOVE_LIBRARY.library()
}
macro_rules! library_getter {
($name:ident) => {
pub(super) const fn $name(&self, sq: Square) -> BitBoard {
self.$name[sq as usize]
}
};
}
struct MoveLibraryWrapper {
library: OnceLock<MoveLibrary>,
}
impl MoveLibraryWrapper {
const fn new() -> Self {
Self {
library: OnceLock::<MoveLibrary>::new(),
}
}
fn library(&self) -> &MoveLibrary {
self.library.get_or_init(|| {
let mut library = MoveLibrary::new();
library.init();
library
})
}
}
#[derive(Debug)]
pub(super) struct MoveLibrary {
// Rays
rays: [[BitBoard; Direction::NUM]; Square::NUM],
// Piecewise move tables
pawn_attacks: [[BitBoard; Square::NUM]; Color::NUM],
pawn_pushes: [[BitBoard; Square::NUM]; Color::NUM],
knight_moves: [BitBoard; Square::NUM],
bishop_moves: [BitBoard; Square::NUM],
rook_moves: [BitBoard; Square::NUM],
queen_moves: [BitBoard; Square::NUM],
king_moves: [BitBoard; Square::NUM],
}
impl MoveLibrary {
const fn new() -> MoveLibrary {
MoveLibrary {
rays: [[BitBoard::empty(); Direction::NUM]; Square::NUM],
pawn_attacks: [[BitBoard::empty(); Square::NUM]; Color::NUM],
pawn_pushes: [[BitBoard::empty(); Square::NUM]; Color::NUM],
knight_moves: [BitBoard::empty(); Square::NUM],
bishop_moves: [BitBoard::empty(); Square::NUM],
rook_moves: [BitBoard::empty(); Square::NUM],
queen_moves: [BitBoard::empty(); Square::NUM],
king_moves: [BitBoard::empty(); Square::NUM],
}
}
fn init(&mut self) {
for sq in Square::ALL {
self.init_pawn_moves(sq);
self.init_orthogonal_rays(sq);
self.init_diagonal_rays(sq);
self.init_knight_moves(sq as usize);
self.init_bishop_moves(sq);
self.init_rook_moves(sq);
self.init_queen_moves(sq);
self.init_king_moves(sq as usize);
}
}
fn init_orthogonal_rays(&mut self, sq: Square) {
let sq_bb: BitBoard = sq.into();
let rays = &mut self.rays[sq as usize];
rays[Direction::North as usize] = Self::_generate_ray(sq_bb, BitBoard::shift_north_one);
rays[Direction::South as usize] = Self::_generate_ray(sq_bb, BitBoard::shift_south_one);
rays[Direction::East as usize] = Self::_generate_ray(sq_bb, BitBoard::shift_east_one);
rays[Direction::West as usize] = Self::_generate_ray(sq_bb, BitBoard::shift_west_one);
}
fn init_diagonal_rays(&mut self, sq: Square) {
let sq_bb: BitBoard = sq.into();
let rays = &mut self.rays[sq as usize];
rays[Direction::NorthEast as usize] =
Self::_generate_ray(sq_bb, BitBoard::shift_north_east_one);
rays[Direction::NorthWest as usize] =
Self::_generate_ray(sq_bb, BitBoard::shift_north_west_one);
rays[Direction::SouthWest as usize] =
Self::_generate_ray(sq_bb, BitBoard::shift_south_west_one);
rays[Direction::SouthEast as usize] =
Self::_generate_ray(sq_bb, BitBoard::shift_south_east_one);
}
fn init_king_moves(&mut self, idx: usize) {
let king = BitBoard::new(1 << idx);
let mut attacks = king.shift_east_one() | king.shift_west_one();
let king = king | attacks;
attacks |= king.shift_north_one() | king.shift_south_one();
self.king_moves[idx] = attacks;
}
/// Calculate bitboards representing knight moves from each square on the
/// board. The algorithm is described on the [Chess Programming Wiki][cpw].
///
/// [cpw]: https://www.chessprogramming.org/Knight_Pattern
fn init_knight_moves(&mut self, idx: usize) {
let knight = BitBoard::new(1 << idx);
let east = knight.shift_east_one();
let west = knight.shift_west_one();
let mut attacks = (east | west).shift_north(2);
attacks |= (east | west).shift_south(2);
let east = east.shift_east_one();
let west = west.shift_west_one();
attacks |= (east | west).shift_north_one();
attacks |= (east | west).shift_south_one();
self.knight_moves[idx] = attacks;
}
fn init_bishop_moves(&mut self, sq: Square) {
let rays = self.rays[sq as usize];
self.bishop_moves[sq as usize] = rays[Direction::NorthWest as usize]
| rays[Direction::NorthEast as usize]
| rays[Direction::SouthEast as usize]
| rays[Direction::SouthWest as usize];
}
fn init_rook_moves(&mut self, sq: Square) {
let rays = self.rays[sq as usize];
self.rook_moves[sq as usize] = rays[Direction::North as usize]
| rays[Direction::East as usize]
| rays[Direction::South as usize]
| rays[Direction::West as usize];
}
fn init_queen_moves(&mut self, sq: Square) {
let rook_moves = self.rook_moves[sq as usize];
let bishop_moves = self.bishop_moves[sq as usize];
self.queen_moves[sq as usize] = rook_moves | bishop_moves;
}
fn init_pawn_moves(&mut self, sq: Square) {
let bitboard: BitBoard = sq.into();
self.pawn_attacks[Color::White as usize][sq as usize] =
bitboard.shift_north_west_one() | bitboard.shift_north_east_one();
self.pawn_attacks[Color::Black as usize][sq as usize] =
bitboard.shift_south_west_one() | bitboard.shift_south_east_one();
self.pawn_pushes[Color::White as usize][sq as usize] = {
let mut push = bitboard.shift_north_one();
if !(bitboard & RANKS[1]).is_empty() {
push |= push.shift_north_one();
}
push
};
self.pawn_pushes[Color::Black as usize][sq as usize] = {
let mut push = bitboard.shift_south_one();
if !(bitboard & RANKS[6]).is_empty() {
push |= push.shift_south_one();
}
push
};
}
fn _generate_ray(sq: BitBoard, shift: fn(&BitBoard) -> BitBoard) -> BitBoard {
let mut ray = BitBoard::empty();
let mut iter = shift(&sq);
while !iter.is_empty() {
ray |= iter;
iter = shift(&iter);
}
ray
}
pub(super) const fn ray(&self, sq: Square, dir: Direction) -> BitBoard {
self.rays[sq as usize][dir as usize]
}
pub(super) const fn pawn_pushes(&self, sq: Square, color: Color) -> BitBoard {
self.pawn_pushes[color as usize][sq as usize]
}
pub(super) const fn pawn_attacks(&self, sq: Square, color: Color) -> BitBoard {
self.pawn_attacks[color as usize][sq as usize]
}
library_getter!(knight_moves);
library_getter!(bishop_moves);
library_getter!(rook_moves);
library_getter!(queen_moves);
library_getter!(king_moves);
}