chessfriend/bitboard/src/bitboard.rs
Eryn Wells 2480ef25e9 Remove BitBoardBuilder
It's unused except for the macro, and BitBoard itself can be declared mutable,
and implements Copy and Clone. So, I don't think having a separate Builder type
helps much.
2024-07-13 07:05:57 -07:00

479 lines
13 KiB
Rust

// Eryn Wells <eryn@erynwells.me>
use crate::library;
use crate::{LeadingBitScanner, TrailingBitScanner};
use chessfriend_core::{Color, Direction, File, Rank, Square};
use std::fmt;
use std::ops::Not;
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct BitBoard(pub(crate) u64);
macro_rules! moves_getter {
($getter_name:ident) => {
pub fn $getter_name(sq: Square) -> BitBoard {
library::library().$getter_name(sq)
}
};
}
impl BitBoard {
pub const EMPTY: BitBoard = BitBoard(u64::MIN);
pub const FULL: BitBoard = BitBoard(u64::MAX);
pub const fn empty() -> BitBoard {
BitBoard(0)
}
pub const fn new(bits: u64) -> BitBoard {
BitBoard(bits)
}
pub fn rank(rank: &u8) -> BitBoard {
debug_assert!(*rank < 8);
library::RANKS[*rank as usize]
}
pub fn file(file: &u8) -> BitBoard {
debug_assert!(*file < 8);
library::FILES[*file as usize]
}
pub fn ray(sq: Square, dir: Direction) -> &'static BitBoard {
library::library().ray(sq, dir)
}
pub fn pawn_attacks(sq: Square, color: Color) -> BitBoard {
library::library().pawn_attacks(sq, color)
}
pub fn pawn_pushes(sq: Square, color: Color) -> BitBoard {
library::library().pawn_pushes(sq, color)
}
moves_getter!(knight_moves);
moves_getter!(bishop_moves);
moves_getter!(rook_moves);
moves_getter!(queen_moves);
moves_getter!(king_moves);
pub const fn kingside(color: Color) -> &'static BitBoard {
&library::KINGSIDES[color as usize]
}
pub const fn queenside(color: Color) -> &'static BitBoard {
&library::QUEENSIDES[color as usize]
}
}
impl BitBoard {
pub const fn as_bits(&self) -> &u64 {
&self.0
}
/// Returns `true` if the [`BitBoard`] has no bits set.
///
/// ## Examples
///
/// ```
/// use chessfriend_bitboard::BitBoard;
/// assert!(BitBoard::EMPTY.is_populated());
/// assert!(!BitBoard::FULL.is_populated());
/// assert!(!BitBoard::new(0b1000).is_populated());
/// ```
pub const fn is_empty(&self) -> bool {
self.0 == 0
}
/// Returns `true` if the [`BitBoard`] has at least one bit set.
///
/// ## Examples
///
/// ```
/// use chessfriend_bitboard::BitBoard;
/// assert!(!BitBoard::EMPTY.is_populated());
/// assert!(BitBoard::FULL.is_populated());
/// assert!(BitBoard::new(0b1).is_populated());
/// ```
pub const fn is_populated(&self) -> bool {
self.0 != 0
}
/// Returns `true` if this [`BitBoard`] has the bit corresponding to `square` set.
pub fn is_set(self, square: Square) -> bool {
let square_bitboard: BitBoard = square.into();
!(self & square_bitboard).is_empty()
}
/// The number of 1 bits in the BitBoard.
///
/// ## Examples
///
/// ```
/// use chessfriend_bitboard::BitBoard;
/// assert_eq!(BitBoard::EMPTY.population_count(), 0);
/// assert_eq!(BitBoard::new(0b01011110010).population_count(), 6);
/// assert_eq!(BitBoard::FULL.population_count(), 64);
/// ```
pub const fn population_count(&self) -> u32 {
self.0.count_ones()
}
pub fn set_square(&mut self, sq: Square) {
let sq_bb: BitBoard = sq.into();
*self |= sq_bb
}
pub fn clear_square(&mut self, sq: Square) {
let sq_bb: BitBoard = sq.into();
*self &= !sq_bb
}
/// Returns `true` if this BitBoard represents a single square.
///
/// ## Examples
///
/// ```
/// use chessfriend_bitboard::BitBoard;
/// assert!(!BitBoard::EMPTY.is_single_square(), "Empty bitboards represent no squares");
/// assert!(!BitBoard::FULL.is_single_square(), "Full bitboards represent all the squares");
/// assert!(!BitBoard::new(0b010011110101101100).is_single_square(), "This bitboard represents a bunch of squares");
/// assert!(BitBoard::new(0b10000000000000).is_single_square());
/// ```
pub fn is_single_square(&self) -> bool {
self.0.is_power_of_two()
}
}
impl BitBoard {
/// Returns an Iterator over the occupied squares.
///
/// The Iterator yields squares starting from the leading (most-significant bit) end of the
/// board to the trailing (least-significant bit) end.
#[must_use]
pub fn occupied_squares(&self) -> impl Iterator<Item = Square> {
LeadingBitScanner::new(self.0).map(|idx| unsafe { Square::from_index(idx as u8) })
}
/// Return an Iterator over the occupied squares, starting from the trailing
/// (least-significant bit) end of the field.
#[must_use]
pub fn occupied_squares_trailing(&self) -> impl Iterator<Item = Square> {
TrailingBitScanner::new(self.0).map(|idx| unsafe { Square::from_index(idx as u8) })
}
#[must_use]
pub fn first_occupied_square(&self) -> Option<Square> {
let leading_zeros = self.0.leading_zeros() as u8;
if leading_zeros < Square::NUM as u8 {
unsafe { Some(Square::from_index(Square::NUM as u8 - leading_zeros - 1)) }
} else {
None
}
}
#[must_use]
pub fn first_occupied_square_trailing(&self) -> Option<Square> {
let trailing_zeros = self.0.trailing_zeros() as u8;
if trailing_zeros < Square::NUM as u8 {
unsafe { Some(Square::from_index(trailing_zeros)) }
} else {
None
}
}
}
impl Default for BitBoard {
fn default() -> Self {
BitBoard::EMPTY
}
}
impl From<File> for BitBoard {
fn from(value: File) -> Self {
library::FILES[*value.as_index() as usize]
}
}
impl From<Option<Square>> for BitBoard {
fn from(value: Option<Square>) -> Self {
value.map_or(BitBoard::EMPTY, Into::<BitBoard>::into)
}
}
impl From<Rank> for BitBoard {
fn from(value: Rank) -> Self {
library::FILES[*value.as_index() as usize]
}
}
impl From<Square> for BitBoard {
fn from(value: Square) -> Self {
BitBoard(1u64 << value as u32)
}
}
impl FromIterator<Square> for BitBoard {
fn from_iter<T: IntoIterator<Item = Square>>(iter: T) -> Self {
let mut builder = BitBoardBuilder::empty();
for sq in iter {
builder = builder.square(sq)
}
builder.build()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TryFromBitBoardError {
NotSingleSquare,
}
impl TryFrom<BitBoard> for Square {
type Error = TryFromBitBoardError;
fn try_from(value: BitBoard) -> Result<Self, Self::Error> {
if !value.is_single_square() {
return Err(TryFromBitBoardError::NotSingleSquare);
}
unsafe { Ok(Square::from_index(value.0.trailing_zeros() as u8)) }
}
}
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::<Vec<String>>();
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> {
write!(f, "BitBoard({:064b})", self.0)
}
}
macro_rules! infix_op {
($trait_type:ident, $func_name:ident, $type:ty) => {
infix_op!($trait_type, $func_name, $type, $type);
infix_op!($trait_type, $func_name, $type, &$type);
infix_op!($trait_type, $func_name, &$type, $type);
infix_op!($trait_type, $func_name, &$type, &$type);
};
($trait_type:ident, $func_name:ident, $left_type:ty, $right_type:ty) => {
impl std::ops::$trait_type<$right_type> for $left_type {
type Output = BitBoard;
#[inline]
fn $func_name(self, rhs: $right_type) -> Self::Output {
BitBoard(std::ops::$trait_type::$func_name(self.0, rhs.0))
}
}
};
}
macro_rules! assign_op {
($trait_type:ident, $func_name:ident, $type:ty) => {
impl std::ops::$trait_type for $type {
#[inline]
fn $func_name(&mut self, rhs: $type) {
std::ops::$trait_type::$func_name(&mut self.0, rhs.0)
}
}
impl std::ops::$trait_type<&$type> for $type {
#[inline]
fn $func_name(&mut self, rhs: &$type) {
std::ops::$trait_type::$func_name(&mut self.0, rhs.0)
}
}
};
}
infix_op!(BitAnd, bitand, BitBoard);
infix_op!(BitOr, bitor, BitBoard);
infix_op!(BitXor, bitxor, BitBoard);
assign_op!(BitAndAssign, bitand_assign, BitBoard);
assign_op!(BitOrAssign, bitor_assign, BitBoard);
assign_op!(BitXorAssign, bitxor_assign, 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::bitboard;
use chessfriend_core::Square;
#[test]
#[ignore]
fn display_and_debug() {
let bb = BitBoard::file(&0) | BitBoard::file(&3) | BitBoard::rank(&7) | BitBoard::rank(&4);
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.is_set(Square::C1));
assert!(!bb.is_set(Square::B1));
}
#[test]
fn set_square() {
let sq = Square::E4;
let mut bb = BitBoard(0b1001100);
bb.set_square(sq);
assert!(bb.is_set(sq));
}
#[test]
fn clear_square() {
let sq = Square::A3;
let mut bb = BitBoard(0b1001100);
bb.clear_square(sq);
assert!(!bb.is_set(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);
}
}
#[test]
fn xor() {
let a = bitboard![C5 G7];
let b = bitboard![B5 G7 H3];
assert_eq!(a ^ b, bitboard![B5 C5 H3]);
assert_eq!(a ^ BitBoard::empty(), a);
assert_eq!(BitBoard::empty() ^ BitBoard::empty(), BitBoard::empty());
}
#[test]
fn bitand_assign() {
let mut a = bitboard![C5 G7];
let b = bitboard![B5 G7 H3];
a &= b;
assert_eq!(a, bitboard![G7]);
}
#[test]
fn bitor_assign() {
let mut a = bitboard![C5 G7];
let b = bitboard![B5 G7 H3];
a |= b;
assert_eq!(a, bitboard![B5 C5 G7 H3]);
}
#[test]
fn from_square() {
assert_eq!(BitBoard::from(Square::A1), BitBoard(0b1));
assert_eq!(BitBoard::from(Square::H8), BitBoard(1 << 63));
}
#[test]
fn first_occupied_squares() {
let bb = bitboard![A8 E1];
assert_eq!(bb.first_occupied_square(), Some(Square::A8));
assert_eq!(bb.first_occupied_square_trailing(), Some(Square::E1));
let bb = bitboard![D6 E7 F8];
assert_eq!(bb.first_occupied_square_trailing(), Some(Square::D6));
}
}