// Eryn Wells use crate::bit_scanner::{LeadingBitScanner, TrailingBitScanner}; use crate::direction::IterationDirection; use crate::library; use chessfriend_core::{Color, Direction, File, Rank, Square}; use forward_ref::{forward_ref_binop, forward_ref_op_assign, forward_ref_unop}; use std::fmt; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; #[allow(clippy::cast_possible_truncation)] const SQUARES_NUM: u8 = Square::NUM as u8; /// A bitfield representation of a chess board that uses the bits of a 64-bit /// unsigned integer to represent whether a square on the board is occupied. /// Squares are laid out as follows, starting at the bottom left, going row-wise, /// and ending at the top right corner: /// /// ```text /// +-------------------------+ /// 8 | 56 57 58 59 60 61 62 63 | /// 7 | 48 49 50 51 52 53 54 55 | /// 6 | 40 41 42 43 44 45 46 47 | /// 5 | 32 33 34 35 36 37 38 39 | /// 4 | 24 25 26 27 28 29 30 31 | /// 3 | 16 17 18 19 20 21 22 23 | /// 2 | 8 9 10 11 12 13 14 15 | /// 1 | 0 1 2 3 4 5 6 7 | /// +-------------------------+ /// A B C D E F G H /// ``` /// #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct BitBoard(pub(crate) u64); macro_rules! moves_getter { ($getter_name:ident) => { #[must_use] pub fn $getter_name(sq: Square) -> BitBoard { library::library().$getter_name(sq) } }; } impl BitBoard { const EMPTY: BitBoard = BitBoard(u64::MIN); const FULL: BitBoard = BitBoard(u64::MAX); #[must_use] pub const fn empty() -> BitBoard { Self::EMPTY } #[must_use] pub const fn full() -> BitBoard { Self::FULL } #[must_use] pub const fn new(bits: u64) -> BitBoard { BitBoard(bits) } #[must_use] pub fn rank(rank: &u8) -> BitBoard { debug_assert!(*rank < 8); library::RANKS[*rank as usize] } #[must_use] pub fn file(file: &u8) -> BitBoard { debug_assert!(*file < 8); library::FILES[*file as usize] } #[must_use] pub fn ray(sq: Square, dir: Direction) -> BitBoard { library::library().ray(sq, dir) } #[must_use] pub fn pawn_attacks(sq: Square, color: Color) -> BitBoard { library::library().pawn_attacks(sq, color) } #[must_use] pub fn pawn_pushes(sq: Square, color: Color) -> BitBoard { library::library().pawn_pushes(sq, color) } moves_getter!(knight_moves); moves_getter!(bishop_moves); moves_getter!(rook_moves); moves_getter!(queen_moves); moves_getter!(king_moves); #[must_use] pub const fn kingside(color: Color) -> &'static BitBoard { &library::KINGSIDES[color as usize] } #[must_use] pub const fn queenside(color: Color) -> &'static BitBoard { &library::QUEENSIDES[color as usize] } } impl BitBoard { /// Converts this [`BitBoard`] to an unsigned 64-bit integer. #[must_use] pub const fn as_bits(&self) -> u64 { self.0 } /// Returns `true` if this [`BitBoard`] has no bits set. This is the opposite /// of [`BitBoard::is_populated`]. /// /// ## Examples /// /// ``` /// use chessfriend_bitboard::BitBoard; /// assert!(BitBoard::empty().is_empty()); /// assert!(!BitBoard::full().is_empty()); /// assert!(!BitBoard::new(0b1000).is_empty()); /// ``` #[must_use] pub const fn is_empty(&self) -> bool { self.0 == 0 } /// Returns `true` if the [`BitBoard`] has at least one bit set. This is the /// opposite of [`BitBoard::is_empty`]. /// /// ## Examples /// /// ``` /// use chessfriend_bitboard::BitBoard; /// assert!(!BitBoard::empty().is_populated()); /// assert!(BitBoard::full().is_populated()); /// assert!(BitBoard::new(0b1).is_populated()); /// ``` #[must_use] pub const fn is_populated(&self) -> bool { self.0 != 0 } /// Returns `true` if this [`BitBoard`] has the bit corresponding to `square` set. /// /// ## Examples /// /// ``` /// use chessfriend_bitboard::BitBoard; /// use chessfriend_core::Square; /// /// let square = Square::E4; /// let mut bitboard = BitBoard::new(0b1001100); /// /// assert!(bitboard.contains(Square::C1)); /// assert!(!bitboard.contains(Square::B1)); /// ``` #[must_use] pub fn contains(self, square: Square) -> bool { let square_bitboard: BitBoard = square.into(); !(self & square_bitboard).is_empty() } /// Counts the number of set squares (1 bits) in this [`BitBoard`]. /// /// ## Examples /// /// ``` /// use chessfriend_bitboard::BitBoard; /// assert_eq!(BitBoard::empty().population_count(), 0); /// assert_eq!(BitBoard::new(0b01011110010).population_count(), 6); /// assert_eq!(BitBoard::full().population_count(), 64); /// ``` #[must_use] pub const fn population_count(&self) -> u32 { self.0.count_ones() } /// Set a square in this [`BitBoard`] by toggling the corresponding bit to 1. /// This always succeeds, even if the bit was already set. /// /// ## Examples /// /// ``` /// use chessfriend_bitboard::BitBoard; /// use chessfriend_core::Square; /// /// let mut bitboard = BitBoard::new(0b1001100); /// bitboard.set(Square::E4); /// assert!(bitboard.contains(Square::E4)); /// ``` pub fn set(&mut self, square: Square) { let square_bitboard: BitBoard = square.into(); self.0 |= square_bitboard.0; } /// Clear a square (set it to 0) in this [`BitBoard`]. This always succeeds /// even if the bit is not set. /// /// ## Examples /// /// ``` /// use chessfriend_bitboard::BitBoard; /// use chessfriend_core::Square; /// /// let mut bitboard = BitBoard::new(0b1001100); /// bitboard.clear(Square::C1); /// assert!(!bitboard.contains(Square::C1)); /// ``` pub fn clear(&mut self, square: Square) { let square_bitboard: BitBoard = square.into(); self.0 &= !square_bitboard.0; } /// Returns `true` if this [`BitBoard`] represents a single square. /// /// ## Examples /// /// ``` /// use chessfriend_bitboard::BitBoard; /// assert!(!BitBoard::empty().is_single_square(), "Empty bitboards represent no squares"); /// assert!(!BitBoard::full().is_single_square(), "Full bitboards represent all the squares"); /// assert!(!BitBoard::new(0b010011110101101100).is_single_square(), "This bitboard represents a bunch of squares"); /// assert!(BitBoard::new(0b10000000000000).is_single_square()); /// ``` #[must_use] pub fn is_single_square(&self) -> bool { self.0.is_power_of_two() } /// Return an Iterator over the occupied squares. #[must_use] pub fn occupied_squares( &self, direction: &IterationDirection, ) -> Box> { match direction { IterationDirection::Leading => Box::new(self.occupied_squares_leading()), IterationDirection::Trailing => Box::new(self.occupied_squares_trailing()), } } pub fn occupied_squares_leading(&self) -> LeadingBitScanner { LeadingBitScanner::new(self.0) } pub fn occupied_squares_trailing(&self) -> TrailingBitScanner { TrailingBitScanner::new(self.0) } #[must_use] pub fn first_occupied_square(&self, direction: &IterationDirection) -> Option { match direction { IterationDirection::Leading => self.first_occupied_square_leading(), IterationDirection::Trailing => self.first_occupied_square_trailing(), } } /// If the board is not empty, returns the first occupied square on the /// board, starting at the leading (most-significant) end of the board. If /// the board is empty, returns `None`. #[must_use] pub fn first_occupied_square_leading(self) -> Option { let leading_zeros = self._leading_zeros(); if leading_zeros < SQUARES_NUM { unsafe { Some(Square::from_index_unchecked( SQUARES_NUM - leading_zeros - 1, )) } } else { None } } /// If the board is not empty, returns the first occupied square on the /// board, starting at the trailing (least-significant) end of the board. /// If the board is empty, returns `None`. #[must_use] pub fn first_occupied_square_trailing(self) -> Option { let trailing_zeros = self._trailing_zeros(); if trailing_zeros < SQUARES_NUM { unsafe { Some(Square::from_index_unchecked(trailing_zeros)) } } else { None } } } impl BitBoard { #[must_use] #[allow(clippy::cast_possible_truncation)] fn _leading_zeros(self) -> u8 { self.0.leading_zeros() as u8 } #[must_use] #[allow(clippy::cast_possible_truncation)] fn _trailing_zeros(self) -> u8 { self.0.trailing_zeros() as u8 } } impl Default for BitBoard { fn default() -> Self { BitBoard::EMPTY } } impl From for u64 { fn from(value: BitBoard) -> Self { value.as_bits() } } impl From for BitBoard { fn from(value: File) -> Self { library::FILES[*value.as_index() as usize] } } impl From> for BitBoard { fn from(value: Option) -> Self { value.map_or(BitBoard::EMPTY, Into::::into) } } impl From for BitBoard { fn from(value: Rank) -> Self { library::FILES[*value.as_index() as usize] } } impl From for BitBoard { fn from(value: Square) -> Self { BitBoard(1u64 << value as u32) } } impl FromIterator for BitBoard { fn from_iter>(iter: T) -> Self { iter.into_iter().fold(BitBoard::EMPTY, |mut acc, sq| { acc.set(sq); acc }) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TryFromBitBoardError { NotSingleSquare, } impl TryFrom for Square { type Error = TryFromBitBoardError; fn try_from(value: BitBoard) -> Result { if !value.is_single_square() { return Err(TryFromBitBoardError::NotSingleSquare); } unsafe { Ok(Square::from_index_unchecked(value._trailing_zeros())) } } } impl fmt::Binary for BitBoard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Delegate to u64's implementation of Binary. fmt::Binary::fmt(&self.0, f) } } impl fmt::LowerHex for BitBoard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Delegate to u64's implementation of LowerHex. fmt::LowerHex::fmt(&self.0, f) } } impl fmt::UpperHex for BitBoard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Delegate to u64's implementation of UpperHex. fmt::UpperHex::fmt(&self.0, f) } } impl fmt::Display for BitBoard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let binary_ranks = format!("{:064b}", self.0) .chars() .rev() .map(String::from) .collect::>(); let mut ranks_written = 0; for rank in binary_ranks.chunks(8).rev() { write!(f, "{}", rank.join(" "))?; ranks_written += 1; if ranks_written < 8 { writeln!(f)?; } } Ok(()) } } impl fmt::Debug for BitBoard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "BitBoard({:064b})", self.0) } } macro_rules! infix_op { ($trait_type:ident, $func_name:ident, $left_type:ty, $right_type:ty) => { impl std::ops::$trait_type<$right_type> for $left_type { type Output = Self; #[inline] fn $func_name(self, rhs: $right_type) -> Self::Output { BitBoard(std::ops::$trait_type::$func_name(self.0, rhs.0)) } } forward_ref_binop!(impl $trait_type, $func_name for $left_type, $right_type); }; } macro_rules! assign_op { ($trait_type:ident, $func_name:ident, $type:ty) => { impl $trait_type for $type { #[inline] fn $func_name(&mut self, rhs: $type) { $trait_type::$func_name(&mut self.0, rhs.0) } } forward_ref_op_assign!(impl $trait_type, $func_name for $type, $type); }; } infix_op!(BitAnd, bitand, BitBoard, BitBoard); infix_op!(BitOr, bitor, BitBoard, BitBoard); infix_op!(BitXor, bitxor, BitBoard, BitBoard); assign_op!(BitAndAssign, bitand_assign, BitBoard); assign_op!(BitOrAssign, bitor_assign, BitBoard); assign_op!(BitXorAssign, bitxor_assign, BitBoard); impl Not for BitBoard { type Output = Self; #[inline] fn not(self) -> Self::Output { BitBoard(!self.0) } } forward_ref_unop!(impl Not, not for BitBoard); #[cfg(test)] mod tests { use super::*; use crate::bitboard; use chessfriend_core::Square; #[test] #[ignore] fn display_and_debug() { let bb = BitBoard::file(&0) | BitBoard::file(&3) | BitBoard::rank(&7) | BitBoard::rank(&4); println!("{}", &bb); } #[test] #[allow(clippy::unreadable_literal)] fn rank() { assert_eq!(BitBoard::rank(&0).0, 0xFF, "Rank 1"); assert_eq!(BitBoard::rank(&1).0, 0xFF00, "Rank 2"); assert_eq!(BitBoard::rank(&2).0, 0xFF0000, "Rank 3"); assert_eq!(BitBoard::rank(&3).0, 0xFF000000, "Rank 4"); assert_eq!(BitBoard::rank(&4).0, 0xFF00000000, "Rank 5"); assert_eq!(BitBoard::rank(&5).0, 0xFF0000000000, "Rank 6"); assert_eq!(BitBoard::rank(&6).0, 0xFF000000000000, "Rank 7"); assert_eq!(BitBoard::rank(&7).0, 0xFF00000000000000, "Rank 8"); } #[test] fn single_rank_occupancy() { #[allow(clippy::unreadable_literal)] let bb = BitBoard(0b01010100); let expected_squares = [Square::G1, Square::E1, Square::C1]; bb.occupied_squares(&IterationDirection::Leading) .zip(expected_squares) .for_each(|(a, b)| assert_eq!(a, b)); } #[test] fn occupancy_spot_check() { #[allow(clippy::unreadable_literal)] let bb = BitBoard(0b10000000_00000000_00100000_00000100_00000000_00000000_00010000_00001000); let expected_squares = [Square::H8, Square::F6, Square::C5, Square::E2, Square::D1]; bb.occupied_squares(&IterationDirection::Leading) .zip(expected_squares) .for_each(|(a, b)| assert_eq!(a, b)); } #[test] fn xor() { let a = bitboard![C5 G7]; let b = bitboard![B5 G7 H3]; assert_eq!(a ^ b, bitboard![B5 C5 H3]); assert_eq!(a ^ BitBoard::empty(), a); assert_eq!(BitBoard::empty() ^ BitBoard::empty(), BitBoard::empty()); } #[test] fn bitand_assign() { let mut a = bitboard![C5 G7]; let b = bitboard![B5 G7 H3]; a &= b; assert_eq!(a, bitboard![G7]); } #[test] fn bitor_assign() { let mut a = bitboard![C5 G7]; let b = bitboard![B5 G7 H3]; a |= b; assert_eq!(a, bitboard![B5 C5 G7 H3]); } #[test] fn from_square() { assert_eq!(BitBoard::from(Square::A1), BitBoard(0b1)); assert_eq!(BitBoard::from(Square::H8), BitBoard(1 << 63)); } #[test] fn first_occupied_squares() { let bb = bitboard![A8 E1]; assert_eq!(bb.first_occupied_square_leading(), Some(Square::A8)); assert_eq!(bb.first_occupied_square_trailing(), Some(Square::E1)); let bb = bitboard![D6 E7 F8]; assert_eq!(bb.first_occupied_square_trailing(), Some(Square::D6)); } }