diff --git a/src/board/castle_rights.rs b/src/board/castle_rights.rs new file mode 100644 index 0000000..0e44e03 --- /dev/null +++ b/src/board/castle_rights.rs @@ -0,0 +1,196 @@ +use super::{Bitboard, Color, File, Square}; + +/// Current castle rights for a player. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CastleRights { + /// No castling allowed. + NoSide, + /// King-side castling only. + KingSide, + /// Queen-side castling only. + QueenSide, + /// Either side allowed. + BothSides, +} + +impl CastleRights { + /// Convert from a castle rights index into a [CastleRights] type. + #[inline(always)] + pub fn from_index(index: usize) -> Self { + assert!(index < 4); + // SAFETY: we know the value is in-bounds + unsafe { Self::from_index_unchecked(index) } + } + + /// Convert from a castle rights index into a [CastleRights] type, no bounds checking. + #[inline(always)] + pub unsafe fn from_index_unchecked(index: usize) -> Self { + std::mem::transmute(index as u8) + } + + /// Return the index of a given [CastleRights]. + #[inline(always)] + pub fn index(self) -> usize { + self as usize + } + + /// Can the player castle king-side. + #[inline(always)] + pub fn has_king_side(self) -> bool { + (self.index() & 1) != 0 + } + + /// Can the player castle king-side. + #[inline(always)] + pub fn has_queen_side(self) -> bool { + (self.index() & 2) != 0 + } + + /// Remove king-side castling rights. + #[inline(always)] + pub fn without_king_side(self) -> Self { + self.remove(Self::KingSide) + } + + /// Remove queen-side castling rights. + #[inline(always)] + pub fn without_queen_side(self) -> Self { + self.remove(Self::QueenSide) + } + + /// Remove some [CastleRights], and return the resulting [CastleRights]. + #[inline(always)] + pub fn remove(self, to_remove: CastleRights) -> Self { + // SAFETY: we know the value is in-bounds + unsafe { Self::from_index_unchecked(self.index() & !to_remove.index()) } + } + + /// Which rooks have not been moved for a given [CastleRights] and [Color]. + #[inline(always)] + pub fn unmoved_rooks(self, color: Color) -> Bitboard { + let rank = color.first_rank(); + + let king_side_square = Square::new(File::H, rank); + let queen_side_square = Square::new(File::A, rank); + + match self { + Self::NoSide => Bitboard::EMPTY, + Self::KingSide => king_side_square.into_bitboard(), + Self::QueenSide => queen_side_square.into_bitboard(), + Self::BothSides => king_side_square | queen_side_square, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn from_index() { + assert_eq!(CastleRights::from_index(0), CastleRights::NoSide); + assert_eq!(CastleRights::from_index(1), CastleRights::KingSide); + assert_eq!(CastleRights::from_index(2), CastleRights::QueenSide); + assert_eq!(CastleRights::from_index(3), CastleRights::BothSides); + } + + #[test] + fn index() { + assert_eq!(CastleRights::NoSide.index(), 0); + assert_eq!(CastleRights::KingSide.index(), 1); + assert_eq!(CastleRights::QueenSide.index(), 2); + assert_eq!(CastleRights::BothSides.index(), 3); + } + + #[test] + fn has_kingside() { + assert!(!CastleRights::NoSide.has_king_side()); + assert!(!CastleRights::QueenSide.has_king_side()); + assert!(CastleRights::KingSide.has_king_side()); + assert!(CastleRights::BothSides.has_king_side()); + } + + #[test] + fn has_queenside() { + assert!(!CastleRights::NoSide.has_queen_side()); + assert!(!CastleRights::KingSide.has_queen_side()); + assert!(CastleRights::QueenSide.has_queen_side()); + assert!(CastleRights::BothSides.has_queen_side()); + } + + #[test] + fn without_king_side() { + assert_eq!( + CastleRights::NoSide.without_king_side(), + CastleRights::NoSide + ); + assert_eq!( + CastleRights::KingSide.without_king_side(), + CastleRights::NoSide + ); + assert_eq!( + CastleRights::QueenSide.without_king_side(), + CastleRights::QueenSide + ); + assert_eq!( + CastleRights::BothSides.without_king_side(), + CastleRights::QueenSide + ); + } + + #[test] + fn without_queen_side() { + assert_eq!( + CastleRights::NoSide.without_queen_side(), + CastleRights::NoSide + ); + assert_eq!( + CastleRights::QueenSide.without_queen_side(), + CastleRights::NoSide + ); + assert_eq!( + CastleRights::KingSide.without_queen_side(), + CastleRights::KingSide + ); + assert_eq!( + CastleRights::BothSides.without_queen_side(), + CastleRights::KingSide + ); + } + + #[test] + fn unmoved_rooks() { + assert_eq!( + CastleRights::NoSide.unmoved_rooks(Color::White), + Bitboard::EMPTY + ); + assert_eq!( + CastleRights::NoSide.unmoved_rooks(Color::Black), + Bitboard::EMPTY + ); + assert_eq!( + CastleRights::KingSide.unmoved_rooks(Color::White), + Square::H1.into_bitboard() + ); + assert_eq!( + CastleRights::KingSide.unmoved_rooks(Color::Black), + Square::H8.into_bitboard() + ); + assert_eq!( + CastleRights::QueenSide.unmoved_rooks(Color::White), + Square::A1.into_bitboard() + ); + assert_eq!( + CastleRights::QueenSide.unmoved_rooks(Color::Black), + Square::A8.into_bitboard() + ); + assert_eq!( + CastleRights::BothSides.unmoved_rooks(Color::White), + Square::A1 | Square::H1 + ); + assert_eq!( + CastleRights::BothSides.unmoved_rooks(Color::Black), + Square::A8 | Square::H8 + ); + } +} diff --git a/src/board/color.rs b/src/board/color.rs new file mode 100644 index 0000000..a7af8b6 --- /dev/null +++ b/src/board/color.rs @@ -0,0 +1,102 @@ +use super::Rank; + +/// An enum representing the color of a player. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Color { + White, + Black, +} + +impl Color { + /// Convert from a file index into a [Color] type. + #[inline(always)] + pub fn from_index(index: usize) -> Self { + assert!(index < 2); + // SAFETY: we know the value is in-bounds + unsafe { Self::from_index_unchecked(index) } + } + + /// Convert from a file index into a [Color] type, no bounds checking. + #[inline(always)] + pub unsafe fn from_index_unchecked(index: usize) -> Self { + std::mem::transmute(index as u8) + } + + /// Return the index of a given [Color]. + #[inline(always)] + pub fn index(self) -> usize { + self as usize + } + + /// Return the first [Rank] for pieces of the given [Color], where its pieces start. + #[inline(always)] + pub fn first_rank(self) -> Rank { + match self { + Self::White => Rank::First, + Self::Black => Rank::Eighth, + } + } + + /// Return the second [Rank] for pieces of the given [Color], where its pawns start. + #[inline(always)] + pub fn second_rank(self) -> Rank { + match self { + Self::White => Rank::Second, + Self::Black => Rank::Seventh, + } + } + + /// Return the fourth [Rank] for pieces of the given [Color], where its pawns move to after a + /// two-square move. + #[inline(always)] + pub fn fourth_rank(self) -> Rank { + match self { + Self::White => Rank::Fourth, + Self::Black => Rank::Fifth, + } + } + + /// Return the seventh [Rank] for pieces of the given [Color], which is the rank before a pawn + /// gets promoted. + #[inline(always)] + pub fn seventh_rank(self) -> Rank { + match self { + Self::White => Rank::Seventh, + Self::Black => Rank::Second, + } + } +} + +impl std::ops::Not for Color { + type Output = Color; + + fn not(self) -> Self::Output { + match self { + Self::White => Self::Black, + Self::Black => Self::White, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn from_index() { + assert_eq!(Color::from_index(0), Color::White); + assert_eq!(Color::from_index(1), Color::Black); + } + + #[test] + fn index() { + assert_eq!(Color::White.index(), 0); + assert_eq!(Color::Black.index(), 1); + } + + #[test] + fn not() { + assert_eq!(!Color::White, Color::Black); + assert_eq!(!Color::Black, Color::White); + } +} diff --git a/src/board/directions.rs b/src/board/direction.rs similarity index 100% rename from src/board/directions.rs rename to src/board/direction.rs diff --git a/src/board/mod.rs b/src/board/mod.rs index bc9d1d9..ad91192 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -1,8 +1,14 @@ pub mod bitboard; pub use bitboard::*; -pub mod directions; -pub use directions::*; +pub mod castle_rights; +pub use castle_rights::*; + +pub mod color; +pub use color::*; + +pub mod direction; +pub use direction::*; pub mod file; pub use file::*;