// Eryn Wells use super::library::{library, FILES, RANKS}; use super::{LeadingBitScanner, TrailingBitScanner}; use crate::{square::Direction, Square}; use std::fmt; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub(crate) struct BitBoard(pub(super) u64); macro_rules! moves_getter { ($getter_name:ident) => { pub fn $getter_name(sq: Square) -> BitBoard { library().$getter_name(sq) } }; } impl BitBoard { pub const fn empty() -> BitBoard { BitBoard(0) } pub fn new(bits: u64) -> BitBoard { BitBoard(bits) } pub fn rank(rank: usize) -> BitBoard { assert!(rank < 8); RANKS[rank] } pub fn file(file: usize) -> BitBoard { assert!(file < 8); FILES[file] } moves_getter!(knight_moves); moves_getter!(bishop_moves); moves_getter!(rook_moves); moves_getter!(queen_moves); moves_getter!(king_moves); pub fn is_empty(&self) -> bool { self.0 == 0 } pub fn has_piece_at(self, sq: Square) -> bool { !(self & sq.into()).is_empty() } pub fn place_piece_at(&mut self, sq: Square) { let sq_bb: BitBoard = sq.into(); *self |= sq_bb } fn remove_piece_at(&mut self, sq: Square) { let sq_bb: BitBoard = sq.into(); *self &= !sq_bb } } impl BitBoard { /// Return an Iterator over the occupied squares, starting from the leading /// (most-significant bit) end of the field. pub(crate) fn occupied_squares(&self) -> impl Iterator { LeadingBitScanner::new(self.0).map(Square::from_index) } /// Return an Iterator over the occupied squares, starting from the trailing /// (least-significant bit) end of the field. pub(crate) fn occupied_squares_trailing(&self) -> impl Iterator { LeadingBitScanner::new(self.0).map(Square::from_index) } } impl From for BitBoard { fn from(value: Square) -> Self { BitBoard(1 << value as u64) } } 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(|c| String::from(c)) .collect::>(); let mut ranks_written = 0; for rank in binary_ranks.chunks(8).rev() { let joined_rank = rank.join(" "); write!(f, "{}", joined_rank)?; ranks_written += 1; if ranks_written < 8 { write!(f, "\n")?; } } Ok(()) } } impl fmt::Debug for BitBoard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_tuple("BitBoard") .field(&format_args!("{:064b}", self.0)) .finish() } } macro_rules! infix_op { ($trait_type:ident, $func_name:ident, $left_type:ty, $right_type:ty) => { impl $trait_type<$right_type> for $left_type { type Output = BitBoard; #[inline] fn $func_name(self, rhs: $right_type) -> Self::Output { BitBoard($trait_type::$func_name(self.0, rhs.0)) } } }; } macro_rules! assign_op { ($trait_type:ident, $func_name:ident, $left_type:ty) => { impl $trait_type for $left_type { #[inline] fn $func_name(&mut self, rhs: Self) { $trait_type::$func_name(&mut self.0, rhs.0) } } }; } infix_op!(BitAnd, bitand, BitBoard, BitBoard); assign_op!(BitAndAssign, bitand_assign, BitBoard); assign_op!(BitOrAssign, bitor_assign, BitBoard); infix_op!(BitOr, bitor, BitBoard, BitBoard); impl Not for BitBoard { type Output = BitBoard; #[inline] fn not(self) -> Self::Output { BitBoard(!self.0) } } impl Not for &BitBoard { type Output = BitBoard; #[inline] fn not(self) -> Self::Output { BitBoard(!self.0) } } #[cfg(test)] mod tests { use super::*; use crate::Square; #[test] fn display_and_debug() { let bb = BitBoard::file(0) | BitBoard::file(3) | BitBoard::rank(7) | BitBoard::rank(4); println!("{}", &bb); println!("{:?}", &bb); } #[test] 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 is_empty() { assert!(BitBoard(0).is_empty()); assert!(!BitBoard(0xFF).is_empty()); } #[test] fn has_piece_at() { let bb = BitBoard(0b1001100); assert!(bb.has_piece_at(Square::C1)); assert!(!bb.has_piece_at(Square::B1)); } #[test] fn place_piece_at() { let sq = Square::E4; let mut bb = BitBoard(0b1001100); bb.place_piece_at(sq); assert!(bb.has_piece_at(sq)); } #[test] fn remove_piece_at() { let sq = Square::A3; let mut bb = BitBoard(0b1001100); bb.remove_piece_at(sq); assert!(!bb.has_piece_at(sq)); } #[test] fn single_rank_occupancy() { let bb = BitBoard(0b01010100); let expected_squares = [Square::G1, Square::E1, Square::C1]; for (a, b) in bb.occupied_squares().zip(expected_squares.iter().cloned()) { assert_eq!(a, b); } } #[test] fn occupancy_spot_check() { let bb = BitBoard(0b10000000_00000000_00100000_00000100_00000000_00000000_00010000_00001000); let expected_squares = [Square::H8, Square::F6, Square::C5, Square::E2, Square::D1]; for (a, b) in bb.occupied_squares().zip(expected_squares.iter().cloned()) { assert_eq!(a, b); } } }