// Eryn Wells //! # 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, } impl MoveLibraryWrapper { const fn new() -> Self { Self { library: OnceLock::::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); }