use super::{ board::Board, coords::{CoordPair, CoordPairInner}, CoordAxis, }; use allocative::Allocative; use static_assertions::const_assert; pub type BitBoardInner = u64; #[derive(Copy, Clone, PartialEq, Eq, Allocative)] pub struct BitBoard(BitBoardInner); // BitBoard should be big enough to fit all points on the board const_assert!(std::mem::size_of::() * 8 >= Board::AREA.0 as usize); impl BitBoard { #[allow(clippy::new_without_default)] pub const fn new() -> Self { Self(0) } pub const fn get(&self, coord: CoordPair) -> bool { self.get_by_index(coord.0) } pub const fn set(&mut self, coord: CoordPair, value: bool) { self.set_by_index(coord.0, value); } const fn get_by_index(&self, index: CoordPairInner) -> bool { ((self.0 >> index) & 0b1) != 0b0 } const fn set_by_index(&mut self, index: CoordPairInner, value: bool) { // PERF! branchless setting of bit (~+3% perf bump) self.0 &= !(0b1 << index); // clear bit self.0 |= (value as BitBoardInner) << index; // set bit (if needed) } pub const fn count(&self) -> usize { self.0.count_ones() as usize } pub const fn is_empty(&self) -> bool { self.0 == 0b0 } pub const fn east(&self, n: usize) -> Self { let mask = !Self::col_mask(Board::SIZE - 1).0; // Mask to block column BOARD_SIZE-1 bits Self((self.0 & mask) << n) } pub const fn west(&self, n: usize) -> Self { let mask = !Self::col_mask(0).0; Self((self.0 & mask) >> n) } pub const fn north(&self, n: usize) -> Self { Self(self.0 >> (Board::SIZE as usize * n)) } pub const fn south(&self, n: usize) -> Self { Self(self.0 << (Board::SIZE as usize * n)) } pub const fn northeast(&self, n: usize) -> Self { self.north(n).east(n) } pub const fn northwest(&self, n: usize) -> Self { self.north(n).west(n) } pub const fn southeast(&self, n: usize) -> Self { self.south(n).east(n) } pub const fn southwest(&self, n: usize) -> Self { self.south(n).west(n) } // Mask for a specific column (e.g., col_mask(7) = 0x8080808080808080) const fn col_mask(col: CoordAxis) -> Self { let mut mask = 0b0; let mut i = 0b0; while i < Board::AREA.0 { mask |= 0b1 << (i + col); i += Board::SIZE; } Self(mask) } // Create a BitBoard from a single coordinate pub const fn from_coord(coord: CoordPair) -> Self { Self(1 << coord.0) } pub const fn intersects(self, other: Self) -> bool { (self.0 & other.0) > 0b0 } pub const fn bitor_assign(&mut self, other: Self) { self.0 |= other.0; } pub const fn bitand_assign(&mut self, other: Self) { self.0 &= other.0; } pub const fn not(self) -> Self { Self(!self.0) } } #[cfg(test)] mod test { use super::*; #[test] fn set_and_get() { let mut b = BitBoard::new(); for c in 0..Board::AREA.0 { assert!( !b.get(CoordPair(c)), "A just-initalized BitBoard should be completely empty" ) } assert!(!b.get((2, 4).into())); b.set((2, 4).into(), true); assert!(b.get((2, 4).into())); b.set((2, 4).into(), false); assert!(!b.get((2, 4).into())); } }