2022-07-15 21:33:07 +02:00
|
|
|
use super::Square;
|
2022-07-18 11:05:38 +02:00
|
|
|
use crate::utils::static_assert;
|
|
|
|
|
2022-07-15 21:53:08 +02:00
|
|
|
mod iterator;
|
|
|
|
use iterator::*;
|
2022-07-21 20:25:29 +02:00
|
|
|
mod superset;
|
|
|
|
use superset::*;
|
2022-07-15 21:33:07 +02:00
|
|
|
|
|
|
|
/// Use a 64-bit number to represent a chessboard. Each bit is mapped from to a specific square, so
|
|
|
|
/// that index 0 -> A1, 1 -> A2, ..., 63 -> H8.
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub struct Bitboard(pub(crate) u64);
|
|
|
|
|
|
|
|
impl Bitboard {
|
|
|
|
/// An empty bitboard.
|
|
|
|
pub const EMPTY: Bitboard = Bitboard(0);
|
|
|
|
|
2022-07-15 21:58:46 +02:00
|
|
|
/// A full bitboard.
|
|
|
|
pub const ALL: Bitboard = Bitboard(u64::MAX);
|
|
|
|
|
2022-07-15 21:33:07 +02:00
|
|
|
/// Array of bitboards representing the eight ranks, in order from rank 1 to rank 8.
|
|
|
|
pub const RANKS: [Self; 8] = [
|
|
|
|
Bitboard(0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00000001),
|
|
|
|
Bitboard(0b00000010_00000010_00000010_00000010_00000010_00000010_00000010_00000010),
|
|
|
|
Bitboard(0b00000100_00000100_00000100_00000100_00000100_00000100_00000100_00000100),
|
|
|
|
Bitboard(0b00001000_00001000_00001000_00001000_00001000_00001000_00001000_00001000),
|
|
|
|
Bitboard(0b00010000_00010000_00010000_00010000_00010000_00010000_00010000_00010000),
|
|
|
|
Bitboard(0b00100000_00100000_00100000_00100000_00100000_00100000_00100000_00100000),
|
|
|
|
Bitboard(0b01000000_01000000_01000000_01000000_01000000_01000000_01000000_01000000),
|
|
|
|
Bitboard(0b10000000_10000000_10000000_10000000_10000000_10000000_10000000_10000000),
|
|
|
|
];
|
|
|
|
|
|
|
|
/// Array of bitboards representing the eight files, in order from file A to file H.
|
|
|
|
pub const FILES: [Self; 8] = [
|
|
|
|
Bitboard(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111),
|
|
|
|
Bitboard(0b00000000_00000000_00000000_00000000_00000000_00000000_11111111_00000000),
|
|
|
|
Bitboard(0b00000000_00000000_00000000_00000000_00000000_11111111_00000000_00000000),
|
|
|
|
Bitboard(0b00000000_00000000_00000000_00000000_11111111_00000000_00000000_00000000),
|
|
|
|
Bitboard(0b00000000_00000000_00000000_11111111_00000000_00000000_00000000_00000000),
|
|
|
|
Bitboard(0b00000000_00000000_11111111_00000000_00000000_00000000_00000000_00000000),
|
|
|
|
Bitboard(0b00000000_11111111_00000000_00000000_00000000_00000000_00000000_00000000),
|
|
|
|
Bitboard(0b11111111_00000000_00000000_00000000_00000000_00000000_00000000_00000000),
|
|
|
|
];
|
2022-07-17 20:39:47 +02:00
|
|
|
|
2022-07-18 00:27:08 +02:00
|
|
|
/// The diagonal from [Square::A1] to [Square::H8].
|
|
|
|
pub const DIAGONAL: Bitboard = Bitboard(0x8040201008040201);
|
|
|
|
|
|
|
|
/// The diagonal from [Square::A8] to [Square::H1].
|
|
|
|
pub const ANTI_DIAGONAL: Bitboard = Bitboard(0x0102040810204080);
|
|
|
|
|
2022-07-18 00:43:48 +02:00
|
|
|
/// The light [Square]s on a board, e.g: [Square::H1].
|
|
|
|
pub const LIGHT_SQUARES: Bitboard = Bitboard(0x55AA55AA55AA55AA);
|
|
|
|
|
|
|
|
/// The dark [Square]s on a board, e.g: [Square::A1].
|
|
|
|
pub const DARK_SQUARES: Bitboard = Bitboard(0x55AA55AA55AA55AA);
|
|
|
|
|
2022-07-17 20:39:47 +02:00
|
|
|
/// Count the number of pieces in the [Bitboard].
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn count(self) -> u32 {
|
|
|
|
self.0.count_ones()
|
|
|
|
}
|
2022-07-18 00:31:33 +02:00
|
|
|
|
|
|
|
/// Return true if there are no pieces in the [Bitboard], otherwise false.
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn is_empty(self) -> bool {
|
|
|
|
self == Self::EMPTY
|
|
|
|
}
|
2022-07-21 20:25:29 +02:00
|
|
|
|
|
|
|
/// Iterate over the power-set of a given [Bitboard], yielding each possible sub-set of
|
|
|
|
/// [Square] that belong to the [Bitboard]. In other words, generate all set of [Square] that
|
|
|
|
/// contain all, some, or none of the [Square] that are in the given [Bitboard].
|
|
|
|
/// If given an empty [Bitboard], yields the empty [Bitboard] back.
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn iter_power_set(self) -> impl Iterator<Item = Self> {
|
|
|
|
BitboardPowerSetIterator::new(self)
|
|
|
|
}
|
2022-07-15 21:33:07 +02:00
|
|
|
}
|
|
|
|
|
2022-07-18 11:05:38 +02:00
|
|
|
// Ensure zero-cost (at least size-wise) wrapping.
|
|
|
|
static_assert!(std::mem::size_of::<Bitboard>() == std::mem::size_of::<u64>());
|
|
|
|
|
2022-07-15 21:33:07 +02:00
|
|
|
impl Default for Bitboard {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::EMPTY
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-15 21:53:08 +02:00
|
|
|
/// Iterate over the [Square](crate::board::Square) values included in the board.
|
|
|
|
impl IntoIterator for Bitboard {
|
|
|
|
type IntoIter = BitboardIterator;
|
|
|
|
type Item = Square;
|
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
BitboardIterator(self.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-15 21:33:07 +02:00
|
|
|
/// Treat bitboard as a set of squares, shift each square's index left by the amount given.
|
|
|
|
impl std::ops::Shl<usize> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn shl(self, rhs: usize) -> Self::Output {
|
|
|
|
Bitboard(self.0 << rhs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat bitboard as a set of squares, shift each square's index right by the amount given.
|
|
|
|
impl std::ops::Shr<usize> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn shr(self, rhs: usize) -> Self::Output {
|
|
|
|
Bitboard(self.0 >> rhs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat bitboard as a set of squares, and invert the set.
|
|
|
|
impl std::ops::Not for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn not(self) -> Self::Output {
|
|
|
|
Bitboard(!self.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat each bitboard as a set of squares, keep squares that are in either sets.
|
|
|
|
impl std::ops::BitOr<Bitboard> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn bitor(self, rhs: Bitboard) -> Self::Output {
|
|
|
|
Bitboard(self.0 | rhs.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
|
|
|
impl std::ops::BitOr<Square> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn bitor(self, rhs: Square) -> Self::Output {
|
|
|
|
self | rhs.into_bitboard()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat each bitboard as a set of squares, keep squares that are in both sets.
|
|
|
|
impl std::ops::BitAnd<Bitboard> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn bitand(self, rhs: Bitboard) -> Self::Output {
|
|
|
|
Bitboard(self.0 & rhs.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
|
|
|
impl std::ops::BitAnd<Square> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn bitand(self, rhs: Square) -> Self::Output {
|
|
|
|
self & rhs.into_bitboard()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat each bitboard as a set of squares, keep squares that are in exactly one of either set.
|
|
|
|
impl std::ops::BitXor<Bitboard> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn bitxor(self, rhs: Bitboard) -> Self::Output {
|
|
|
|
Bitboard(self.0 ^ rhs.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
|
|
|
impl std::ops::BitXor<Square> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn bitxor(self, rhs: Square) -> Self::Output {
|
|
|
|
self ^ rhs.into_bitboard()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat each bitboard as a set of squares, and substract one set from another.
|
|
|
|
impl std::ops::Sub<Bitboard> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn sub(self, rhs: Bitboard) -> Self::Output {
|
|
|
|
Bitboard(self.0 & !rhs.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Treat the [Square](crate::board::Square) as a singleton bitboard, and apply the operator.
|
|
|
|
impl std::ops::Sub<Square> for Bitboard {
|
|
|
|
type Output = Bitboard;
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn sub(self, rhs: Square) -> Self::Output {
|
|
|
|
self - rhs.into_bitboard()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2022-07-21 20:25:29 +02:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
2022-07-15 21:33:07 +02:00
|
|
|
use super::*;
|
2022-07-21 20:25:29 +02:00
|
|
|
use crate::board::{square::*, File, Rank};
|
2022-07-15 21:33:07 +02:00
|
|
|
|
2022-07-17 20:39:47 +02:00
|
|
|
#[test]
|
|
|
|
fn count() {
|
|
|
|
assert_eq!(Bitboard::EMPTY.count(), 0);
|
|
|
|
assert_eq!(Bitboard::FILES[0].count(), 8);
|
|
|
|
assert_eq!(Bitboard::ALL.count(), 64);
|
|
|
|
}
|
|
|
|
|
2022-07-15 21:53:08 +02:00
|
|
|
#[test]
|
|
|
|
fn iter() {
|
|
|
|
assert_eq!(Bitboard::EMPTY.into_iter().collect::<Vec<_>>(), Vec::new());
|
|
|
|
assert_eq!(
|
|
|
|
Bitboard::RANKS[0].into_iter().collect::<Vec<_>>(),
|
|
|
|
vec![
|
|
|
|
Square::A1,
|
|
|
|
Square::B1,
|
|
|
|
Square::C1,
|
|
|
|
Square::D1,
|
|
|
|
Square::E1,
|
|
|
|
Square::F1,
|
|
|
|
Square::G1,
|
|
|
|
Square::H1,
|
|
|
|
]
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Bitboard::FILES[0].into_iter().collect::<Vec<_>>(),
|
|
|
|
vec![
|
|
|
|
Square::A1,
|
|
|
|
Square::A2,
|
|
|
|
Square::A3,
|
|
|
|
Square::A4,
|
|
|
|
Square::A5,
|
|
|
|
Square::A6,
|
|
|
|
Square::A7,
|
|
|
|
Square::A8,
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-07-15 21:33:07 +02:00
|
|
|
#[test]
|
|
|
|
fn left_shift() {
|
|
|
|
assert_eq!(Bitboard::RANKS[0] << 1, Bitboard::RANKS[1]);
|
|
|
|
assert_eq!(Bitboard::FILES[0] << 8, Bitboard::FILES[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn right_shift() {
|
|
|
|
assert_eq!(Bitboard::RANKS[1] >> 1, Bitboard::RANKS[0]);
|
|
|
|
assert_eq!(Bitboard::FILES[1] >> 8, Bitboard::FILES[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn not() {
|
2022-07-15 21:58:46 +02:00
|
|
|
assert_eq!(!Bitboard::EMPTY, Bitboard::ALL);
|
2022-07-15 21:33:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn or() {
|
|
|
|
assert_eq!(Bitboard::FILES[0] | Bitboard::FILES[1], Bitboard(0xff_ff));
|
|
|
|
assert_eq!(Bitboard::FILES[0] | Square::B1, Bitboard(0x1_ff));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn and() {
|
|
|
|
assert_eq!(Bitboard::FILES[0] & Bitboard::FILES[1], Bitboard::EMPTY);
|
|
|
|
assert_eq!(
|
|
|
|
Bitboard::FILES[0] & Bitboard::RANKS[0],
|
|
|
|
Square::A1.into_bitboard()
|
|
|
|
);
|
|
|
|
assert_eq!(Bitboard::FILES[0] & Square::A1, Square::A1.into_bitboard());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn xor() {
|
|
|
|
assert_eq!(Bitboard::FILES[0] ^ Square::A1, Bitboard(0xff - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn sub() {
|
|
|
|
assert_eq!(Bitboard::FILES[0] - Bitboard::RANKS[0], Bitboard(0xff - 1));
|
|
|
|
assert_eq!(Bitboard::FILES[0] - Square::A1, Bitboard(0xff - 1));
|
|
|
|
}
|
2022-07-21 20:25:29 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn iter_power_set_empty() {
|
|
|
|
assert_eq!(
|
|
|
|
Bitboard::EMPTY.iter_power_set().collect::<Vec<_>>(),
|
|
|
|
vec![Bitboard::EMPTY]
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn iter_power_set_one_square() {
|
|
|
|
for square in Square::iter() {
|
|
|
|
assert_eq!(
|
|
|
|
square
|
|
|
|
.into_bitboard()
|
|
|
|
.iter_power_set()
|
|
|
|
.collect::<HashSet<_>>(),
|
|
|
|
[Bitboard::EMPTY, square.into_bitboard()]
|
|
|
|
.into_iter()
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn iter_power_set_two_squares() {
|
|
|
|
assert_eq!(
|
|
|
|
(Square::A1 | Square::H8)
|
|
|
|
.iter_power_set()
|
|
|
|
.collect::<HashSet<_>>(),
|
|
|
|
[
|
|
|
|
Bitboard::EMPTY,
|
|
|
|
Square::A1.into_bitboard(),
|
|
|
|
Square::H8.into_bitboard(),
|
|
|
|
Square::A1 | Square::H8
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn iter_power_set_six_squares_exhaustive() {
|
|
|
|
let mask = (0..6)
|
|
|
|
.map(Square::from_index)
|
|
|
|
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs);
|
|
|
|
assert_eq!(
|
|
|
|
mask.iter_power_set().collect::<HashSet<_>>(),
|
|
|
|
(0..(1 << 6)).map(Bitboard).collect::<HashSet<_>>()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn iter_power_set_eight_squares_length() {
|
|
|
|
assert_eq!(
|
|
|
|
File::A
|
|
|
|
.into_bitboard()
|
|
|
|
.iter_power_set()
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
.len(),
|
|
|
|
1 << 8
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Rank::First
|
|
|
|
.into_bitboard()
|
|
|
|
.iter_power_set()
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
.len(),
|
|
|
|
1 << 8
|
|
|
|
);
|
|
|
|
}
|
2022-07-15 21:33:07 +02:00
|
|
|
}
|