use crate::{file::File, rank::Rank, square::Square};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Bitboard(pub u64);
impl Bitboard {
pub const EMPTY: Bitboard = Bitboard(0);
pub const FULL: Bitboard = Bitboard(u64::MAX);
pub const LIGHT: Bitboard = Bitboard(0x55AA_55AA_55AA_55AA);
pub const DARK: Bitboard = Bitboard(0xAA55_AA55_AA55_AA55);
pub const fn new(value: u64) -> Self {
Self(value)
}
pub fn is_empty(self) -> bool {
self == Self::EMPTY
}
pub fn is_non_empty(self) -> bool {
self != Self::EMPTY
}
pub fn toggle(self, other: impl Into<Bitboard>) -> Self {
self ^ other
}
pub fn set(self, other: impl Into<Bitboard>) -> Self {
self | other
}
pub fn unset(self, other: impl Into<Bitboard>) -> Bitboard {
let mask = other.into();
self & !mask
}
pub fn is_set(self, other: impl Into<Bitboard>) -> bool {
(self & other) != Self::EMPTY
}
}
impl Iterator for Bitboard {
type Item = Square;
fn next(&mut self) -> Option<Self::Item> {
if self.0 == 0 {
return None;
}
let square_index = self.0.trailing_zeros() as u8;
self.0 &= self.0 - 1;
Some(Square(square_index))
}
}
impl From<u64> for Bitboard {
fn from(value: u64) -> Self {
Self::new(value)
}
}
impl From<Rank> for Bitboard {
fn from(value: Rank) -> Self {
Bitboard(Rank::MASKS[value.as_u8() as usize])
}
}
impl From<File> for Bitboard {
fn from(value: File) -> Self {
Bitboard(File::MASKS[value.as_u8() as usize])
}
}
impl From<Square> for Bitboard {
fn from(value: Square) -> Self {
Bitboard(1_u64 << value.0)
}
}
impl TryFrom<Bitboard> for Square {
type Error = ();
fn try_from(value: Bitboard) -> Result<Self, Self::Error> {
if value.0.count_ones() == 1 {
Ok(Square(value.0.trailing_zeros() as u8))
} else {
Err(())
}
}
}
impl std::ops::Not for Bitboard {
type Output = Self;
fn not(self) -> Self::Output {
Self(!self.0)
}
}
impl<T: Into<Bitboard>> std::ops::BitAnd<T> for Bitboard {
type Output = Bitboard;
fn bitand(self, rhs: T) -> Self::Output {
Bitboard(self.0 & rhs.into().0)
}
}
impl<T: Into<Bitboard>> std::ops::BitOr<T> for Bitboard {
type Output = Bitboard;
fn bitor(self, rhs: T) -> Self::Output {
Bitboard(self.0 | rhs.into().0)
}
}
impl<T: Into<Bitboard>> std::ops::BitXor<T> for Bitboard {
type Output = Bitboard;
fn bitxor(self, rhs: T) -> Self::Output {
Bitboard(self.0 ^ rhs.into().0)
}
}
impl<T: Into<Bitboard>> std::ops::Shl<T> for Bitboard {
type Output = Self;
fn shl(self, rhs: T) -> Self::Output {
Bitboard(self.0 << rhs.into().0)
}
}
impl<T: Into<Bitboard>> std::ops::Shr<T> for Bitboard {
type Output = Self;
fn shr(self, rhs: T) -> Self::Output {
Bitboard(self.0 >> rhs.into().0)
}
}
impl<T: Into<Bitboard>> std::ops::BitOrAssign<T> for Bitboard {
fn bitor_assign(&mut self, rhs: T) {
self.0 |= rhs.into().0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_is_zero() {
assert_eq!(Bitboard::EMPTY.0, 0);
}
#[test]
fn full_is_all_ones() {
assert_eq!(Bitboard::FULL.0, u64::MAX);
}
#[test]
fn light_and_dark_partition_full() {
assert_eq!(Bitboard::LIGHT | Bitboard::DARK, Bitboard::FULL);
assert_eq!(Bitboard::LIGHT & Bitboard::DARK, Bitboard::EMPTY);
}
#[test]
fn new_roundtrips_value() {
assert_eq!(Bitboard::new(42).0, 42);
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
fn bb() -> impl Strategy<Value = Bitboard> {
any::<u64>().prop_map(Bitboard)
}
fn sq() -> impl Strategy<Value = Square> {
(0u8..64).prop_map(Square)
}
proptest! {
#[test]
fn and_is_commutative(a in bb(), b in bb()) {
prop_assert_eq!(a & b, b & a);
}
#[test]
fn or_is_commutative(a in bb(), b in bb()) {
prop_assert_eq!(a | b, b | a);
}
#[test]
fn xor_is_commutative(a in bb(), b in bb()) {
prop_assert_eq!(a ^ b, b ^ a);
}
#[test]
fn and_is_associative(a in bb(), b in bb(), c in bb()) {
prop_assert_eq!((a & b) & c, a & (b & c));
}
#[test]
fn or_is_associative(a in bb(), b in bb(), c in bb()) {
prop_assert_eq!((a | b) | c, a | (b | c));
}
#[test]
fn xor_is_associative(a in bb(), b in bb(), c in bb()) {
prop_assert_eq!((a ^ b) ^ c, a ^ (b ^ c));
}
#[test]
fn and_distributes_over_or(a in bb(), b in bb(), c in bb()) {
prop_assert_eq!(a & (b | c), (a & b) | (a & c));
}
#[test]
fn or_distributes_over_and(a in bb(), b in bb(), c in bb()) {
prop_assert_eq!(a | (b & c), (a | b) & (a | c));
}
#[test]
fn and_is_idempotent(a in bb()) {
prop_assert_eq!(a & a, a);
}
#[test]
fn or_is_idempotent(a in bb()) {
prop_assert_eq!(a | a, a);
}
#[test]
fn xor_with_self_is_empty(a in bb()) {
prop_assert_eq!(a ^ a, Bitboard::EMPTY);
}
#[test]
fn and_empty_is_empty(a in bb()) {
prop_assert_eq!(a & Bitboard::EMPTY, Bitboard::EMPTY);
}
#[test]
fn and_full_is_identity(a in bb()) {
prop_assert_eq!(a & Bitboard::FULL, a);
}
#[test]
fn or_empty_is_identity(a in bb()) {
prop_assert_eq!(a | Bitboard::EMPTY, a);
}
#[test]
fn or_full_is_full(a in bb()) {
prop_assert_eq!(a | Bitboard::FULL, Bitboard::FULL);
}
#[test]
fn xor_empty_is_identity(a in bb()) {
prop_assert_eq!(a ^ Bitboard::EMPTY, a);
}
#[test]
fn xor_full_is_complement(a in bb()) {
prop_assert_eq!(a ^ Bitboard::FULL, !a);
}
#[test]
fn not_is_involution(a in bb()) {
prop_assert_eq!(!!a, a);
}
#[test]
fn de_morgan_and(a in bb(), b in bb()) {
prop_assert_eq!(!(a & b), !a | !b);
}
#[test]
fn de_morgan_or(a in bb(), b in bb()) {
prop_assert_eq!(!(a | b), !a & !b);
}
#[test]
fn set_matches_bitor(a in bb(), b in bb()) {
prop_assert_eq!(a.set(b), a | b);
}
#[test]
fn unset_matches_bitand_not(a in bb(), b in bb()) {
prop_assert_eq!(a.unset(b), a & !b);
}
#[test]
fn toggle_matches_bitxor(a in bb(), b in bb()) {
prop_assert_eq!(a.toggle(b), a ^ b);
}
#[test]
fn set_is_idempotent(a in bb(), b in bb()) {
prop_assert_eq!(a.set(b).set(b), a.set(b));
}
#[test]
fn unset_is_idempotent(a in bb(), b in bb()) {
prop_assert_eq!(a.unset(b).unset(b), a.unset(b));
}
#[test]
fn toggle_twice_is_identity(a in bb(), b in bb()) {
prop_assert_eq!(a.toggle(b).toggle(b), a);
}
#[test]
fn set_empty_is_identity(a in bb()) {
prop_assert_eq!(a.set(Bitboard::EMPTY), a);
}
#[test]
fn unset_empty_is_identity(a in bb()) {
prop_assert_eq!(a.unset(Bitboard::EMPTY), a);
}
#[test]
fn toggle_empty_is_identity(a in bb()) {
prop_assert_eq!(a.toggle(Bitboard::EMPTY), a);
}
#[test]
fn set_full_is_full(a in bb()) {
prop_assert_eq!(a.set(Bitboard::FULL), Bitboard::FULL);
}
#[test]
fn unset_full_is_empty(a in bb()) {
prop_assert_eq!(a.unset(Bitboard::FULL), Bitboard::EMPTY);
}
#[test]
fn toggle_full_is_complement(a in bb()) {
prop_assert_eq!(a.toggle(Bitboard::FULL), !a);
}
#[test]
fn set_then_unset_clears(a in bb(), b in bb()) {
prop_assert_eq!(a.set(b).unset(b), a.unset(b));
}
#[test]
fn unset_then_set_fills(a in bb(), b in bb()) {
prop_assert_eq!(a.unset(b).set(b), a.set(b));
}
#[test]
fn is_set_iff_intersection_nonempty(a in bb(), b in bb()) {
prop_assert_eq!(a.is_set(b), !(a & b).is_empty());
}
#[test]
fn is_empty_negates_is_non_empty(a in bb()) {
prop_assert_eq!(a.is_empty(), !a.is_non_empty());
}
#[test]
fn empty_is_never_set(a in bb()) {
prop_assert!(!Bitboard::EMPTY.is_set(a));
prop_assert!(!a.is_set(Bitboard::EMPTY));
}
#[test]
fn set_single_square_adds_bit(a in bb(), s in sq()) {
let after = a.set(s);
prop_assert!(after.is_set(s));
prop_assert_eq!(after.0 | a.0, after.0); }
#[test]
fn unset_single_square_clears_bit(a in bb(), s in sq()) {
let after = a.unset(s);
prop_assert!(!after.is_set(s));
prop_assert_eq!(after.0 & a.0, after.0); }
#[test]
fn is_set_matches_underlying_bit(a in bb(), s in sq()) {
let expected = (a.0 >> s.0) & 1 == 1;
prop_assert_eq!(a.is_set(s), expected);
}
#[test]
fn shl_zero_is_identity(a in bb()) {
prop_assert_eq!(a << Bitboard(0), a);
}
#[test]
fn shr_zero_is_identity(a in bb()) {
prop_assert_eq!(a >> Bitboard(0), a);
}
#[test]
fn shl_then_shr_clears_low_bits(a in bb(), n in 0u64..64) {
let shifted = (a << Bitboard(n)) >> Bitboard(n);
let expected = Bitboard((a.0 << n) >> n);
prop_assert_eq!(shifted, expected);
}
#[test]
fn bit_or_assign_matches_bit_or(a in bb(), b in bb()) {
let mut x = a;
x |= b;
prop_assert_eq!(x, a | b);
}
#[test]
fn from_u64_roundtrip(v in any::<u64>()) {
prop_assert_eq!(Bitboard::from(v).0, v);
}
#[test]
fn from_square_has_one_bit(s in sq()) {
let bb = Bitboard::from(s);
prop_assert_eq!(bb.0.count_ones(), 1);
prop_assert_eq!(bb.0, 1u64 << s.0);
}
#[test]
fn square_try_from_singleton_roundtrip(s in sq()) {
let bb = Bitboard::from(s);
prop_assert_eq!(Square::try_from(bb), Ok(s));
}
#[test]
fn square_try_from_fails_for_non_singletons(a in bb()) {
prop_assume!(a.0.count_ones() != 1);
prop_assert_eq!(Square::try_from(a), Err(()));
}
#[test]
fn from_rank_has_eight_bits(r in 0u32..8) {
let bb = Bitboard::from(Rank::new(r));
prop_assert_eq!(bb.0.count_ones(), 8);
}
#[test]
fn from_file_has_eight_bits(f in 0u32..8) {
let bb = Bitboard::from(File::new(f));
prop_assert_eq!(bb.0.count_ones(), 8);
}
#[test]
fn square_belongs_to_its_rank_and_file(s in sq()) {
let r = Bitboard::from(s.rank());
let f = Bitboard::from(s.file());
prop_assert!(r.is_set(s));
prop_assert!(f.is_set(s));
prop_assert_eq!(r & f, Bitboard::from(s));
}
#[test]
fn iter_yields_count_ones_items(a in bb()) {
let squares: Vec<Square> = a.into_iter().collect();
prop_assert_eq!(squares.len() as u32, a.0.count_ones());
}
#[test]
fn iter_yields_ascending_squares(a in bb()) {
let squares: Vec<Square> = a.into_iter().collect();
for w in squares.windows(2) {
prop_assert!(w[0].0 < w[1].0);
}
}
#[test]
fn iter_yields_only_set_bits(a in bb()) {
for s in a {
prop_assert!(a.is_set(s));
}
}
#[test]
fn iter_reconstructs_original(a in bb()) {
let mut acc = Bitboard::EMPTY;
for s in a {
acc |= Bitboard::from(s);
}
prop_assert_eq!(acc, a);
}
#[test]
fn empty_iter_yields_nothing(()in Just(())) {
prop_assert_eq!(Bitboard::EMPTY.into_iter().count(), 0);
}
}
}