diff --git a/src/board/bitboard.rs b/src/board/bitboard.rs new file mode 100644 index 0000000..50298d9 --- /dev/null +++ b/src/board/bitboard.rs @@ -0,0 +1,201 @@ +use super::Square; + +/// 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); + + /// 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), + ]; +} + +impl Default for Bitboard { + fn default() -> Self { + Self::EMPTY + } +} + +/// Treat bitboard as a set of squares, shift each square's index left by the amount given. +impl std::ops::Shl 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 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 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 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 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 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 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 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 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 for Bitboard { + type Output = Bitboard; + + #[inline(always)] + fn sub(self, rhs: Square) -> Self::Output { + self - rhs.into_bitboard() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::board::square::*; + + #[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() { + assert_eq!(!Bitboard::EMPTY, Bitboard(u64::MAX)); + } + + #[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)); + } +} diff --git a/src/board/mod.rs b/src/board/mod.rs new file mode 100644 index 0000000..06a7d91 --- /dev/null +++ b/src/board/mod.rs @@ -0,0 +1,5 @@ +pub mod bitboard; +pub use bitboard::*; + +pub mod square; +pub use square::*; diff --git a/src/board/square.rs b/src/board/square.rs new file mode 100644 index 0000000..640214c --- /dev/null +++ b/src/board/square.rs @@ -0,0 +1,214 @@ +use super::Bitboard; + +/// Represent a square on a chessboard. Defined in the same order as the +/// [Bitboard](crate::board::Bitboard) squares. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[rustfmt::skip] +pub enum Square { + A1, A2, A3, A4, A5, A6, A7, A8, + B1, B2, B3, B4, B5, B6, B7, B8, + C1, C2, C3, C4, C5, C6, C7, C8, + D1, D2, D3, D4, D5, D6, D7, D8, + E1, E2, E3, E4, E5, E6, E7, E8, + F1, F2, F3, F4, F5, F6, F7, F8, + G1, G2, G3, G4, G5, G6, G7, G8, + H1, H2, H3, H4, H5, H6, H7, H8, +} + +impl std::fmt::Display for Square { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl Square { + #[rustfmt::skip] + const ALL: [Self; 64] = [ + Self::A1, Self::A2, Self::A3, Self::A4, Self::A5, Self::A6, Self::A7, Self::A8, + Self::B1, Self::B2, Self::B3, Self::B4, Self::B5, Self::B6, Self::B7, Self::B8, + Self::C1, Self::C2, Self::C3, Self::C4, Self::C5, Self::C6, Self::C7, Self::C8, + Self::D1, Self::D2, Self::D3, Self::D4, Self::D5, Self::D6, Self::D7, Self::D8, + Self::E1, Self::E2, Self::E3, Self::E4, Self::E5, Self::E6, Self::E7, Self::E8, + Self::F1, Self::F2, Self::F3, Self::F4, Self::F5, Self::F6, Self::F7, Self::F8, + Self::G1, Self::G2, Self::G3, Self::G4, Self::G5, Self::G6, Self::G7, Self::G8, + Self::H1, Self::H2, Self::H3, Self::H4, Self::H5, Self::H6, Self::H7, Self::H8, + ]; + + /// Iterate over all squares in order. + pub fn iter() -> impl Iterator { + Self::ALL.iter().cloned() + } + + /// Convert from a square index into a [Square] type. + #[inline(always)] + pub fn from_index(index: usize) -> Self { + assert!(index < 64); + // SAFETY: we know the value is in-bounds + unsafe { Self::from_index_unchecked(index) } + } + + /// Convert from a square index into a [Square] type, no bounds checking. + /// + /// # Safety + /// + /// Should only be called with values that can be output by [Square::index()]. + #[inline(always)] + pub unsafe fn from_index_unchecked(index: usize) -> Self { + std::mem::transmute(index as u8) + } + + /// Return the index of the rank of this square (0 -> rank 1, ..., 7 -> rank 8). + #[inline(always)] + pub fn rank_index(self) -> usize { + (self as usize) % 8 + } + + /// Return the index of the rank of this square (0 -> file A, ..., 7 -> file H). + #[inline(always)] + pub fn file_index(self) -> usize { + (self as usize) / 8 + } + + /// Return a bitboard representing the rank of this square. + #[inline(always)] + pub fn rank(self) -> Bitboard { + Bitboard::RANKS[self.rank_index()] + } + + /// Return a bitboard representing the rank of this square. + #[inline(always)] + pub fn file(self) -> Bitboard { + Bitboard::FILES[self.file_index()] + } + + /// Turn a square into a singleton bitboard. + #[inline(always)] + pub fn into_bitboard(self) -> Bitboard { + Bitboard(1 << (self as usize)) + } +} + +/// Shift the square's index left by the amount given. +impl std::ops::Shl for Square { + type Output = Square; + + #[inline(always)] + fn shl(self, rhs: usize) -> Self::Output { + #[allow(clippy::suspicious_arithmetic_impl)] + Square::from_index(self as usize + rhs) + } +} + +/// Shift the square's index right by the amount given. +impl std::ops::Shr for Square { + type Output = Square; + + #[inline(always)] + fn shr(self, rhs: usize) -> Self::Output { + #[allow(clippy::suspicious_arithmetic_impl)] + Square::from_index(self as usize - rhs) + } +} + +/// Return a board containing all squares but the one given. +impl std::ops::Not for Square { + type Output = Bitboard; + + #[inline(always)] + fn not(self) -> Self::Output { + !self.into_bitboard() + } +} + +/// Treat the square as a singleton board, and apply the operator. +impl std::ops::BitOr for Square { + type Output = Bitboard; + + #[inline(always)] + fn bitor(self, rhs: Square) -> Self::Output { + self.into_bitboard() | rhs.into_bitboard() + } +} + +/// Treat the square as a singleton board, and apply the operator. +impl std::ops::BitOr for Square { + type Output = Bitboard; + + #[inline(always)] + fn bitor(self, rhs: Bitboard) -> Self::Output { + self.into_bitboard() | rhs + } +} + +/// Treat the square as a singleton board, and apply the operator. +impl std::ops::BitAnd for Square { + type Output = Bitboard; + + #[inline(always)] + fn bitand(self, rhs: Bitboard) -> Self::Output { + self.into_bitboard() & rhs + } +} + +/// Treat the square as a singleton board, and apply the operator. +impl std::ops::BitXor for Square { + type Output = Bitboard; + + #[inline(always)] + fn bitxor(self, rhs: Bitboard) -> Self::Output { + self.into_bitboard() ^ rhs + } +} + +/// Treat the square as a singleton board, and apply the operator. +impl std::ops::Sub for Square { + type Output = Bitboard; + + #[inline(always)] + fn sub(self, rhs: Bitboard) -> Self::Output { + self.into_bitboard() - rhs + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::board::bitboard::*; + + #[test] + fn left_shift() { + assert_eq!(Square::A1 << 1, Square::A2); + assert_eq!(Square::A1 << 8, Square::B1); + } + + #[test] + fn right_shift() { + assert_eq!(Square::A2 >> 1, Square::A1); + assert_eq!(Square::B1 >> 8, Square::A1); + } + + #[test] + fn not() { + assert_eq!(!Square::A1, Bitboard(u64::MAX - 1)); + } + + #[test] + fn or() { + assert_eq!(Square::A1 | Square::A2, Bitboard(0b11)); + } + + #[test] + fn and() { + assert_eq!(Square::A1 & Bitboard::FILES[0], Square::A1.into_bitboard()); + } + + #[test] + fn xor() { + assert_eq!(Square::A1 ^ Bitboard::FILES[0], Bitboard(0xff - 1)); + } + + #[test] + fn sub() { + assert_eq!(Square::A1 - Bitboard::FILES[0], Bitboard::EMPTY); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..667c357 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod board;