From c55b7c4877e12a828fc54cf5f372564074548602 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 9 Feb 2024 20:00:47 -0800 Subject: [PATCH] Implement a whole new move crate --- moves/src/builder.rs | 288 +++++++++++++++++++++++++++++++++++++++--- moves/src/castle.rs | 141 +++++++++------------ moves/src/defs.rs | 34 +++++ moves/src/lib.rs | 5 +- moves/src/moves.rs | 73 ++++------- moves/tests/flags.rs | 104 +++++++++++++++ moves/tests/pushes.rs | 15 +++ 7 files changed, 512 insertions(+), 148 deletions(-) create mode 100644 moves/src/defs.rs create mode 100644 moves/tests/flags.rs create mode 100644 moves/tests/pushes.rs diff --git a/moves/src/builder.rs b/moves/src/builder.rs index 9340497..780ed73 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -1,9 +1,35 @@ // Eryn Wells -use crate::{castle, Move}; -use chessfriend_core::{PlacedPiece, Square}; +use crate::{castle, defs::Kind, Move, PromotionShape}; +use chessfriend_core::{Color, File, PlacedPiece, Rank, Square}; +use std::result::Result as StdResult; -pub trait Style {} +pub type Result = std::result::Result; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Error { + MissingOriginSquare, + MissingTargetSquare, + MissingCaptureSquare, + InvalidEnPassantSquare, +} + +pub trait Style { + fn origin_square(&self) -> Option { + None + } + + fn target_square(&self) -> Option { + None + } + + fn into_move_bits(&self) -> StdResult { + let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)? as u16; + let target_square = self.target_square().ok_or(Error::MissingTargetSquare)? as u16; + + Ok((origin_square & 0b111111) << 4 | (target_square & 0b111111) << 10) + } +} #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Builder { @@ -19,67 +45,241 @@ pub struct Push { to: Option, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct DoublePush { + from: Square, + to: Square, +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Capture { push: Push, capture: Option, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct EnPassantCapture { + push: Push, + capture: Option, +} + +pub struct Promotion { + style: S, + promotion: PromotionShape, +} + pub struct Castle { castle: castle::Castle, } impl Style for Null {} -impl Style for Push {} -impl Style for Capture {} + +impl Style for Push { + fn origin_square(&self) -> Option { + self.from + } + + fn target_square(&self) -> Option { + self.to + } +} + +impl Style for Capture { + fn origin_square(&self) -> Option { + self.push.from + } + + fn target_square(&self) -> Option { + self.push.to + } +} + impl Style for Castle {} +impl Style for DoublePush { + fn origin_square(&self) -> Option { + Some(self.from) + } + + fn target_square(&self) -> Option { + Some(self.to) + } + + fn into_move_bits(&self) -> StdResult { + Ok(Kind::DoublePush as u16 + | (self.from as u16 & 0b111111) << 4 + | (self.to as u16 & 0b111111) << 10) + } +} + +impl Style for EnPassantCapture { + fn origin_square(&self) -> Option { + self.push.from + } + + fn target_square(&self) -> Option { + self.push.to + } + + fn into_move_bits(&self) -> StdResult { + let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)? as u16; + let target_square = self.target_square().ok_or(Error::MissingTargetSquare)? as u16; + + Ok((origin_square & 0b111111) << 4 | (target_square & 0b111111) << 10) + } +} + +impl Style for Promotion { + fn origin_square(&self) -> Option { + self.style.from + } + + fn target_square(&self) -> Option { + self.style.to + } +} + +impl Style for Promotion { + fn origin_square(&self) -> Option { + self.style.push.from + } + + fn target_square(&self) -> Option { + self.style.push.to + } +} + +impl Promotion { + fn into_move_bits(&self) -> StdResult { + let origin_square = self + .style + .origin_square() + .ok_or(Error::MissingOriginSquare)? as u16; + let target_square = self + .style + .target_square() + .ok_or(Error::MissingTargetSquare)? as u16; + + Ok(Kind::Promotion as u16 + | self.promotion as u16 + | (origin_square & 0b111111) << 4 + | (target_square & 0b111111) << 10) + } +} + +impl Promotion { + fn into_move_bits(&self) -> StdResult { + let origin_square = self + .style + .origin_square() + .ok_or(Error::MissingOriginSquare)? as u16; + let target_square = self + .style + .target_square() + .ok_or(Error::MissingTargetSquare)? as u16; + + Ok(Kind::CapturePromotion as u16 + | self.promotion as u16 + | (origin_square & 0b111111) << 4 + | (target_square & 0b111111) << 10) + } +} + impl Builder { pub fn new() -> Self { Self { style: Null } } - pub fn piece(piece: &PlacedPiece) -> Builder { + pub fn push(piece: &PlacedPiece, to: Square) -> Builder { Builder { style: Push { from: Some(piece.square()), - to: None, + to: Some(to), }, } } + pub fn double_push(file: File, color: Color) -> Builder { + let (from, to) = match color { + Color::White => ( + Square::from_file_rank(file, Rank::TWO), + Square::from_file_rank(file, Rank::FOUR), + ), + Color::Black => ( + Square::from_file_rank(file, Rank::SEVEN), + Square::from_file_rank(file, Rank::FIVE), + ), + }; + + Builder { + style: DoublePush { from, to }, + } + } + pub fn castling(castle: castle::Castle) -> Builder { Builder { style: Castle { castle }, } } + pub fn capturing_on(piece: &PlacedPiece, to: Square) -> Builder { + Self::push(piece, to).capturing_on(to) + } + + pub fn capturing_piece(piece: &PlacedPiece, capturing: &PlacedPiece) -> Builder { + Self::push(piece, capturing.square()).capturing_piece(&capturing) + } + + pub fn from(self, square: Square) -> Builder { + Builder { + style: Push { + from: Some(square), + to: None, + }, + } + } + pub fn build(&self) -> Move { Move(0) } } impl Builder { - pub fn from(mut self, square: Square) -> Self { + pub fn from(&mut self, square: Square) -> &mut Self { self.style.from = Some(square); self } - pub fn to(mut self, square: Square) -> Self { + pub fn to(&mut self, square: Square) -> &mut Self { self.style.to = Some(square); self } - pub fn capturing(self, square: Square) -> Builder { + pub fn capturing_on(self, square: Square) -> Builder { + let mut style = self.style; + style.to = Some(square); + Builder { style: Capture { + push: style, + capture: Some(square), + }, + } + } + + pub fn capturing_en_passant_on(self, square: Square) -> Builder { + let mut style = self.style; + style.to = Some(square); + + Builder { + style: EnPassantCapture { push: self.style, capture: Some(square), }, } } - pub fn capturing_piece(self, piece: PlacedPiece) -> Builder { + pub fn capturing_piece(self, piece: &PlacedPiece) -> Builder { Builder { style: Capture { push: self.style, @@ -87,19 +287,73 @@ impl Builder { }, } } + + pub fn promoting_to(self, shape: PromotionShape) -> Builder> { + Builder { + style: Promotion { + style: self.style, + promotion: shape, + }, + } + } + + pub fn build(&self) -> Result { + Ok(Move(Kind::Quiet as u16 | self.style.into_move_bits()?)) + } } impl Builder { + fn bits(&self) -> u16 { + let bits = match self.style.castle { + castle::Castle::KingSide => Kind::KingSideCastle, + castle::Castle::QueenSide => Kind::QueenSideCastle, + }; + + bits as u16 + } + pub fn build(&self) -> Move { - Move(self.style.into_bits()) + Move(self.bits()) } } -impl Castle { - fn into_bits(&self) -> u16 { - match self.castle { - castle::Castle::KingSide => 0b10, - castle::Castle::QueenSide => 0b11, +impl Builder { + pub fn promoting_to(self, shape: PromotionShape) -> Builder> { + Builder { + style: Promotion { + style: self.style, + promotion: shape, + }, } } + + pub fn build(&self) -> Result { + Ok(Move(Kind::Capture as u16 | self.style.into_move_bits()?)) + } +} + +impl Builder { + pub fn build(&self) -> Result { + Ok(Move(Kind::DoublePush as u16 | self.style.into_move_bits()?)) + } +} + +impl Builder { + pub unsafe fn build_unchecked(&self) -> Result { + Ok(Move( + Kind::EnPassantCapture as u16 | self.style.into_move_bits()?, + )) + } +} + +impl Builder> { + pub fn build(&self) -> Result { + Ok(Move(self.style.into_move_bits()?)) + } +} + +impl Builder> { + pub fn build(&self) -> Result { + Ok(Move(self.style.into_move_bits()?)) + } } diff --git a/moves/src/castle.rs b/moves/src/castle.rs index a8e95d1..5459ff4 100644 --- a/moves/src/castle.rs +++ b/moves/src/castle.rs @@ -1,17 +1,26 @@ // Eryn Wells use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Square}; +use chessfriend_core::Square; -#[repr(u16)] +#[repr(u8)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Castle { - KingSide = 0b10, - QueenSide = 0b11, + KingSide = 0, + QueenSide = 1, } pub(crate) struct CastlingParameters { + /// Origin squares of the king and rook. + origin_squares: Squares, + + /// Target or destination squares for the king and rook. + target_squares: Squares, + + /// The set of squares that must be clear of any pieces in order to perform this castle. clear_squares: BitBoard, + + /// The set of squares that must not be attacked in order to perform this castle. check_squares: BitBoard, } @@ -24,87 +33,59 @@ pub(crate) struct Squares { impl Castle { pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide]; - const STARTING_SQUARES: [[Squares; 2]; 2] = [ + /// Parameters for each castling move, organized by color and board-side. + const PARAMETERS: [[CastlingParameters; 2]; 2] = [ [ - Squares { - king: Square::E1, - rook: Square::H1, - }, - Squares { - king: Square::E1, - rook: Square::A1, - }, - ], - [ - Squares { - king: Square::E8, - rook: Square::H8, - }, - Squares { - king: Square::E8, - rook: Square::A8, - }, - ], - ]; - - const TARGET_SQUARES: [[Squares; 2]; 2] = [ - [ - Squares { - king: Square::G1, - rook: Square::F1, - }, - Squares { - king: Square::C1, - rook: Square::D1, - }, - ], - [ - Squares { - king: Square::G8, - rook: Square::F8, - }, - Squares { - king: Square::C8, - rook: Square::D8, - }, - ], - ]; - - pub(crate) fn starting_squares(&self, color: Color) -> &'static Squares { - &Castle::STARTING_SQUARES[color as usize][self.into_index()] - } - - pub(crate) fn target_squares(&self, color: Color) -> &'static Squares { - &Castle::TARGET_SQUARES[color as usize][self.into_index()] - } - - pub(crate) fn into_index(&self) -> usize { - match self { - Castle::KingSide => 0, - Castle::QueenSide => 1, - } - } - - pub(crate) fn parameters(&self) -> CastlingParameters { - match self { - Castle::KingSide => CastlingParameters { + CastlingParameters { + origin_squares: Squares { + king: Square::E1, + rook: Square::H1, + }, + target_squares: Squares { + king: Square::G1, + rook: Square::F1, + }, clear_squares: BitBoard::new(0b01100000), check_squares: BitBoard::new(0b01110000), }, - Castle::QueenSide => CastlingParameters { + CastlingParameters { + origin_squares: Squares { + king: Square::E1, + rook: Square::A1, + }, + target_squares: Squares { + king: Square::C1, + rook: Square::D1, + }, clear_squares: BitBoard::new(0b00001110), check_squares: BitBoard::new(0b00011100), }, - } - } -} - -impl CastlingParameters { - pub fn clear_squares(&self) -> &BitBoard { - &self.clear_squares - } - - pub fn check_squares(&self) -> &BitBoard { - &self.check_squares - } + ], + [ + CastlingParameters { + origin_squares: Squares { + king: Square::E8, + rook: Square::H8, + }, + target_squares: Squares { + king: Square::G8, + rook: Square::F8, + }, + clear_squares: BitBoard::new(0b01100000 << 8 * 7), + check_squares: BitBoard::new(0b01110000 << 8 * 7), + }, + CastlingParameters { + origin_squares: Squares { + king: Square::E8, + rook: Square::A8, + }, + target_squares: Squares { + king: Square::C8, + rook: Square::D8, + }, + clear_squares: BitBoard::new(0b00001110 << 8 * 7), + check_squares: BitBoard::new(0b00011100 << 8 * 7), + }, + ], + ]; } diff --git a/moves/src/defs.rs b/moves/src/defs.rs new file mode 100644 index 0000000..642b82e --- /dev/null +++ b/moves/src/defs.rs @@ -0,0 +1,34 @@ +// Eryn Wells + +use chessfriend_core::Shape; + +pub(crate) enum Kind { + Quiet = 0b00, + DoublePush = 0b01, + KingSideCastle = 0b10, + QueenSideCastle = 0b11, + Capture = 0b0100, + EnPassantCapture = 0b0101, + Promotion = 0b1000, + CapturePromotion = 0b1100, +} + +#[repr(u16)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PromotionShape { + Knight = 0b00, + Bishop = 0b01, + Rook = 0b10, + Queen = 0b11, +} + +impl From for Shape { + fn from(value: PromotionShape) -> Self { + match value { + PromotionShape::Knight => Shape::Knight, + PromotionShape::Bishop => Shape::Bishop, + PromotionShape::Rook => Shape::Rook, + PromotionShape::Queen => Shape::Queen, + } + } +} diff --git a/moves/src/lib.rs b/moves/src/lib.rs index 564c897..0ad0fc1 100644 --- a/moves/src/lib.rs +++ b/moves/src/lib.rs @@ -2,7 +2,10 @@ mod builder; mod castle; +mod defs; mod moves; -pub use builder::Builder; +pub use builder::{Builder, Error as BuilderError}; +pub use castle::Castle; +pub use defs::PromotionShape; pub use moves::Move; diff --git a/moves/src/moves.rs b/moves/src/moves.rs index 8d33b04..ab0c0da 100644 --- a/moves/src/moves.rs +++ b/moves/src/moves.rs @@ -1,72 +1,45 @@ // Eryn Wells -use crate::castle::Castle; -use chessfriend_core::{PlacedPiece, Shape, Square}; +use crate::{castle::Castle, defs::Kind}; +use chessfriend_core::{Rank, Shape, Square}; use std::fmt; -#[repr(u16)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PromotableShape { - Knight = 0b00, - Bishop = 0b01, - Rook = 0b10, - Queen = 0b11, -} - -#[repr(u16)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum Kind { - Quiet = 0b00, - DoublePush = 0b01, - Castle(Castle), - Capture(PlacedPiece) = 0b0100, - EnPassantCapture(PlacedPiece) = 0b0101, - Promotion(PromotableShape) = 0b1000, - CapturePromotion(PlacedPiece, PromotableShape) = 0b1100, -} - -impl Kind { - fn bits(&self) -> u16 { - match self { - Self::Promotion(shape) => self.discriminant() | *shape as u16, - Self::CapturePromotion(_, shape) => self.discriminant() | *shape as u16, - Self::Castle(castle) => *castle as u16, - _ => self.discriminant(), - } - } - - /// Return the discriminant value. This implementation is copied from the Rust docs. - /// See https://doc.rust-lang.org/std/mem/fn.discriminant.html - fn discriminant(&self) -> u16 { - unsafe { *<*const _>::from(self).cast::() } - } -} - -impl Default for Kind { - fn default() -> Self { - Self::Quiet - } -} - /// A single player's move. In chess parlance, this is a "ply". #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Move(pub(crate) u16); impl Move { - pub fn from_square(&self) -> Square { + pub fn origin_square(&self) -> Square { ((self.0 >> 4) & 0b111111).try_into().unwrap() } - pub fn to_square(&self) -> Square { + pub fn target_square(&self) -> Square { (self.0 >> 10).try_into().unwrap() } + pub fn capture_square(&self) -> Option { + if self.is_capture() { + return Some(self.target_square()); + } + + if self.is_en_passant() { + let target_square = self.target_square(); + return Some(match target_square.rank() { + Rank::THREE => Square::from_file_rank(target_square.file(), Rank::FOUR), + Rank::SIX => Square::from_file_rank(target_square.file(), Rank::FIVE), + _ => unreachable!(), + }); + } + + None + } + pub fn is_quiet(&self) -> bool { - self.flags() == Kind::Quiet.discriminant() + self.flags() == Kind::Quiet as u16 } pub fn is_double_push(&self) -> bool { - self.flags() == Kind::DoublePush.discriminant() + self.flags() == Kind::DoublePush as u16 } pub fn is_castle(&self) -> bool { diff --git a/moves/tests/flags.rs b/moves/tests/flags.rs new file mode 100644 index 0000000..cff0841 --- /dev/null +++ b/moves/tests/flags.rs @@ -0,0 +1,104 @@ +// Eryn Wells + +use chessfriend_core::{piece, Color, File, Shape, Square}; +use chessfriend_moves::{Builder, BuilderError, Castle, PromotionShape}; + +macro_rules! assert_flag { + ($move:expr, $left:expr, $right:expr, $desc:expr) => { + assert_eq!($left, $right, "{:?} -> {}", $move, stringify!($desc)) + }; +} + +macro_rules! assert_flags { + ($move:expr, $quiet:expr, $double_push:expr, $en_passant:expr, $capture:expr, $castle:expr, $promotion:expr) => { + assert_flag!($move, $move.is_quiet(), $quiet, "is_quiet"); + assert_flag!( + $move, + $move.is_double_push(), + $double_push, + "is_double_push" + ); + assert_flag!($move, $move.is_en_passant(), $en_passant, "is_en_passant"); + assert_flag!($move, $move.is_capture(), $capture, "is_capture"); + assert_flag!($move, $move.is_castle(), $castle, "is_castle"); + assert_flag!($move, $move.is_promotion(), $promotion, "is_promotion"); + }; +} + +#[test] +fn move_flags_quiet() -> Result<(), BuilderError> { + let mv = Builder::push(&piece!(White Pawn on A4), Square::A5).build()?; + assert_flags!(mv, true, false, false, false, false, false); + + Ok(()) +} + +#[test] +fn move_flags_double_push() -> Result<(), BuilderError> { + let mv = Builder::double_push(File::C, Color::White).build()?; + assert_flags!(mv, false, true, false, false, false, false); + + Ok(()) +} + +#[test] +fn move_flags_capture() -> Result<(), BuilderError> { + let mv = Builder::new() + .from(Square::A4) + .capturing_on(Square::B5) + .build()?; + + assert_flags!(mv, false, false, false, true, false, false); + + Ok(()) +} + +#[test] +fn move_flags_en_passant_capture() -> Result<(), BuilderError> { + let mv = unsafe { + Builder::new() + .from(Square::A5) + .capturing_en_passant_on(Square::B4) + .build_unchecked()? + }; + + assert_flags!(mv, false, false, true, true, false, false); + assert_eq!(mv.origin_square(), Square::A5); + assert_eq!(mv.target_square(), Square::B4); + + Ok(()) +} + +#[test] +fn move_flags_promotion() -> Result<(), BuilderError> { + let mv = Builder::push(&piece!(White Pawn on H7), Square::H8) + .promoting_to(PromotionShape::Queen) + .build()?; + + assert_flags!(mv, false, false, false, false, false, true); + assert_eq!(mv.promotion(), Some(Shape::Queen)); + + Ok(()) +} + +#[test] +fn move_flags_capture_promotion() -> Result<(), BuilderError> { + let mv = Builder::push(&piece!(White Pawn on H7), Square::H8) + .capturing_piece(&piece!(Black Knight on G8)) + .promoting_to(PromotionShape::Queen) + .build()?; + + assert_flags!(mv, false, false, false, true, false, true); + assert_eq!(mv.promotion(), Some(Shape::Queen)); + + Ok(()) +} + +#[test] +fn move_flags_castle() -> Result<(), BuilderError> { + let mv = Builder::castling(Castle::KingSide).build(); + + assert_flags!(mv, false, false, false, false, true, false); + + Ok(()) +} diff --git a/moves/tests/pushes.rs b/moves/tests/pushes.rs new file mode 100644 index 0000000..0ef2a2f --- /dev/null +++ b/moves/tests/pushes.rs @@ -0,0 +1,15 @@ +// Eryn Wells + +use chessfriend_core::{piece, Square}; +use chessfriend_moves::{Builder, BuilderError}; + +#[test] +fn pawn_push() -> Result<(), BuilderError> { + let mv = Builder::push(&piece!(White Pawn on A3), Square::A4).build()?; + + assert!(mv.is_quiet()); + assert_eq!(mv.origin_square(), Square::A3); + assert_eq!(mv.target_square(), Square::A4); + + Ok(()) +}