use crate::utils::bit_operations::u16_get_bit;
use std::fmt::{Display, Formatter};
use std::ops::{BitAnd, BitOr, BitXor, Not};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct BitBoard(u64);
impl BitBoard {
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(value: u64) -> Self {
Self(value)
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn empty() -> Self {
Self(0)
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn get_value(&self) -> u64 {
self.0
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn is_empty(&self) -> bool {
self.0 == 0
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn get_bit(&self, index: u8) -> bool {
(self.0 & (1u64 << index)) != 0u64
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_bit(&mut self, index: u8) {
self.0 |= 1u64 << index;
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn clear_bit(&mut self, index: u8) {
self.0 &= !(1u64 << index);
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn count_set_bits(&self) -> u8 {
self.0.count_ones() as u8
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn get_lowest_set_bit(&self) -> Option<u8> {
if self.0 == 0 {
return None;
}
Some(self.0.trailing_zeros() as u8)
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn pop_lowest_set_bit(&mut self) -> Option<u8> {
let lsb_index = self.get_lowest_set_bit()?;
self.0 &= self.0 - 1;
Some(lsb_index)
}
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn iter_set_bits(&self) -> BitBoardIter {
BitBoardIter { bits: self.0 }
}
pub fn occupancy_variation(&self, index: u16) -> BitBoard {
let mut mask = *self;
let mut result = BitBoard::empty();
for variation_index in 0u8..16 {
if let Some(lowest_set_bit_index) = mask.pop_lowest_set_bit() {
if u16_get_bit(index, variation_index) {
result.set_bit(lowest_set_bit_index);
}
} else {
break;
}
}
result
}
}
impl BitOr for BitBoard {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
BitBoard::new(self.0 | rhs.0)
}
}
impl BitAnd for BitBoard {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
BitBoard::new(self.0 & rhs.0)
}
}
impl BitXor for BitBoard {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
BitBoard::new(self.0 ^ rhs.0)
}
}
impl Not for BitBoard {
type Output = Self;
fn not(self) -> Self::Output {
BitBoard::new(!self.0)
}
}
pub struct BitBoardIter {
bits: u64,
}
impl Iterator for BitBoardIter {
type Item = u8;
#[cfg_attr(tarpaulin, inline(never))]
#[cfg_attr(not(tarpaulin), inline(always))]
fn next(&mut self) -> Option<Self::Item> {
if self.bits == 0 {
return None;
}
let index = self.bits.trailing_zeros() as u8;
self.bits &= self.bits - 1;
Some(index)
}
}
impl Display for BitBoard {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for y in (0..8).rev() {
for x in 0..8 {
let index: u8 = x + y * 8;
if self.get_bit(index) {
write!(f, "1 ")?;
} else {
write!(f, "0 ")?;
}
}
writeln!(f)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::BitBoard;
#[test]
fn test_or() {
let b1 = BitBoard::new(0b0101);
let b2 = BitBoard::new(0b1010);
assert_eq!((b1 | b2).get_value(), 0b1111)
}
#[test]
fn test_and() {
let b1 = BitBoard::new(0b0101);
let b2 = BitBoard::new(0b1110);
assert_eq!((b1 & b2).get_value(), 0b0100)
}
#[test]
fn test_xor() {
let b1 = BitBoard::new(0b0101);
let b2 = BitBoard::new(0b1110);
assert_eq!((b1 ^ b2).get_value(), 0b1011)
}
#[test]
fn test_not() {
let bb = BitBoard::new(0xF);
assert_eq!((!bb).get_value(), 0xFFFFFFFFFFFFFFF0)
}
}