othello/src/repr/bitboard.rs

137 lines
3.4 KiB
Rust

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::<BitBoard>() * 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()));
}
}