From f69c7d4c9626535c38a414e72817eb652da7fb32 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 3 Feb 2024 15:17:02 -0800 Subject: [PATCH 01/29] Move a bunch of stuff from the position::move module over to a new chessfriend_moves crate --- Cargo.lock | 3 +- Cargo.toml | 1 + moves/Cargo.toml | 10 ++++ moves/src/castle.rs | 110 ++++++++++++++++++++++++++++++++++++++ moves/src/lib.rs | 8 +++ moves/src/moves.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 moves/Cargo.toml create mode 100644 moves/src/castle.rs create mode 100644 moves/src/lib.rs create mode 100644 moves/src/moves.rs diff --git a/Cargo.lock b/Cargo.lock index 6e580bf..7251e3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,12 +74,11 @@ name = "chessfriend_core" version = "0.1.0" [[package]] -name = "chessfriend_move_generator" +name = "chessfriend_moves" version = "0.1.0" dependencies = [ "chessfriend_bitboard", "chessfriend_core", - "chessfriend_position", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b490c8e..490ec43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "bitboard", "core", "explorer", + "moves", "position", ] resolver = "2" diff --git a/moves/Cargo.toml b/moves/Cargo.toml new file mode 100644 index 0000000..3c9e6cf --- /dev/null +++ b/moves/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "chessfriend_moves" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chessfriend_core = { path = "../core" } +chessfriend_bitboard = { path = "../bitboard" } diff --git a/moves/src/castle.rs b/moves/src/castle.rs new file mode 100644 index 0000000..a8e95d1 --- /dev/null +++ b/moves/src/castle.rs @@ -0,0 +1,110 @@ +// Eryn Wells + +use chessfriend_bitboard::BitBoard; +use chessfriend_core::{Color, Square}; + +#[repr(u16)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Castle { + KingSide = 0b10, + QueenSide = 0b11, +} + +pub(crate) struct CastlingParameters { + clear_squares: BitBoard, + check_squares: BitBoard, +} + +#[derive(Debug)] +pub(crate) struct Squares { + pub king: Square, + pub rook: Square, +} + +impl Castle { + pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide]; + + const STARTING_SQUARES: [[Squares; 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 { + clear_squares: BitBoard::new(0b01100000), + check_squares: BitBoard::new(0b01110000), + }, + Castle::QueenSide => CastlingParameters { + 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 + } +} diff --git a/moves/src/lib.rs b/moves/src/lib.rs new file mode 100644 index 0000000..564c897 --- /dev/null +++ b/moves/src/lib.rs @@ -0,0 +1,8 @@ +// Eryn Wells + +mod builder; +mod castle; +mod moves; + +pub use builder::Builder; +pub use moves::Move; diff --git a/moves/src/moves.rs b/moves/src/moves.rs new file mode 100644 index 0000000..8d33b04 --- /dev/null +++ b/moves/src/moves.rs @@ -0,0 +1,127 @@ +// Eryn Wells + +use crate::castle::Castle; +use chessfriend_core::{PlacedPiece, 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 { + ((self.0 >> 4) & 0b111111).try_into().unwrap() + } + + pub fn to_square(&self) -> Square { + (self.0 >> 10).try_into().unwrap() + } + + pub fn is_quiet(&self) -> bool { + self.flags() == Kind::Quiet.discriminant() + } + + pub fn is_double_push(&self) -> bool { + self.flags() == Kind::DoublePush.discriminant() + } + + pub fn is_castle(&self) -> bool { + self.castle().is_some() + } + + pub fn castle(&self) -> Option { + match self.flags() { + 0b0010 => Some(Castle::KingSide), + 0b0011 => Some(Castle::QueenSide), + _ => None, + } + } + + pub fn is_capture(&self) -> bool { + (self.0 & 0b0100) != 0 + } + + pub fn is_en_passant(&self) -> bool { + self.flags() == 0b0101 + } + + pub fn is_promotion(&self) -> bool { + (self.0 & 0b1000) != 0 + } + + pub fn promotion(&self) -> Option { + if !self.is_promotion() { + return None; + } + + Some(match self.special() { + 0b00 => Shape::Knight, + 0b01 => Shape::Bishop, + 0b10 => Shape::Rook, + 0b11 => Shape::Queen, + _ => unreachable!(), + }) + } + + #[inline] + fn flags(&self) -> u16 { + self.0 & 0b1111 + } + + #[inline] + fn special(&self) -> u16 { + self.0 & 0b11 + } +} + +impl fmt::Debug for Move { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Move") + .field(&format_args!("{:08b}", &self.0)) + .finish() + } +} From 0bedf2aa9f490c430e799b61d9629264e52f7ba8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 3 Feb 2024 15:17:40 -0800 Subject: [PATCH 02/29] Implement part of a new Builder using the type state pattern The API for this is much easier to use. --- moves/src/builder.rs | 105 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 moves/src/builder.rs diff --git a/moves/src/builder.rs b/moves/src/builder.rs new file mode 100644 index 0000000..9340497 --- /dev/null +++ b/moves/src/builder.rs @@ -0,0 +1,105 @@ +// Eryn Wells + +use crate::{castle, Move}; +use chessfriend_core::{PlacedPiece, Square}; + +pub trait Style {} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Builder { + style: S, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Null; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Push { + from: Option, + to: Option, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Capture { + push: Push, + capture: Option, +} + +pub struct Castle { + castle: castle::Castle, +} + +impl Style for Null {} +impl Style for Push {} +impl Style for Capture {} +impl Style for Castle {} + +impl Builder { + pub fn new() -> Self { + Self { style: Null } + } + + pub fn piece(piece: &PlacedPiece) -> Builder { + Builder { + style: Push { + from: Some(piece.square()), + to: None, + }, + } + } + + pub fn castling(castle: castle::Castle) -> Builder { + Builder { + style: Castle { castle }, + } + } + + pub fn build(&self) -> Move { + Move(0) + } +} + +impl Builder { + pub fn from(mut self, square: Square) -> Self { + self.style.from = Some(square); + self + } + + pub fn to(mut self, square: Square) -> Self { + self.style.to = Some(square); + self + } + + pub fn capturing(self, square: Square) -> Builder { + Builder { + style: Capture { + push: self.style, + capture: Some(square), + }, + } + } + + pub fn capturing_piece(self, piece: PlacedPiece) -> Builder { + Builder { + style: Capture { + push: self.style, + capture: Some(piece.square()), + }, + } + } +} + +impl Builder { + pub fn build(&self) -> Move { + Move(self.style.into_bits()) + } +} + +impl Castle { + fn into_bits(&self) -> u16 { + match self.castle { + castle::Castle::KingSide => 0b10, + castle::Castle::QueenSide => 0b11, + } + } +} From c55b7c4877e12a828fc54cf5f372564074548602 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 9 Feb 2024 20:00:47 -0800 Subject: [PATCH 03/29] 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(()) +} From cc23ee2d90148d3c32f6fcd84d6091220b0b7cf6 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 10 Feb 2024 11:40:27 -0700 Subject: [PATCH 04/29] Rename en passant square method on Position and implement a getter for the capture square MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Position::en_passant_square → en_passant_target_square Position::en_passant_capture_square --- position/src/fen.rs | 2 +- .../src/position/builders/move_builder.rs | 2 +- .../src/position/builders/position_builder.rs | 2 +- position/src/position/position.rs | 19 +++++++++++++++++-- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/position/src/fen.rs b/position/src/fen.rs index d6c3568..9a8369a 100644 --- a/position/src/fen.rs +++ b/position/src/fen.rs @@ -95,7 +95,7 @@ impl ToFen for Position { write!( fen_string, " {}", - if let Some(en_passant_square) = self.en_passant_square() { + if let Some(en_passant_square) = self.en_passant_target_square() { en_passant_square.to_string() } else { "-".to_string() diff --git a/position/src/position/builders/move_builder.rs b/position/src/position/builders/move_builder.rs index c926c08..2de56d4 100644 --- a/position/src/position/builders/move_builder.rs +++ b/position/src/position/builders/move_builder.rs @@ -292,7 +292,7 @@ mod tests { new_position.piece_on_square(Square::E4), Some(piece!(White Pawn on E4)) ); - assert_eq!(new_position.en_passant_square(), Some(Square::E3)); + assert_eq!(new_position.en_passant_target_square(), Some(Square::E3)); Ok(()) } diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs index 5011f21..cba432f 100644 --- a/position/src/position/builders/position_builder.rs +++ b/position/src/position/builders/position_builder.rs @@ -52,7 +52,7 @@ impl Builder { flags: position.flags(), pieces, kings: [Some(white_king), Some(black_king)], - en_passant_square: position.en_passant_square(), + en_passant_square: position.en_passant_target_square(), ply_counter: position.ply_counter(), move_number: position.move_number(), } diff --git a/position/src/position/position.rs b/position/src/position/position.rs index 01de793..b8c193a 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -9,7 +9,7 @@ use crate::{ sight::SightExt, }; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; +use chessfriend_core::{Color, Piece, PlacedPiece, Rank, Shape, Square}; use std::{cell::OnceCell, fmt}; #[derive(Clone, Debug, Eq)] @@ -182,10 +182,25 @@ impl Position { Pieces::new(&self, color) } - pub fn en_passant_square(&self) -> Option { + /// If en passant is available in this position, the square a pawn will move + /// to if it captures en passant. + pub fn en_passant_target_square(&self) -> Option { self.en_passant_square } + /// If en passant is available in this position, the square on which the + /// captured pawn is sitting. + pub fn en_passant_capture_square(&self) -> Option { + let target_square = self.en_passant_square?; + + let file = target_square.file(); + Some(match target_square.rank() { + Rank::THREE => Square::from_file_rank(file, Rank::FOUR), + Rank::SIX => Square::from_file_rank(file, Rank::SEVEN), + _ => unreachable!(), + }) + } + fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard { let en_passant_square = self.en_passant_square; From e6a9b7f8c4865848cef5022b1580e12ba9868d27 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 10 Feb 2024 18:30:11 -0700 Subject: [PATCH 05/29] Return a chessfriend_moves::EnPassant from a new Position::en_passant() Replace Position's en_passant_target_square() and en_passant_capture_square() with a single en_passant() method that returns a new EnPassant struct that has the target and capture squares for the en passant move. --- Cargo.lock | 1 + moves/src/en_passant.rs | 49 +++++++++++++++++++ moves/src/lib.rs | 2 + position/Cargo.toml | 1 + position/src/fen.rs | 7 +-- .../src/position/builders/move_builder.rs | 11 ++--- .../src/position/builders/position_builder.rs | 2 +- position/src/position/position.rs | 20 ++------ 8 files changed, 66 insertions(+), 27 deletions(-) create mode 100644 moves/src/en_passant.rs diff --git a/Cargo.lock b/Cargo.lock index 7251e3e..90cedc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,6 +87,7 @@ version = "0.1.0" dependencies = [ "chessfriend_bitboard", "chessfriend_core", + "chessfriend_moves", ] [[package]] diff --git a/moves/src/en_passant.rs b/moves/src/en_passant.rs new file mode 100644 index 0000000..ac57835 --- /dev/null +++ b/moves/src/en_passant.rs @@ -0,0 +1,49 @@ +// Eryn Wells + +use chessfriend_core::{Rank, Square}; + +/// En passant information. +#[derive(Clone, Copy, Debug)] +pub struct EnPassant { + target: Square, + capture: Square, +} + +impl EnPassant { + fn _capture_square(target: Square) -> Option { + let (file, rank) = target.file_rank(); + match rank { + Rank::THREE => Some(Square::from_file_rank(file, Rank::FOUR)), + Rank::SIX => Some(Square::from_file_rank(file, Rank::FIVE)), + _ => None, + } + } + + /// Return en passant information for a particular target square. The target + /// square is the square a pawn capturing en passant will move to. + /// + /// Return `None` if the square is not eligible for en passant. + /// + /// ## Examples + /// + /// ``` + /// use chessfriend_core::Square; + /// use chessfriend_moves::EnPassant; + /// assert!(EnPassant::from_target_square(Square::E3).is_some()); + /// assert!(EnPassant::from_target_square(Square::B4).is_none()); + /// ``` + pub fn from_target_square(target: Square) -> Option { + match Self::_capture_square(target) { + Some(capture) => Some(Self { target, capture }), + None => None, + } + } + + pub fn target_square(&self) -> Square { + self.target + } + + pub fn capture_square(&self) -> Square { + self.capture + } +} diff --git a/moves/src/lib.rs b/moves/src/lib.rs index 0ad0fc1..75e26ce 100644 --- a/moves/src/lib.rs +++ b/moves/src/lib.rs @@ -3,9 +3,11 @@ mod builder; mod castle; mod defs; +mod en_passant; mod moves; pub use builder::{Builder, Error as BuilderError}; pub use castle::Castle; pub use defs::PromotionShape; +pub use en_passant::EnPassant; pub use moves::Move; diff --git a/position/Cargo.toml b/position/Cargo.toml index db11c0d..07157eb 100644 --- a/position/Cargo.toml +++ b/position/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] chessfriend_core = { path = "../core" } chessfriend_bitboard = { path = "../bitboard" } +chessfriend_moves = { path = "../moves" } diff --git a/position/src/fen.rs b/position/src/fen.rs index 9a8369a..dd9fa29 100644 --- a/position/src/fen.rs +++ b/position/src/fen.rs @@ -95,11 +95,8 @@ impl ToFen for Position { write!( fen_string, " {}", - if let Some(en_passant_square) = self.en_passant_target_square() { - en_passant_square.to_string() - } else { - "-".to_string() - } + self.en_passant() + .map_or("-".to_string(), |ep| ep.target_square().to_string()) ) .map_err(|err| ToFenError::FmtError(err))?; diff --git a/position/src/position/builders/move_builder.rs b/position/src/position/builders/move_builder.rs index 2de56d4..cb7f0f7 100644 --- a/position/src/position/builders/move_builder.rs +++ b/position/src/position/builders/move_builder.rs @@ -1,10 +1,6 @@ // Eryn Wells -use crate::{ - position::flags::Flags, - r#move::{AlgebraicMoveFormatter, Castle}, - MakeMoveError, Move, Position, -}; +use crate::{position::flags::Flags, r#move::Castle, MakeMoveError, Move, Position}; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; @@ -292,7 +288,10 @@ mod tests { new_position.piece_on_square(Square::E4), Some(piece!(White Pawn on E4)) ); - assert_eq!(new_position.en_passant_target_square(), Some(Square::E3)); + assert_eq!( + new_position.en_passant().map(|ep| ep.target_square()), + Some(Square::E3) + ); Ok(()) } diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs index cba432f..96757ce 100644 --- a/position/src/position/builders/position_builder.rs +++ b/position/src/position/builders/position_builder.rs @@ -52,7 +52,7 @@ impl Builder { flags: position.flags(), pieces, kings: [Some(white_king), Some(black_king)], - en_passant_square: position.en_passant_target_square(), + en_passant_square: position.en_passant().map(|ep| ep.target_square()), ply_counter: position.ply_counter(), move_number: position.move_number(), } diff --git a/position/src/position/position.rs b/position/src/position/position.rs index b8c193a..15da1c7 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -10,6 +10,7 @@ use crate::{ }; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, PlacedPiece, Rank, Shape, Square}; +use chessfriend_moves::EnPassant; use std::{cell::OnceCell, fmt}; #[derive(Clone, Debug, Eq)] @@ -182,23 +183,12 @@ impl Position { Pieces::new(&self, color) } - /// If en passant is available in this position, the square a pawn will move - /// to if it captures en passant. - pub fn en_passant_target_square(&self) -> Option { - self.en_passant_square + pub fn has_en_passant_square(&self) -> bool { + self.en_passant_square.is_some() } - /// If en passant is available in this position, the square on which the - /// captured pawn is sitting. - pub fn en_passant_capture_square(&self) -> Option { - let target_square = self.en_passant_square?; - - let file = target_square.file(); - Some(match target_square.rank() { - Rank::THREE => Square::from_file_rank(file, Rank::FOUR), - Rank::SIX => Square::from_file_rank(file, Rank::SEVEN), - _ => unreachable!(), - }) + pub fn en_passant(&self) -> Option { + EnPassant::from_target_square(self.en_passant_square?) } fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard { From 8724c3cdce51d940967a2dd3c4b256bac70d7d8b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 10 Feb 2024 18:33:13 -0700 Subject: [PATCH 06/29] Pad out the discriminants of Kind variants --- moves/src/defs.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/moves/src/defs.rs b/moves/src/defs.rs index 642b82e..337f30e 100644 --- a/moves/src/defs.rs +++ b/moves/src/defs.rs @@ -3,10 +3,10 @@ use chessfriend_core::Shape; pub(crate) enum Kind { - Quiet = 0b00, - DoublePush = 0b01, - KingSideCastle = 0b10, - QueenSideCastle = 0b11, + Quiet = 0b0000, + DoublePush = 0b0001, + KingSideCastle = 0b0010, + QueenSideCastle = 0b0011, Capture = 0b0100, EnPassantCapture = 0b0101, Promotion = 0b1000, From 3b8b6b36e37b5b042ea5f4507c44ddcd2b236694 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 10 Feb 2024 18:33:29 -0700 Subject: [PATCH 07/29] Implement a basic Display for chessfriend_moves::Move --- moves/src/moves.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/moves/src/moves.rs b/moves/src/moves.rs index ab0c0da..f4f9b21 100644 --- a/moves/src/moves.rs +++ b/moves/src/moves.rs @@ -89,6 +89,41 @@ impl Move { fn special(&self) -> u16 { self.0 & 0b11 } + + fn _transfer_char(&self) -> char { + if self.is_capture() || self.is_en_passant() { + 'x' + } else { + '-' + } + } +} + +impl fmt::Display for Move { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(castle) = self.castle() { + match castle { + Castle::KingSide => return write!(f, "0-0"), + Castle::QueenSide => return write!(f, "0-0-0"), + } + } + + write!( + f, + "{}{}{}", + self.origin_square(), + self._transfer_char(), + self.target_square() + )?; + + if let Some(promotion) = self.promotion() { + write!(f, "={}", promotion)?; + } else if self.is_en_passant() { + write!(f, " e.p.")?; + } + + Ok(()) + } } impl fmt::Debug for Move { From c03a804c792ad291f262ec52507e4c78313506f2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 11 Feb 2024 09:57:29 -0700 Subject: [PATCH 08/29] Rework the Pawn move generator to correctly compute en passant moves --- position/src/move_generator/pawn.rs | 93 ++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index c6e7a5a..1aa7a4d 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -1,14 +1,22 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::Position; +use crate::{r#move::Move, MoveBuilder, Position}; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square}; +use std::collections::BTreeMap; #[derive(Debug)] struct MoveIterator(usize, usize); -move_generator_declaration!(PawnMoveGenerator); +#[derive(Clone, Debug, Eq, PartialEq)] +pub(super) struct PawnMoveGenerator { + color: chessfriend_core::Color, + move_sets: BTreeMap, + en_passant_captures: Vec, +} + +move_generator_declaration!(PawnMoveGenerator, getters); impl MoveGeneratorInternal for PawnMoveGenerator { fn shape() -> Shape { @@ -31,7 +39,50 @@ impl MoveGeneratorInternal for PawnMoveGenerator { } impl PawnMoveGenerator { - fn pushes(position: &Position, piece: PlacedPiece) -> BitBoard { + pub(super) fn new( + position: &Position, + color: Color, + capture_mask: BitBoard, + push_mask: BitBoard, + ) -> Self { + let move_sets = if !capture_mask.is_empty() && !push_mask.is_empty() { + Self::move_sets(position, color, capture_mask, push_mask) + } else { + std::collections::BTreeMap::new() + }; + + Self { + color, + move_sets, + en_passant_captures: Vec::new(), + } + } + + fn move_sets( + position: &Position, + color: Color, + capture_mask: BitBoard, + push_mask: BitBoard, + ) -> BTreeMap { + let piece = Self::piece(color); + let mut moves_for_pieces = + BTreeMap::from_iter(position.bitboard_for_piece(piece).occupied_squares().map( + |square| { + let piece = PlacedPiece::new(piece, square); + let move_set = + Self::move_set_for_piece(position, &piece, capture_mask, push_mask); + (square, move_set) + }, + )); + + if position.has_en_passant_square() { + + } + + moves_for_pieces + } + + fn pushes(position: &Position, piece: &PlacedPiece) -> BitBoard { let color = piece.color(); let square = piece.square(); let bitboard: BitBoard = square.into(); @@ -59,16 +110,40 @@ impl PawnMoveGenerator { } } - fn attacks(position: &Position, piece: PlacedPiece) -> BitBoard { + fn attacks(position: &Position, piece: &PlacedPiece) -> BitBoard { let color = piece.color(); let opponent_pieces = position.bitboard_for_color(color.other()); - let en_passant_bitboard = match position.en_passant_square() { - Some(square) => >::into(square), - None => BitBoard::empty(), - }; - BitBoard::pawn_attacks(piece.square(), color) & (opponent_pieces | en_passant_bitboard) + BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces + } + + fn en_passant_attack(position: &Position, piece: &PlacedPiece) -> Option { + match position.en_passant() { + Some(en_passant) => { + let en_passant_bitboard: BitBoard = en_passant.target_square().into(); + let capture = + BitBoard::pawn_attacks(piece.square(), piece.color()) & en_passant_bitboard; + + if capture.is_empty() { + return None; + } + + match position.piece_on_square(en_passant.capture_square()) { + Some(captured_piece) => Some( + MoveBuilder::new( + *piece.piece(), + piece.square(), + en_passant.target_square(), + ) + .capturing_en_passant(captured_piece) + .build(), + ), + None => None, + } + } + None => None, + } } } From a2865c87b02e8b61b23727ecaa7db381eba77900 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 11 Feb 2024 10:22:10 -0700 Subject: [PATCH 09/29] Remove an unused Rank import from position.rs --- position/src/position/position.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/position/src/position/position.rs b/position/src/position/position.rs index 15da1c7..9b913c2 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -9,7 +9,7 @@ use crate::{ sight::SightExt, }; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Rank, Shape, Square}; +use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; use chessfriend_moves::EnPassant; use std::{cell::OnceCell, fmt}; From f23967f4f3096596fd7d34b690e72646c089405c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 11 Feb 2024 10:29:09 -0700 Subject: [PATCH 10/29] =?UTF-8?q?Rename=20MoveGenerator::bitboard=20?= =?UTF-8?q?=E2=86=92=20=5Ftest=5Fbitboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method is only used by tests. Mark it with cfg(test) and prefix it with _test to indicate that fact. --- position/src/move_generator.rs | 3 ++- position/src/move_generator/bishop.rs | 8 ++++---- position/src/move_generator/king.rs | 8 ++++---- position/src/move_generator/queen.rs | 8 ++++---- position/src/move_generator/rook.rs | 8 ++++---- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index e6884d4..c9d4b76 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -77,7 +77,8 @@ macro_rules! move_generator_declaration { self.move_sets.get(&piece.square()) } - fn bitboard(&self) -> chessfriend_bitboard::BitBoard { + #[cfg(test)] + fn _test_bitboard(&self) -> chessfriend_bitboard::BitBoard { self.move_sets.values().fold( chessfriend_bitboard::BitBoard::empty(), |partial, mv_set| partial | mv_set.bitboard(), diff --git a/position/src/move_generator/bishop.rs b/position/src/move_generator/bishop.rs index 5630246..a57b1a4 100644 --- a/position/src/move_generator/bishop.rs +++ b/position/src/move_generator/bishop.rs @@ -72,7 +72,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), BitBoard::new( 0b10000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000 ) @@ -93,7 +93,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), BitBoard::new( 0b00000000_00000000_00000000_00000000_00001000_00000100_00000010_00000000 ) @@ -112,7 +112,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), BitBoard::new( 0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000 ) @@ -129,7 +129,7 @@ mod tests { let generator = ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let bitboard = generator.bitboard(); + let bitboard = generator._test_bitboard(); let expected = BitBoard::new( 0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010, ); diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index befd0b2..121905a 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -69,7 +69,7 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), bitboard![E5, F5, F4, F3, E3, D3, D4, D5] ); @@ -95,10 +95,10 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let generated_bitboard = generator.bitboard(); + let generated_bitboard = generator._test_bitboard(); let expected_bitboard = bitboard![A2, B2, B1]; assert_eq!( - generator.bitboard(), + generator._test_bitboard(), bitboard![A2, B2, B1], "Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}" ); @@ -138,7 +138,7 @@ mod tests { assert!(pos.is_king_in_check()); let generator = KingMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL); - let generated_moves = generator.bitboard(); + let generated_moves = generator._test_bitboard(); let expected_moves = bitboard![F8, F7, F6, D6, D7, D8]; diff --git a/position/src/move_generator/queen.rs b/position/src/move_generator/queen.rs index 0b8a571..e0817cb 100644 --- a/position/src/move_generator/queen.rs +++ b/position/src/move_generator/queen.rs @@ -75,7 +75,7 @@ mod tests { let generator = ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let bitboard = generator.bitboard(); + let bitboard = generator._test_bitboard(); let expected = bitboard![ A2, C2, D2, E2, F2, G2, H2, // Rank B1, B3, B4, B5, B6, B7, B8, // File @@ -102,7 +102,7 @@ mod tests { let generator = ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let bitboard = generator.bitboard(); + let bitboard = generator._test_bitboard(); let expected = BitBoard::new( 0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_00001110, ); @@ -127,7 +127,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), bitboard![ A2, C2, D2, E2, F2, G2, H2, // Rank B1, B3, B4, B5, B6, B7, B8, // File @@ -146,7 +146,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), bitboard![ A3, B3, C3, E3, F3, G3, H3, // Rank D1, D2, D4, D5, D6, D7, D8, // File diff --git a/position/src/move_generator/rook.rs b/position/src/move_generator/rook.rs index 8e9037a..f692da5 100644 --- a/position/src/move_generator/rook.rs +++ b/position/src/move_generator/rook.rs @@ -73,7 +73,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), bitboard![A1, A3, A4, A5, A6, A7, A8, B2, C2, D2, E2, F2, G2, H2] ); } @@ -92,7 +92,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), BitBoard::new( 0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00001110 ) @@ -111,7 +111,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), bitboard![A2, A3, A4, A5, A6, A7, A8, B1, C1, D1, E1] ); } @@ -124,7 +124,7 @@ mod tests { ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( - generator.bitboard(), + generator._test_bitboard(), bitboard![A4, B4, C4, E4, F4, G4, H4, D1, D2, D3, D5, D6, D7, D8] ); } From e172bfb5dd9ef87d5760b8f09a9e7e3b72a7681b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 13 Feb 2024 11:03:28 -0700 Subject: [PATCH 11/29] Remove the Copy trait from most move Styles and add Clone, Debug, Eq, and PartialEq to Promotion and Castle --- moves/src/builder.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/moves/src/builder.rs b/moves/src/builder.rs index 780ed73..e8144a1 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -31,43 +31,45 @@ pub trait Style { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Builder { style: S, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Null; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Push { from: Option, to: Option, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct DoublePush { from: Square, to: Square, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Capture { push: Push, capture: Option, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct EnPassantCapture { push: Push, capture: Option, } +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Promotion { style: S, promotion: PromotionShape, } +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Castle { castle: castle::Castle, } From 5d1ad73be6d1021747ec02f27d89aa9cccc08544 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 13 Feb 2024 11:04:21 -0700 Subject: [PATCH 12/29] In Move::capture_square, move the en passant check above the simple capture check --- moves/src/moves.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/moves/src/moves.rs b/moves/src/moves.rs index f4f9b21..5348306 100644 --- a/moves/src/moves.rs +++ b/moves/src/moves.rs @@ -18,10 +18,6 @@ impl Move { } 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() { @@ -31,6 +27,10 @@ impl Move { }); } + if self.is_capture() { + return Some(self.target_square()); + } + None } From 047eb4fd77cbd3999b18dfc5d8b4e5bc89bf8ae3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 13 Feb 2024 11:05:02 -0700 Subject: [PATCH 13/29] Get en passant move building working (again?) --- moves/src/builder.rs | 19 ++++++++++++------- moves/tests/flags.rs | 21 +++++++++++++-------- moves/tests/pushes.rs | 4 +++- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/moves/src/builder.rs b/moves/src/builder.rs index e8144a1..f18e5d1 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -270,14 +270,19 @@ impl Builder { } pub fn capturing_en_passant_on(self, square: Square) -> Builder { - let mut style = self.style; - style.to = Some(square); + match EnPassant::from_target_square(square) { + Some(en_passant) => { + let mut style = self.style; + style.to = Some(en_passant.target_square()); - Builder { - style: EnPassantCapture { - push: self.style, - capture: Some(square), - }, + Builder { + style: EnPassantCapture { + push: style, + capture: Some(en_passant.capture_square()), + }, + } + } + None => todo!(), } } diff --git a/moves/tests/flags.rs b/moves/tests/flags.rs index cff0841..2e56389 100644 --- a/moves/tests/flags.rs +++ b/moves/tests/flags.rs @@ -27,7 +27,9 @@ macro_rules! assert_flags { #[test] fn move_flags_quiet() -> Result<(), BuilderError> { - let mv = Builder::push(&piece!(White Pawn on A4), Square::A5).build()?; + let mv = Builder::push(&piece!(White Pawn on A4)) + .to(Square::A5) + .build()?; assert_flags!(mv, true, false, false, false, false, false); Ok(()) @@ -57,21 +59,23 @@ fn move_flags_capture() -> Result<(), BuilderError> { fn move_flags_en_passant_capture() -> Result<(), BuilderError> { let mv = unsafe { Builder::new() - .from(Square::A5) - .capturing_en_passant_on(Square::B4) - .build_unchecked()? + .from(Square::A4) + .capturing_en_passant_on(Square::B3) + .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); + assert_eq!(mv.origin_square(), Square::A4); + assert_eq!(mv.target_square(), Square::B3); + assert_eq!(mv.capture_square(), Some(Square::B4)); Ok(()) } #[test] fn move_flags_promotion() -> Result<(), BuilderError> { - let mv = Builder::push(&piece!(White Pawn on H7), Square::H8) + let mv = Builder::push(&piece!(White Pawn on H7)) + .to(Square::H8) .promoting_to(PromotionShape::Queen) .build()?; @@ -83,7 +87,8 @@ fn move_flags_promotion() -> Result<(), BuilderError> { #[test] fn move_flags_capture_promotion() -> Result<(), BuilderError> { - let mv = Builder::push(&piece!(White Pawn on H7), Square::H8) + let mv = Builder::push(&piece!(White Pawn on H7)) + .to(Square::H8) .capturing_piece(&piece!(Black Knight on G8)) .promoting_to(PromotionShape::Queen) .build()?; diff --git a/moves/tests/pushes.rs b/moves/tests/pushes.rs index 0ef2a2f..ad66152 100644 --- a/moves/tests/pushes.rs +++ b/moves/tests/pushes.rs @@ -5,7 +5,9 @@ use chessfriend_moves::{Builder, BuilderError}; #[test] fn pawn_push() -> Result<(), BuilderError> { - let mv = Builder::push(&piece!(White Pawn on A3), Square::A4).build()?; + let mv = Builder::push(&piece!(White Pawn on A3)) + .to(Square::A4) + .build()?; assert!(mv.is_quiet()); assert_eq!(mv.origin_square(), Square::A3); From b3e55f6dcdfd45daf68c9e87942941e626fa5c7c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 13 Feb 2024 11:07:49 -0700 Subject: [PATCH 14/29] Split out some unchecked and check move build() methods Unchecked are unsafe. Checked are safe. --- moves/src/builder.rs | 54 +++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/moves/src/builder.rs b/moves/src/builder.rs index f18e5d1..b71c432 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -1,10 +1,11 @@ // Eryn Wells -use crate::{castle, defs::Kind, Move, PromotionShape}; +use crate::{castle, defs::Kind, EnPassant, Move, PromotionShape}; use chessfriend_core::{Color, File, PlacedPiece, Rank, Square}; use std::result::Result as StdResult; pub type Result = std::result::Result; +type EncodedMoveResult = std::result::Result; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Error { @@ -23,11 +24,22 @@ pub trait Style { 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; + fn into_move_bits(&self) -> EncodedMoveResult { + let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?; + let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?; - Ok((origin_square & 0b111111) << 4 | (target_square & 0b111111) << 10) + Ok(self._build_move_bits(origin_square, target_square)) + } + + unsafe fn into_move_bits_unchecked(&self) -> u16 { + let origin_square = self.origin_square().unwrap(); + let target_square = self.target_square().unwrap(); + + self._build_move_bits(origin_square, target_square) + } + + fn _build_move_bits(&self, origin_square: Square, target_square: Square) -> u16 { + (origin_square as u16 & 0b111111) << 4 | (target_square as u16 & 0b111111) << 10 } } @@ -123,11 +135,17 @@ impl Style for EnPassantCapture { 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; + fn into_move_bits(&self) -> EncodedMoveResult { + let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?; + let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?; - Ok((origin_square & 0b111111) << 4 | (target_square & 0b111111) << 10) + Ok(self._build_move_bits(origin_square, target_square)) + } +} + +impl EnPassantCapture { + fn _build_move_bits(&self, origin_square: Square, target_square: Square) -> u16 { + (origin_square as u16 & 0b111111) << 4 | (target_square as u16 & 0b111111) << 10 } } @@ -192,11 +210,11 @@ impl Builder { Self { style: Null } } - pub fn push(piece: &PlacedPiece, to: Square) -> Builder { + pub fn push(piece: &PlacedPiece) -> Builder { Builder { style: Push { from: Some(piece.square()), - to: Some(to), + to: None, }, } } @@ -224,12 +242,8 @@ impl Builder { } } - 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) + Self::push(piece).capturing_piece(&capturing) } pub fn from(self, square: Square) -> Builder { @@ -252,7 +266,7 @@ impl Builder { self } - pub fn to(&mut self, square: Square) -> &mut Self { + pub fn to(mut self, square: Square) -> Self { self.style.to = Some(square); self } @@ -346,7 +360,11 @@ impl Builder { } impl Builder { - pub unsafe fn build_unchecked(&self) -> Result { + pub unsafe fn build_unchecked(&self) -> Move { + Move(Kind::EnPassantCapture as u16 | self.style.into_move_bits_unchecked()) + } + + pub fn build(&self) -> Result { Ok(Move( Kind::EnPassantCapture as u16 | self.style.into_move_bits()?, )) From b5d4069751426baa3e32d1b126d34010663d4e11 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 08:51:23 -0800 Subject: [PATCH 15/29] Make moves::castle::Parameters public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename this struct CastlingParameters → Parameters Implement getter methods for private properties --- moves/src/castle.rs | 50 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/moves/src/castle.rs b/moves/src/castle.rs index 5459ff4..731f25f 100644 --- a/moves/src/castle.rs +++ b/moves/src/castle.rs @@ -1,7 +1,7 @@ // Eryn Wells use chessfriend_bitboard::BitBoard; -use chessfriend_core::Square; +use chessfriend_core::{Color, Square}; #[repr(u8)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -10,7 +10,7 @@ pub enum Castle { QueenSide = 1, } -pub(crate) struct CastlingParameters { +pub struct Parameters { /// Origin squares of the king and rook. origin_squares: Squares, @@ -24,19 +24,45 @@ pub(crate) struct CastlingParameters { check_squares: BitBoard, } +impl Parameters { + pub fn king_origin_square(&self) -> Square { + self.origin_squares.king + } + + pub fn rook_origin_square(&self) -> Square { + self.origin_squares.rook + } + + pub fn king_target_square(&self) -> Square { + self.target_squares.king + } + + pub fn rook_target_square(&self) -> Square { + self.target_squares.rook + } + + pub fn clear_squares(&self) -> &BitBoard { + &self.clear_squares + } + + pub fn check_squares(&self) -> &BitBoard { + &self.check_squares + } +} + #[derive(Debug)] -pub(crate) struct Squares { - pub king: Square, - pub rook: Square, +struct Squares { + king: Square, + rook: Square, } impl Castle { pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide]; /// Parameters for each castling move, organized by color and board-side. - const PARAMETERS: [[CastlingParameters; 2]; 2] = [ + const PARAMETERS: [[Parameters; 2]; 2] = [ [ - CastlingParameters { + Parameters { origin_squares: Squares { king: Square::E1, rook: Square::H1, @@ -48,7 +74,7 @@ impl Castle { clear_squares: BitBoard::new(0b01100000), check_squares: BitBoard::new(0b01110000), }, - CastlingParameters { + Parameters { origin_squares: Squares { king: Square::E1, rook: Square::A1, @@ -62,7 +88,7 @@ impl Castle { }, ], [ - CastlingParameters { + Parameters { origin_squares: Squares { king: Square::E8, rook: Square::H8, @@ -74,7 +100,7 @@ impl Castle { clear_squares: BitBoard::new(0b01100000 << 8 * 7), check_squares: BitBoard::new(0b01110000 << 8 * 7), }, - CastlingParameters { + Parameters { origin_squares: Squares { king: Square::E8, rook: Square::A8, @@ -88,4 +114,8 @@ impl Castle { }, ], ]; + + pub fn parameters(&self, color: Color) -> &'static Parameters { + &Castle::PARAMETERS[color as usize][*self as usize] + } } From d714374f35ee63a9a8976f547fe7fadc2cae835a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 08:57:16 -0800 Subject: [PATCH 16/29] Pass self by reference to move builder methods where possible --- moves/src/builder.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/moves/src/builder.rs b/moves/src/builder.rs index b71c432..a1ab78e 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -246,7 +246,7 @@ impl Builder { Self::push(piece).capturing_piece(&capturing) } - pub fn from(self, square: Square) -> Builder { + pub fn from(&self, square: Square) -> Builder { Builder { style: Push { from: Some(square), @@ -266,13 +266,13 @@ impl Builder { 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_on(self, square: Square) -> Builder { - let mut style = self.style; + pub fn capturing_on(&self, square: Square) -> Builder { + let mut style = self.style.clone(); style.to = Some(square); Builder { @@ -283,10 +283,10 @@ impl Builder { } } - pub fn capturing_en_passant_on(self, square: Square) -> Builder { + pub fn capturing_en_passant_on(&self, square: Square) -> Builder { match EnPassant::from_target_square(square) { Some(en_passant) => { - let mut style = self.style; + let mut style = self.style.clone(); style.to = Some(en_passant.target_square()); Builder { @@ -300,19 +300,19 @@ impl Builder { } } - pub fn capturing_piece(self, piece: &PlacedPiece) -> Builder { + pub fn capturing_piece(&self, piece: &PlacedPiece) -> Builder { Builder { style: Capture { - push: self.style, + push: self.style.clone(), capture: Some(piece.square()), }, } } - pub fn promoting_to(self, shape: PromotionShape) -> Builder> { + pub fn promoting_to(&self, shape: PromotionShape) -> Builder> { Builder { style: Promotion { - style: self.style, + style: self.style.clone(), promotion: shape, }, } From 9b7bf3a212d643947d670e75cd9a8717f03129dd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 09:15:07 -0800 Subject: [PATCH 17/29] Implement some helpful testing types and traits in the moves package --- moves/src/lib.rs | 4 +++- moves/src/testing.rs | 17 +++++++++++++++++ moves/tests/flags.rs | 16 ++++++++-------- moves/tests/pushes.rs | 4 ++-- 4 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 moves/src/testing.rs diff --git a/moves/src/lib.rs b/moves/src/lib.rs index 75e26ce..297f82e 100644 --- a/moves/src/lib.rs +++ b/moves/src/lib.rs @@ -1,12 +1,14 @@ // Eryn Wells +pub mod testing; + mod builder; mod castle; mod defs; mod en_passant; mod moves; -pub use builder::{Builder, Error as BuilderError}; +pub use builder::{Builder, Error as BuildMoveError, Result as BuildMoveResult}; pub use castle::Castle; pub use defs::PromotionShape; pub use en_passant::EnPassant; diff --git a/moves/src/testing.rs b/moves/src/testing.rs new file mode 100644 index 0000000..b298d5d --- /dev/null +++ b/moves/src/testing.rs @@ -0,0 +1,17 @@ +// Eryn Wells + +use crate::BuildMoveError; + +pub type TestResult = Result<(), TestError>; + +#[derive(Debug, Eq, PartialEq)] +pub enum TestError { + BuildMove(BuildMoveError), + NoLegalMoves, +} + +impl From for TestError { + fn from(value: BuildMoveError) -> Self { + TestError::BuildMove(value) + } +} diff --git a/moves/tests/flags.rs b/moves/tests/flags.rs index 2e56389..cdcdd57 100644 --- a/moves/tests/flags.rs +++ b/moves/tests/flags.rs @@ -1,7 +1,7 @@ // Eryn Wells use chessfriend_core::{piece, Color, File, Shape, Square}; -use chessfriend_moves::{Builder, BuilderError, Castle, PromotionShape}; +use chessfriend_moves::{testing::*, Builder, Castle, PromotionShape}; macro_rules! assert_flag { ($move:expr, $left:expr, $right:expr, $desc:expr) => { @@ -26,7 +26,7 @@ macro_rules! assert_flags { } #[test] -fn move_flags_quiet() -> Result<(), BuilderError> { +fn move_flags_quiet() -> TestResult { let mv = Builder::push(&piece!(White Pawn on A4)) .to(Square::A5) .build()?; @@ -36,7 +36,7 @@ fn move_flags_quiet() -> Result<(), BuilderError> { } #[test] -fn move_flags_double_push() -> Result<(), BuilderError> { +fn move_flags_double_push() -> TestResult { let mv = Builder::double_push(File::C, Color::White).build()?; assert_flags!(mv, false, true, false, false, false, false); @@ -44,7 +44,7 @@ fn move_flags_double_push() -> Result<(), BuilderError> { } #[test] -fn move_flags_capture() -> Result<(), BuilderError> { +fn move_flags_capture() -> TestResult { let mv = Builder::new() .from(Square::A4) .capturing_on(Square::B5) @@ -56,7 +56,7 @@ fn move_flags_capture() -> Result<(), BuilderError> { } #[test] -fn move_flags_en_passant_capture() -> Result<(), BuilderError> { +fn move_flags_en_passant_capture() -> TestResult { let mv = unsafe { Builder::new() .from(Square::A4) @@ -73,7 +73,7 @@ fn move_flags_en_passant_capture() -> Result<(), BuilderError> { } #[test] -fn move_flags_promotion() -> Result<(), BuilderError> { +fn move_flags_promotion() -> TestResult { let mv = Builder::push(&piece!(White Pawn on H7)) .to(Square::H8) .promoting_to(PromotionShape::Queen) @@ -86,7 +86,7 @@ fn move_flags_promotion() -> Result<(), BuilderError> { } #[test] -fn move_flags_capture_promotion() -> Result<(), BuilderError> { +fn move_flags_capture_promotion() -> TestResult { let mv = Builder::push(&piece!(White Pawn on H7)) .to(Square::H8) .capturing_piece(&piece!(Black Knight on G8)) @@ -100,7 +100,7 @@ fn move_flags_capture_promotion() -> Result<(), BuilderError> { } #[test] -fn move_flags_castle() -> Result<(), BuilderError> { +fn move_flags_castle() -> TestResult { let mv = Builder::castling(Castle::KingSide).build(); assert_flags!(mv, false, false, false, false, true, false); diff --git a/moves/tests/pushes.rs b/moves/tests/pushes.rs index ad66152..7406eb0 100644 --- a/moves/tests/pushes.rs +++ b/moves/tests/pushes.rs @@ -1,10 +1,10 @@ // Eryn Wells use chessfriend_core::{piece, Square}; -use chessfriend_moves::{Builder, BuilderError}; +use chessfriend_moves::{testing::*, Builder}; #[test] -fn pawn_push() -> Result<(), BuilderError> { +fn pawn_push() -> TestResult { let mv = Builder::push(&piece!(White Pawn on A3)) .to(Square::A4) .build()?; From 36db46ac18cfe864c25299c99641b01f692e5253 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 09:20:45 -0800 Subject: [PATCH 18/29] =?UTF-8?q?Move=20position::tests=20=E2=86=92=20test?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand information printed in assert_move_list --- position/src/lib.rs | 2 +- position/src/testing.rs | 58 +++++++++++++++++++++++++++++++++++++++++ position/src/tests.rs | 34 ------------------------ 3 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 position/src/testing.rs delete mode 100644 position/src/tests.rs diff --git a/position/src/lib.rs b/position/src/lib.rs index 5776e05..d313231 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -13,7 +13,7 @@ mod sight; mod macros; #[macro_use] -mod tests; +mod testing; pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder}; pub use r#move::{Castle, MakeMoveError, Move, MoveBuilder}; diff --git a/position/src/testing.rs b/position/src/testing.rs new file mode 100644 index 0000000..ba4bd69 --- /dev/null +++ b/position/src/testing.rs @@ -0,0 +1,58 @@ +// Eryn Wells + +use crate::MakeMoveError; +use chessfriend_moves::{BuildMoveError, BuildMoveResult, Move}; + +#[macro_export] +macro_rules! assert_move_list { + ($generated:expr, $expected:expr, $position:expr) => { + assert_eq!( + $generated, + $expected, + "\n\tMatching: {:?}\n\tGenerated, not expected: {:?}\n\tExpected, not generated: {:?}", + $generated + .intersection(&$expected) + .map(|mv| format!("{}", mv)) + .collect::>(), + $generated + .difference(&$expected) + .map(|mv| format!("{}", mv)) + .collect::>(), + $expected + .difference(&$generated) + .map(|mv| format!("{}", mv)) + .collect::>(), + ) + }; +} + +#[macro_export] +macro_rules! formatted_move_list { + ($move_list:expr, $position:expr) => { + $move_list + .iter() + .map(|mv| format!("{}", mv)) + .collect::>() + }; +} + +pub type TestResult = Result<(), TestError>; + +#[derive(Debug, Eq, PartialEq)] +pub enum TestError { + BuildMove(BuildMoveError), + MakeMove(MakeMoveError), + NoLegalMoves, +} + +impl From for TestError { + fn from(value: BuildMoveError) -> Self { + TestError::BuildMove(value) + } +} + +impl From for TestError { + fn from(value: MakeMoveError) -> Self { + TestError::MakeMove(value) + } +} diff --git a/position/src/tests.rs b/position/src/tests.rs deleted file mode 100644 index 962b9bc..0000000 --- a/position/src/tests.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Eryn Wells - -#[macro_export] -macro_rules! assert_move_list { - ($generated:expr, $expected:expr, $position:expr) => { - assert_eq!( - $generated, - $expected, - "Difference: {:?}", - $generated - .symmetric_difference(&$expected) - .map(|mv| format!( - "{}", - $crate::r#move::AlgebraicMoveFormatter::new(mv, &$position) - )) - .collect::>() - ) - }; -} - -#[macro_export] -macro_rules! formatted_move_list { - ($move_list:expr, $position:expr) => { - $move_list - .iter() - .map(|mv| { - format!( - "{}", - $crate::r#move::AlgebraicMoveFormatter::new(mv, &$position) - ) - }) - .collect::>() - }; -} From aaad9918998b7e015c163b867dd5b2b3a2594a0c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 09:44:25 -0800 Subject: [PATCH 19/29] Replace crate::r#move::castle::Castle with moves::Castle --- position/src/move_generator/king.rs | 3 ++- position/src/move_generator/move_set.rs | 2 +- position/src/position/builders/position_builder.rs | 2 +- position/src/position/flags.rs | 4 ++-- position/src/position/position.rs | 5 ++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index 121905a..1813339 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -4,9 +4,10 @@ //! generating the possible moves for the king in the given position. use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::{r#move::Castle, Position}; +use crate::Position; use chessfriend_bitboard::BitBoard; use chessfriend_core::{PlacedPiece, Shape}; +use chessfriend_moves::Castle; move_generator_declaration!(KingMoveGenerator, struct); move_generator_declaration!(KingMoveGenerator, new); diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs index 6cefb7a..d03eaef 100644 --- a/position/src/move_generator/move_set.rs +++ b/position/src/move_generator/move_set.rs @@ -1,8 +1,8 @@ // Eryn Wells -use crate::{r#move::Castle, Move, MoveBuilder}; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; +use chessfriend_moves::{Builder as MoveBuilder, Castle, Move}; /// A set of bitboards defining the moves for a single piece on the board. #[derive(Clone, Debug, Default, Eq, PartialEq)] diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs index 96757ce..28652ed 100644 --- a/position/src/position/builders/position_builder.rs +++ b/position/src/position/builders/position_builder.rs @@ -2,10 +2,10 @@ use crate::{ position::{flags::Flags, piece_sets::PieceBitBoards}, - r#move::Castle, Position, }; use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square}; +use chessfriend_moves::Castle; use std::collections::BTreeMap; #[derive(Clone)] diff --git a/position/src/position/flags.rs b/position/src/position/flags.rs index 47e49bf..7c722a9 100644 --- a/position/src/position/flags.rs +++ b/position/src/position/flags.rs @@ -1,7 +1,7 @@ // Eryn Wells -use crate::r#move::Castle; use chessfriend_core::Color; +use chessfriend_moves::Castle; use std::fmt; #[derive(Clone, Copy, Eq, Hash, PartialEq)] @@ -10,7 +10,7 @@ pub struct Flags(u8); impl Flags { #[inline] pub(super) fn player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize { - ((color as usize) << 1) + castle.into_index() + ((color as usize) << 1) + castle as usize } pub(super) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool { diff --git a/position/src/position/position.rs b/position/src/position/position.rs index 9b913c2..bbdcb9e 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -5,12 +5,11 @@ use crate::{ check::CheckingPieces, move_generator::{MoveSet, Moves}, position::DiagramFormatter, - r#move::Castle, sight::SightExt, }; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; -use chessfriend_moves::EnPassant; +use chessfriend_moves::{Castle, EnPassant}; use std::{cell::OnceCell, fmt}; #[derive(Clone, Debug, Eq)] @@ -93,7 +92,7 @@ impl Position { return false; } - let castling_parameters = castle.parameters(); + let castling_parameters = castle.parameters(player); let all_pieces = self.occupied_squares(); if !(all_pieces & castling_parameters.clear_squares()).is_empty() { From d668091d0dadd2dede4260cbf68dc15305b55d5c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 09:52:40 -0800 Subject: [PATCH 20/29] Replace uses of types in r#move with types from the moves package types --- position/src/fen.rs | 3 +- position/src/lib.rs | 5 +- position/src/move_generator.rs | 7 +- position/src/move_generator/king.rs | 73 ++++------ position/src/move_generator/knight.rs | 53 +++---- position/src/move_generator/move_set.rs | 41 ++---- position/src/move_generator/pawn.rs | 134 ++++++++++-------- .../move_generator/tests/peterellisjones.rs | 90 +++++++----- .../src/move_generator/tests/single_pieces.rs | 38 +++-- position/src/position/builders/mod.rs | 2 +- .../src/position/builders/move_builder.rs | 90 +++++++----- .../src/position/builders/position_builder.rs | 6 +- position/src/position/flags.rs | 2 - position/src/position/mod.rs | 2 +- position/src/position/position.rs | 5 +- position/src/testing.rs | 2 +- 16 files changed, 273 insertions(+), 280 deletions(-) diff --git a/position/src/fen.rs b/position/src/fen.rs index dd9fa29..fab45e1 100644 --- a/position/src/fen.rs +++ b/position/src/fen.rs @@ -1,7 +1,8 @@ // Eryn Wells -use crate::{r#move::Castle, Position, PositionBuilder}; +use crate::{Position, PositionBuilder}; use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square}; +use chessfriend_moves::Castle; use std::fmt::Write; macro_rules! fen { diff --git a/position/src/lib.rs b/position/src/lib.rs index d313231..1995be0 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -4,7 +4,6 @@ pub mod fen; mod check; mod display; -mod r#move; mod move_generator; mod position; mod sight; @@ -12,8 +11,8 @@ mod sight; #[macro_use] mod macros; +#[cfg(test)] #[macro_use] mod testing; -pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder}; -pub use r#move::{Castle, MakeMoveError, Move, MoveBuilder}; +pub use position::{MakeMoveError, MoveBuilder as MakeMoveBuilder, Position, PositionBuilder}; diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index c9d4b76..4d14ece 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -7,6 +7,8 @@ mod move_set; mod pawn; mod queen; mod rook; + +#[cfg(test)] mod tests; pub(crate) use move_set::MoveSet; @@ -17,9 +19,10 @@ use self::{ queen::ClassicalMoveGenerator as QueenMoveGenerator, rook::ClassicalMoveGenerator as RookMoveGenerator, }; -use crate::{Move, Position}; +use crate::Position; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; +use chessfriend_moves::Move; use std::collections::BTreeMap; trait MoveGenerator { @@ -66,7 +69,7 @@ macro_rules! move_generator_declaration { }; ($name:ident, getters) => { impl $name { - pub(super) fn iter(&self) -> impl Iterator + '_ { + pub(super) fn iter(&self) -> impl Iterator + '_ { self.move_sets.values().flat_map(|set| set.moves()) } diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index 1813339..45c928e 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -58,13 +58,14 @@ impl MoveGeneratorInternal for KingMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{assert_move_list, position, test_position, Move, MoveBuilder, PositionBuilder}; + use crate::{assert_move_list, position, test_position, testing::*, PositionBuilder}; use chessfriend_bitboard::bitboard; use chessfriend_core::{piece, Color, Square}; + use chessfriend_moves::{Builder as MoveBuilder, Castle, Move}; use std::collections::HashSet; #[test] - fn one_king() { + fn one_king() -> TestResult { let pos = position![White King on E4]; let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); @@ -74,24 +75,27 @@ mod tests { bitboard![E5, F5, F4, F3, E3, D3, D4, D5] ); + let builder = MoveBuilder::push(&piece!(White King on E4)); let expected_moves: HashSet = HashSet::from_iter([ - MoveBuilder::new(piece!(White King), Square::E4, Square::D5).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::E5).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::F5).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::F4).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::F3).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::E3).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::D3).build(), - MoveBuilder::new(piece!(White King), Square::E4, Square::D4).build(), + builder.clone().to(Square::D5).build()?, + builder.clone().to(Square::E5).build()?, + builder.clone().to(Square::F5).build()?, + builder.clone().to(Square::F4).build()?, + builder.clone().to(Square::F3).build()?, + builder.clone().to(Square::E3).build()?, + builder.clone().to(Square::D3).build()?, + builder.clone().to(Square::D4).build()?, ]); let generated_moves: HashSet = generator.iter().collect(); assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] - fn one_king_corner() { + fn one_king_corner() -> TestResult { let pos = position![White King on A1]; let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); @@ -104,13 +108,14 @@ mod tests { "Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}" ); + let builder = MoveBuilder::push(&piece!(White King on A1)); let expected_moves = [ - MoveBuilder::new(piece!(White King), Square::A1, Square::A2).build(), - MoveBuilder::new(piece!(White King), Square::A1, Square::B1).build(), - MoveBuilder::new(piece!(White King), Square::A1, Square::B2).build(), + builder.clone().to(Square::A2).build()?, + builder.clone().to(Square::B1).build()?, + builder.clone().to(Square::B2).build()?, ]; - let mut generated_moves: HashSet = generator.iter().collect(); + let mut generated_moves: HashSet<_> = generator.iter().collect(); for ex_move in expected_moves { assert!( @@ -125,6 +130,8 @@ mod tests { "Moves unexpectedly present: {:#?}", generated_moves ); + + Ok(()) } #[test] @@ -161,16 +168,8 @@ mod tests { let generated_moves: HashSet = generator.iter().collect(); let king = piece!(White King); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::G1) - .castle(Castle::KingSide) - .build() - )); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::C1) - .castle(Castle::QueenSide) - .build() - )); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } #[test] @@ -189,16 +188,8 @@ mod tests { let generated_moves: HashSet = generator.iter().collect(); let king = piece!(White King); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::G1) - .castle(Castle::KingSide) - .build() - )); - assert!(!generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::C1) - .castle(Castle::QueenSide) - .build() - )); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); + assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } #[test] @@ -217,15 +208,7 @@ mod tests { let generated_moves: HashSet = generator.iter().collect(); let king = piece!(White King); - assert!(!generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::G1) - .castle(Castle::KingSide) - .build() - )); - assert!(generated_moves.contains( - &MoveBuilder::new(king, Square::E1, Square::C1) - .castle(Castle::QueenSide) - .build() - )); + assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); + assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } } diff --git a/position/src/move_generator/knight.rs b/position/src/move_generator/knight.rs index 6366808..2b385ec 100644 --- a/position/src/move_generator/knight.rs +++ b/position/src/move_generator/knight.rs @@ -34,54 +34,35 @@ impl MoveGeneratorInternal for KnightMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{position, Move, MoveBuilder}; + use crate::{assert_move_list, position, testing::*}; use chessfriend_core::{piece, Color, Square}; + use chessfriend_moves::Builder as MoveBuilder; use std::collections::HashSet; #[test] - fn one_knight() { + fn one_knight() -> TestResult { let pos = position![ White Knight on E4, ]; let generator = KnightMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generated_moves: HashSet<_> = generator.iter().collect(); - /* - let bb = generator.bitboard(); - assert_eq!( - bb, - BitBoard::new( - 0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000 - ) - ); - */ + let piece = piece!(White Knight on E4); + let expected_moves = HashSet::from_iter([ + MoveBuilder::push(&piece).to(Square::C3).build()?, + MoveBuilder::push(&piece).to(Square::D2).build()?, + MoveBuilder::push(&piece).to(Square::F2).build()?, + MoveBuilder::push(&piece).to(Square::G3).build()?, + MoveBuilder::push(&piece).to(Square::C5).build()?, + MoveBuilder::push(&piece).to(Square::D6).build()?, + MoveBuilder::push(&piece).to(Square::G5).build()?, + MoveBuilder::push(&piece).to(Square::F6).build()?, + ]); - let expected_moves = [ - MoveBuilder::new(piece!(White Knight), Square::E4, Square::C3).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::D2).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::F2).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::G3).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::C5).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::D6).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::G5).build(), - MoveBuilder::new(piece!(White Knight), Square::E4, Square::F6).build(), - ]; + assert_move_list!(generated_moves, expected_moves, pos); - let mut generated_moves: HashSet = generator.iter().collect(); - - for ex_move in expected_moves { - assert!( - generated_moves.remove(&ex_move), - "{:#?} was not generated", - &ex_move - ); - } - - assert!( - generated_moves.is_empty(), - "Moves unexpectedly present: {:#?}", - generated_moves - ); + Ok(()) } } diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs index d03eaef..f1042c9 100644 --- a/position/src/move_generator/move_set.rs +++ b/position/src/move_generator/move_set.rs @@ -1,7 +1,7 @@ // Eryn Wells use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; +use chessfriend_core::{PlacedPiece, Square}; use chessfriend_moves::{Builder as MoveBuilder, Castle, Move}; /// A set of bitboards defining the moves for a single piece on the board. @@ -65,57 +65,34 @@ impl MoveSet { } pub(crate) fn moves(&self) -> impl Iterator + '_ { - let piece = self.piece.piece(); - let from_square = self.piece.square(); + let piece = &self.piece; self.bitboards .quiet .occupied_squares() - .map(move |to_square| MoveBuilder::new(*piece, from_square, to_square).build()) + .filter_map(|to_square| MoveBuilder::push(&self.piece).to(to_square).build().ok()) .chain( self.bitboards .captures .occupied_squares() - .map(move |to_square| { - MoveBuilder::new(*piece, from_square, to_square) - .capturing(PlacedPiece::new( - Piece::new(Color::White, Shape::Pawn), - to_square, - )) - .build() - }), + .filter_map(|to_square| MoveBuilder::push(piece).to(to_square).build().ok()), ) .chain( if (self.special & 0b1) != 0 { - Some(()) + let mv = MoveBuilder::castling(Castle::KingSide).build(); + Some(mv) } else { None } - .map(|()| { - MoveBuilder::new( - *piece, - from_square, - Castle::KingSide.target_squares(piece.color()).king, - ) - .castle(Castle::KingSide) - .build() - }), + .into_iter(), ) .chain( if (self.special & 0b10) != 0 { - Some(()) + Some(MoveBuilder::castling(Castle::QueenSide).build()) } else { None } - .map(|()| { - MoveBuilder::new( - *piece, - from_square, - Castle::QueenSide.target_squares(piece.color()).king, - ) - .castle(Castle::QueenSide) - .build() - }), + .into_iter(), ) } } diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index 1aa7a4d..67fefb3 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -1,9 +1,10 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::{r#move::Move, MoveBuilder, Position}; +use crate::Position; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square}; +use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::BTreeMap; #[derive(Debug)] @@ -65,7 +66,7 @@ impl PawnMoveGenerator { push_mask: BitBoard, ) -> BTreeMap { let piece = Self::piece(color); - let mut moves_for_pieces = + let moves_for_pieces = BTreeMap::from_iter(position.bitboard_for_piece(piece).occupied_squares().map( |square| { let piece = PlacedPiece::new(piece, square); @@ -75,10 +76,6 @@ impl PawnMoveGenerator { }, )); - if position.has_en_passant_square() { - - } - moves_for_pieces } @@ -121,23 +118,21 @@ impl PawnMoveGenerator { fn en_passant_attack(position: &Position, piece: &PlacedPiece) -> Option { match position.en_passant() { Some(en_passant) => { - let en_passant_bitboard: BitBoard = en_passant.target_square().into(); + let target_square = en_passant.target_square(); + + let en_passant_bitboard: BitBoard = target_square.into(); let capture = BitBoard::pawn_attacks(piece.square(), piece.color()) & en_passant_bitboard; - if capture.is_empty() { return None; } match position.piece_on_square(en_passant.capture_square()) { - Some(captured_piece) => Some( - MoveBuilder::new( - *piece.piece(), - piece.square(), - en_passant.target_square(), - ) - .capturing_en_passant(captured_piece) - .build(), + Some(_) => Some( + MoveBuilder::push(piece) + .capturing_en_passant_on(target_square) + .build() + .ok()?, ), None => None, } @@ -150,46 +145,48 @@ impl PawnMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{assert_move_list, position::DiagramFormatter, test_position, Move, MoveBuilder}; - use chessfriend_core::{piece, Color, Piece, Square}; + use crate::{assert_move_list, position::DiagramFormatter, test_position, testing::*}; + use chessfriend_core::{piece, Color, Square}; + use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::HashSet; #[test] - fn one_2square_push() { + fn one_double_push() -> TestResult { let pos = test_position![White Pawn on E2]; let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let pawn = piece!(White Pawn on E2); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(), - MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(), + MoveBuilder::push(&pawn).to(Square::E3).build()?, + MoveBuilder::double_push(pawn.square().file(), pawn.color()).build()?, ]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); assert_eq!(generated_moves, expected_moves); + + Ok(()) } #[test] - fn one_1square_push() { + fn one_single_push() -> TestResult { let pos = test_position![White Pawn on E3]; let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generated_moves: HashSet<_> = generator.iter().collect(); - let expected_moves = HashSet::from_iter([MoveBuilder::new( - Piece::pawn(Color::White), - Square::E3, - Square::E4, - ) - .build()]); - - let generated_moves: HashSet = generator.iter().collect(); + let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E3)) + .to(Square::E4) + .build()?]); assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] - fn one_obstructed_2square_push() { + fn one_obstructed_2square_push() -> TestResult { let pos = test_position![ White Pawn on E2, White Knight on E4, @@ -199,16 +196,15 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let expected_moves = HashSet::from_iter([MoveBuilder::new( - Piece::pawn(Color::White), - Square::E2, - Square::E3, - ) - .build()]); + let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E2)) + .to(Square::E3) + .build()?]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] @@ -220,14 +216,14 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let generated_moves: HashSet = generator.iter().collect(); - let expected_moves: HashSet = HashSet::new(); + let generated_moves: HashSet<_> = generator.iter().collect(); + let expected_moves: HashSet<_> = HashSet::new(); assert_move_list!(generated_moves, expected_moves, pos); } #[test] - fn one_attack() { + fn one_attack() -> TestResult { let pos = test_position![ White Pawn on E4, White Bishop on E5, @@ -236,20 +232,19 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); - let expected_moves = - HashSet::from_iter( - [MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5) - .capturing(piece!(Black Knight on D5)) - .build()], - ); + let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E4)) + .capturing_on(Square::D5) + .build()?]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); - assert_eq!(generated_moves, expected_moves); + assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } #[test] - fn one_double_attack() { + fn one_double_attack() -> TestResult { let pos = test_position![ White Pawn on E4, White Bishop on E5, @@ -259,21 +254,44 @@ mod tests { let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let builder = MoveBuilder::push(&piece!(White Pawn on E4)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5) - .capturing(piece!(Black Knight on D5)) - .build(), - MoveBuilder::new(piece!(White Pawn), Square::E4, Square::F5) - .capturing(piece!(Black Queen on F5)) - .build(), + builder.clone().capturing_on(Square::D5).build()?, + builder.clone().capturing_on(Square::F5).build()?, ]); - let generated_moves: HashSet = generator.iter().collect(); + let generated_moves: HashSet<_> = generator.iter().collect(); assert_eq!( generated_moves, expected_moves, "generated: {:#?}\nexpected: {:#?}", generated_moves, expected_moves ); + + Ok(()) + } + + #[test] + fn one_en_passant_attack() -> TestResult { + let pos = test_position!(Black, [ + White Pawn on D4, + Black Pawn on E4, + ], D3); + + let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generated_moves: HashSet = generator.iter().collect(); + + let builder = MoveBuilder::push(&piece!(Black Pawn on E4)); + let expected_moves = HashSet::from_iter([ + builder + .clone() + .capturing_en_passant_on(Square::D3) + .build()?, + builder.clone().to(Square::E3).build()?, + ]); + + assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } } diff --git a/position/src/move_generator/tests/peterellisjones.rs b/position/src/move_generator/tests/peterellisjones.rs index 3828970..31a878a 100644 --- a/position/src/move_generator/tests/peterellisjones.rs +++ b/position/src/move_generator/tests/peterellisjones.rs @@ -6,15 +6,13 @@ //! [1]: https://peterellisjones.com //! [2]: https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/ -use crate::{ - assert_move_list, formatted_move_list, move_generator::Moves, r#move::AlgebraicMoveFormatter, - test_position, Move, MoveBuilder, -}; +use crate::{assert_move_list, formatted_move_list, test_position, testing::*}; use chessfriend_core::{piece, Square}; +use chessfriend_moves::Builder as MoveBuilder; use std::collections::HashSet; #[test] -fn pseudo_legal_move_generation() -> Result<(), String> { +fn pseudo_legal_move_generation() -> TestResult { let pos = test_position!(Black, [ Black King on E8, White King on E1, @@ -24,7 +22,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> { let generated_moves = pos.moves(); let king_moves = generated_moves .moves_for_piece(&piece!(Black King on E8)) - .ok_or("No valid king moves")?; + .ok_or(TestError::NoLegalMoves)?; assert!(!king_moves.can_move_to_square(Square::F8)); assert!(!king_moves.can_move_to_square(Square::F7)); @@ -33,7 +31,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> { } #[test] -fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> { +fn gotcha_king_moves_away_from_a_checking_slider() -> TestResult { let pos = test_position!(Black, [ Black King on E7, White King on E1, @@ -43,7 +41,7 @@ fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> { let generated_moves = pos.moves(); let king_moves = generated_moves .moves_for_piece(&piece!(Black King on E7)) - .ok_or("No valid king moves")?; + .ok_or(TestError::NoLegalMoves)?; assert!(!king_moves.can_move_to_square(Square::E8)); @@ -51,7 +49,7 @@ fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> { } #[test] -fn check_evasions_1() { +fn check_evasions_1() -> TestResult { let pos = test_position!(Black, [ Black King on E8, White King on E1, @@ -60,11 +58,12 @@ fn check_evasions_1() { let generated_moves = pos.moves(); + let builder = MoveBuilder::push(&piece!(Black King on E8)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::E7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(), + builder.clone().to(Square::D8).build()?, + builder.clone().to(Square::E7).build()?, + builder.clone().to(Square::F7).build()?, + builder.clone().to(Square::F8).build()?, ]); assert_move_list!( @@ -72,10 +71,12 @@ fn check_evasions_1() { expected_moves, pos ); + + Ok(()) } #[test] -fn check_evasions_double_check() { +fn check_evasions_double_check() -> TestResult { let pos = test_position!(Black, [ Black King on E8, Black Bishop on F6, @@ -86,11 +87,12 @@ fn check_evasions_double_check() { let generated_moves = pos.moves(); + let builder = MoveBuilder::push(&piece!(Black King on E8)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(), + builder.clone().to(Square::D8).build()?, + builder.clone().to(Square::D7).build()?, + builder.clone().to(Square::F7).build()?, + builder.clone().to(Square::F8).build()?, ]); assert_move_list!( @@ -98,10 +100,12 @@ fn check_evasions_double_check() { expected_moves, pos ); + + Ok(()) } #[test] -fn single_check_with_blocker() { +fn single_check_with_blocker() -> TestResult { let pos = test_position!(Black, [ Black King on E8, Black Knight on G6, @@ -111,15 +115,15 @@ fn single_check_with_blocker() { let generated_moves = pos.moves(); + let king_builder = MoveBuilder::push(&piece!(Black King on E8)); + let knight_builder = MoveBuilder::push(&piece!(Black Knight on G6)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(), - MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(), - MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E7).build(), - MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E5) - .capturing(piece!(White Rook on E5)) - .build(), + king_builder.clone().to(Square::D8).build()?, + king_builder.clone().to(Square::D7).build()?, + king_builder.clone().to(Square::F7).build()?, + king_builder.clone().to(Square::F8).build()?, + knight_builder.clone().to(Square::E7).build()?, + knight_builder.clone().capturing_on(Square::E5).build()?, ]); assert_move_list!( @@ -127,10 +131,12 @@ fn single_check_with_blocker() { expected_moves, pos ); + + Ok(()) } #[test] -fn en_passant_check_capture() { +fn en_passant_check_capture() -> TestResult { let pos = test_position!(Black, [ Black King on C5, Black Pawn on E4, @@ -143,17 +149,19 @@ fn en_passant_check_capture() { assert!( generated_moves.contains( - &MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) - .capturing_en_passant(piece!(White Pawn on D4)) - .build() + &MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D4) + .build()? ), "Valid moves: {:?}", formatted_move_list!(generated_moves, pos) ); + + Ok(()) } #[test] -fn en_passant_check_block() { +fn en_passant_check_block() -> TestResult { let pos = test_position!(Black, [ Black King on B5, Black Pawn on E4, @@ -167,13 +175,15 @@ fn en_passant_check_block() { assert!( generated_moves.contains( - &MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) - .capturing_en_passant(piece!(White Pawn on D4)) - .build() + &MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D4) + .build()? ), "Valid moves: {:?}", formatted_move_list!(generated_moves, pos) ); + + Ok(()) } #[test] @@ -204,7 +214,7 @@ fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> { } #[test] -fn en_passant_discovered_check() { +fn en_passant_discovered_check() -> TestResult { let pos = test_position!(Black, [ Black King on A4, Black Pawn on E4, @@ -214,13 +224,15 @@ fn en_passant_discovered_check() { let generated_moves = pos.moves().iter().collect::>(); - let unexpected_move = MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) - .capturing_en_passant(piece!(White Pawn on D4)) - .build(); + let unexpected_move = MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D4) + .build()?; assert!( !generated_moves.contains(&unexpected_move), "Valid moves: {:?}", formatted_move_list!(generated_moves, pos) ); + + Ok(()) } diff --git a/position/src/move_generator/tests/single_pieces.rs b/position/src/move_generator/tests/single_pieces.rs index 01a3e34..f8d06fb 100644 --- a/position/src/move_generator/tests/single_pieces.rs +++ b/position/src/move_generator/tests/single_pieces.rs @@ -1,36 +1,32 @@ // Eryn Wells -use crate::{position, r#move::AlgebraicMoveFormatter, Move, MoveBuilder}; +use crate::{assert_move_list, test_position, testing::*}; use chessfriend_core::{piece, Square}; +use chessfriend_moves::Builder as MoveBuilder; use std::collections::HashSet; #[test] -fn one_king() { - let pos = position![ +fn one_king() -> TestResult { + let pos = test_position![ White King on D3, Black King on H6, ]; + let builder = MoveBuilder::push(&piece!(White King on D3)); let expected_moves = HashSet::from_iter([ - MoveBuilder::new(piece!(White King), Square::D3, Square::D4).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::E4).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::E3).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::E2).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::D2).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::C2).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::C3).build(), - MoveBuilder::new(piece!(White King), Square::D3, Square::C4).build(), + builder.clone().to(Square::D4).build()?, + builder.clone().to(Square::E4).build()?, + builder.clone().to(Square::E3).build()?, + builder.clone().to(Square::E2).build()?, + builder.clone().to(Square::D2).build()?, + builder.clone().to(Square::C2).build()?, + builder.clone().to(Square::C3).build()?, + builder.clone().to(Square::C4).build()?, ]); - let generated_moves: HashSet = pos.moves().iter().collect(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); - assert_eq!( - generated_moves, - expected_moves, - "{:?}", - generated_moves - .symmetric_difference(&expected_moves) - .map(|m| format!("{}", AlgebraicMoveFormatter::new(&m, &pos))) - .collect::>() - ); + assert_move_list!(generated_moves, expected_moves, pos); + + Ok(()) } diff --git a/position/src/position/builders/mod.rs b/position/src/position/builders/mod.rs index 9324249..ea1f60d 100644 --- a/position/src/position/builders/mod.rs +++ b/position/src/position/builders/mod.rs @@ -3,5 +3,5 @@ mod move_builder; mod position_builder; -pub use move_builder::Builder as MoveBuilder; +pub use move_builder::{Builder as MoveBuilder, MakeMoveError}; pub use position_builder::Builder as PositionBuilder; diff --git a/position/src/position/builders/move_builder.rs b/position/src/position/builders/move_builder.rs index cb7f0f7..900c322 100644 --- a/position/src/position/builders/move_builder.rs +++ b/position/src/position/builders/move_builder.rs @@ -1,8 +1,19 @@ // Eryn Wells -use crate::{position::flags::Flags, r#move::Castle, MakeMoveError, Move, Position}; +use crate::{position::flags::Flags, Position}; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; +use chessfriend_moves::{Castle, Move}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MakeMoveError { + PlayerOutOfTurn, + NoPiece, + NoCapturedPiece, + NoLegalMoves, + IllegalCastle, + IllegalSquare(Square), +} /// A position builder that builds a new position by making a move. #[derive(Clone)] @@ -51,14 +62,14 @@ where M: MoveToMake, { pub fn make(self, mv: &Move) -> Result, MakeMoveError> { - let from_square = mv.from_square(); + let origin_square = mv.origin_square(); let piece = self .position - .piece_on_square(from_square) + .piece_on_square(origin_square) .ok_or(MakeMoveError::NoPiece)?; - let to_square = mv.to_square(); + let target_square = mv.target_square(); let moves = self .position @@ -72,8 +83,8 @@ where } } None => { - if !moves.can_move_to_square(to_square) { - return Err(MakeMoveError::IllegalSquare(to_square)); + if !moves.can_move_to_square(target_square) { + return Err(MakeMoveError::IllegalSquare(target_square)); } } } @@ -83,8 +94,8 @@ where let captured_piece = if mv.is_en_passant() { // En passant captures the pawn directly ahead (in the player's direction) of the en passant square. let capture_square = match player { - Color::White => to_square.neighbor(Direction::South), - Color::Black => to_square.neighbor(Direction::North), + Color::White => target_square.neighbor(Direction::South), + Color::Black => target_square.neighbor(Direction::North), } .ok_or(MakeMoveError::NoCapturedPiece)?; @@ -96,7 +107,7 @@ where } else if mv.is_capture() { Some( self.position - .piece_on_square(to_square) + .piece_on_square(target_square) .ok_or(MakeMoveError::NoCapturedPiece)?, ) } else { @@ -140,8 +151,8 @@ where } else { let en_passant_square: Option = if mv.is_double_push() { match piece.color() { - Color::White => to_square.neighbor(Direction::South), - Color::Black => to_square.neighbor(Direction::North), + Color::White => target_square.neighbor(Direction::South), + Color::Black => target_square.neighbor(Direction::North), } } else { None @@ -150,8 +161,8 @@ where Ok(Builder { position: self.position, move_to_make: ValidatedMove::RegularMove { - from_square, - to_square, + from_square: origin_square, + to_square: target_square, moving_piece: piece, captured_piece, promotion: mv.promotion(), @@ -219,18 +230,19 @@ impl<'p> Builder<'p, ValidatedMove> { } => { let mut pieces = self.position.piece_bitboards().clone(); - let target_squares = castle.target_squares(player); + let parameters = castle.parameters(player); - let king_from: BitBoard = king.square().into(); - let king_to: BitBoard = target_squares.king.into(); - *pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to; + let king_origin_square: BitBoard = king.square().into(); + let king_target_square: BitBoard = parameters.king_target_square().into(); + *pieces.bitboard_for_piece_mut(king.piece()) ^= + king_origin_square | king_target_square; let rook_from: BitBoard = rook.square().into(); - let rook_to: BitBoard = target_squares.rook.into(); + let rook_to: BitBoard = parameters.rook_target_square().into(); *pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to; *pieces.bitboard_for_color_mut(player) &= - !(king_from | rook_from) | (king_to | rook_to); + !(king_origin_square | rook_from) | (king_target_square | rook_to); Position::new( player.other(), @@ -257,15 +269,24 @@ impl<'p> From<&'p Position> for Builder<'p, NoMove> { #[cfg(test)] mod tests { use super::*; - use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder}; + use crate::testing::*; + use crate::{position, PositionBuilder}; use chessfriend_core::piece; + use chessfriend_moves::Builder as MoveBuilder; #[test] - fn move_white_pawn_one_square() -> Result<(), MakeMoveError> { + fn move_white_pawn_one_square() -> TestResult { let pos = position![White Pawn on E2]; - let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(); + let mv = MoveBuilder::new() + .from(Square::E2) + .to(Square::E3) + .build() + .map_err(TestError::BuildMove)?; - let new_position = Builder::::new(&pos).make(&mv)?.build(); + let new_position = Builder::::new(&pos) + .make(&mv) + .map_err(|err| TestError::MakeMove(err))? + .build(); println!("{}", &new_position); assert_eq!( @@ -277,9 +298,13 @@ mod tests { } #[test] - fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> { + fn move_white_pawn_two_squares() -> TestResult { let pos = position![White Pawn on E2]; - let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(); + let mv = MoveBuilder::new() + .from(Square::E2) + .to(Square::E4) + .build() + .map_err(TestError::BuildMove)?; let new_position = Builder::::new(&pos).make(&mv)?.build(); println!("{}", &new_position); @@ -308,9 +333,7 @@ mod tests { ]; println!("{}", &pos); - let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1) - .castle(Castle::KingSide) - .build(); + let mv = MoveBuilder::castling(Castle::KingSide).build(); let new_position = Builder::::new(&pos).make(&mv)?.build(); println!("{}", &new_position); @@ -328,7 +351,7 @@ mod tests { } #[test] - fn en_passant_capture() -> Result<(), MakeMoveError> { + fn en_passant_capture() -> TestResult { let pos = PositionBuilder::new() .place_piece(piece!(White Pawn on B5)) .place_piece(piece!(Black Pawn on A7)) @@ -336,7 +359,8 @@ mod tests { .build(); println!("{pos}"); - let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build(); + let black_pawn_move = MoveBuilder::new().from(Square::A7).to(Square::A5).build()?; + assert!(black_pawn_move.is_double_push()); assert!(!black_pawn_move.is_en_passant()); @@ -352,9 +376,9 @@ mod tests { Some(piece!(White Pawn on B5)) ); - let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6) - .capturing_en_passant(piece!(Black Pawn on A5)) - .build(); + let white_pawn_capture = MoveBuilder::push(&piece!(White Pawn on B5)) + .capturing_en_passant_on(Square::A5) + .build()?; let en_passant_capture = Builder::::new(&en_passant_position) .make(&white_pawn_capture)? .build(); diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs index 28652ed..a4f8d2f 100644 --- a/position/src/position/builders/position_builder.rs +++ b/position/src/position/builders/position_builder.rs @@ -120,13 +120,13 @@ impl Builder { for color in Color::ALL { for castle in Castle::ALL { - let starting_squares = castle.starting_squares(color); + let parameters = castle.parameters(color); let has_rook_on_starting_square = self .pieces - .get(&starting_squares.rook) + .get(¶meters.rook_origin_square()) .is_some_and(|piece| piece.shape() == Shape::Rook); let king_is_on_starting_square = - self.kings[color as usize] == Some(starting_squares.king); + self.kings[color as usize] == Some(parameters.king_origin_square()); if !king_is_on_starting_square || !has_rook_on_starting_square { flags.clear_player_has_right_to_castle_flag(color, castle); diff --git a/position/src/position/flags.rs b/position/src/position/flags.rs index 7c722a9..19d3165 100644 --- a/position/src/position/flags.rs +++ b/position/src/position/flags.rs @@ -45,8 +45,6 @@ impl Default for Flags { #[cfg(test)] mod tests { use super::*; - use crate::r#move::Castle; - use chessfriend_core::Color; #[test] fn castle_flags() { diff --git a/position/src/position/mod.rs b/position/src/position/mod.rs index 8ece091..644ccc1 100644 --- a/position/src/position/mod.rs +++ b/position/src/position/mod.rs @@ -9,7 +9,7 @@ mod pieces; mod position; pub use { - builders::{MoveBuilder, PositionBuilder}, + builders::{MakeMoveError, MoveBuilder, PositionBuilder}, diagram_formatter::DiagramFormatter, pieces::Pieces, position::Position, diff --git a/position/src/position/position.rs b/position/src/position/position.rs index bbdcb9e..d336703 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -372,9 +372,10 @@ impl fmt::Display for Position { #[cfg(test)] mod tests { - use crate::{position, test_position, Castle, Position, PositionBuilder}; + use super::*; + use crate::{position, test_position, Position, PositionBuilder}; use chessfriend_bitboard::bitboard; - use chessfriend_core::{piece, Color, Square}; + use chessfriend_core::piece; #[test] fn piece_on_square() { diff --git a/position/src/testing.rs b/position/src/testing.rs index ba4bd69..1693110 100644 --- a/position/src/testing.rs +++ b/position/src/testing.rs @@ -1,7 +1,7 @@ // Eryn Wells use crate::MakeMoveError; -use chessfriend_moves::{BuildMoveError, BuildMoveResult, Move}; +use chessfriend_moves::BuildMoveError; #[macro_export] macro_rules! assert_move_list { From f1cd36952b5f2f5d3043ececbebcd29e76fc1890 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 09:52:49 -0800 Subject: [PATCH 21/29] Fix build errors in explorer --- explorer/Cargo.toml | 1 + explorer/src/main.rs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/explorer/Cargo.toml b/explorer/Cargo.toml index 3281cf4..9689332 100644 --- a/explorer/Cargo.toml +++ b/explorer/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] chessfriend_core = { path = "../core" } +chessfriend_moves = { path = "../moves" } chessfriend_position = { path = "../position" } clap = { version = "4.4.12", features = ["derive"] } rustyline = "13.0.0" diff --git a/explorer/src/main.rs b/explorer/src/main.rs index 8edb00e..5b8a17d 100644 --- a/explorer/src/main.rs +++ b/explorer/src/main.rs @@ -1,5 +1,8 @@ +// Eryn Wells + use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; -use chessfriend_position::{fen::ToFen, MakeMoveBuilder, MoveBuilder, Position, PositionBuilder}; +use chessfriend_moves::Builder as MoveBuilder; +use chessfriend_position::{fen::ToFen, MakeMoveBuilder, Position, PositionBuilder}; use clap::{Arg, Command}; use rustyline::error::ReadlineError; use rustyline::DefaultEditor; @@ -97,16 +100,15 @@ fn respond(line: &str, state: &mut State) -> Result { ) .map_err(|_| "Error: invalid square specifier")?; - let mv = MoveBuilder::new( - Piece::new(state.position.player_to_move(), shape), - from_square, - to_square, - ) - .build(); + let mv = MoveBuilder::new() + .from(from_square) + .to(to_square) + .build() + .map_err(|err| format!("Error: cannot build move: {:?}", err))?; state.position = MakeMoveBuilder::new(&state.position) .make(&mv) - .map_err(|err| format!("error: Cannot make move: {:?}", err))? + .map_err(|err| format!("Error: cannot make move: {:?}", err))? .build(); state.builder = PositionBuilder::from_position(&state.position); } From d77345901cdfff11bfaacbc540725e931cff745f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 09:52:59 -0800 Subject: [PATCH 22/29] Add chessfriend_moves to the workspace --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 90cedc0..53be692 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,7 @@ name = "explorer" version = "0.1.0" dependencies = [ "chessfriend_core", + "chessfriend_moves", "chessfriend_position", "clap", "rustyline", From 63c03fb879e774d74ac7591e26861c82d7238344 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 09:54:03 -0800 Subject: [PATCH 23/29] Delete the position::r#move module --- position/src/move.rs | 602 ------------------------------------------- 1 file changed, 602 deletions(-) delete mode 100644 position/src/move.rs diff --git a/position/src/move.rs b/position/src/move.rs deleted file mode 100644 index 32bc399..0000000 --- a/position/src/move.rs +++ /dev/null @@ -1,602 +0,0 @@ -// Eryn Wells - -use chessfriend_core::{Piece, PlacedPiece, Rank, Shape, Square}; -use std::fmt; - -pub use castle::Castle; -pub(crate) use move_formatter::AlgebraicMoveFormatter; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum MakeMoveError { - PlayerOutOfTurn, - NoPiece, - NoCapturedPiece, - NoLegalMoves, - IllegalCastle, - IllegalSquare(Square), -} - -mod castle { - use chessfriend_bitboard::BitBoard; - use chessfriend_core::{Color, Square}; - - #[repr(u16)] - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - pub enum Castle { - KingSide = 0b10, - QueenSide = 0b11, - } - - pub(crate) struct CastlingParameters { - clear_squares: BitBoard, - check_squares: BitBoard, - } - - #[derive(Debug)] - pub(crate) struct Squares { - pub king: Square, - pub rook: Square, - } - - impl Castle { - pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide]; - - const STARTING_SQUARES: [[Squares; 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 { - clear_squares: BitBoard::new(0b01100000), - check_squares: BitBoard::new(0b01110000), - }, - Castle::QueenSide => CastlingParameters { - 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 - } - } -} - -#[repr(u16)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PromotableShape { - Knight = 0b00, - Bishop = 0b01, - Rook = 0b10, - Queen = 0b11, -} - -impl TryFrom for PromotableShape { - type Error = (); - - fn try_from(value: Shape) -> Result { - match value { - Shape::Knight => Ok(PromotableShape::Knight), - Shape::Bishop => Ok(PromotableShape::Bishop), - Shape::Rook => Ok(PromotableShape::Rook), - Shape::Queen => Ok(PromotableShape::Queen), - _ => Err(()), - } - } -} - -#[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(u16); - -impl Move { - pub fn from_square(&self) -> Square { - ((self.0 >> 4) & 0b111111).try_into().unwrap() - } - - pub fn to_square(&self) -> Square { - (self.0 >> 10).try_into().unwrap() - } - - pub fn is_quiet(&self) -> bool { - self.flags() == Kind::Quiet.discriminant() - } - - pub fn is_double_push(&self) -> bool { - self.flags() == Kind::DoublePush.discriminant() - } - - pub fn is_castle(&self) -> bool { - self.castle().is_some() - } - - pub fn castle(&self) -> Option { - match self.flags() { - 0b0010 => Some(Castle::KingSide), - 0b0011 => Some(Castle::QueenSide), - _ => None, - } - } - - pub fn is_capture(&self) -> bool { - (self.0 & 0b0100) != 0 - } - - pub fn is_en_passant(&self) -> bool { - self.flags() == 0b0101 - } - - pub fn is_promotion(&self) -> bool { - (self.0 & 0b1000) != 0 - } - - pub fn promotion(&self) -> Option { - if !self.is_promotion() { - return None; - } - - Some(match self.special() { - 0b00 => Shape::Knight, - 0b01 => Shape::Bishop, - 0b10 => Shape::Rook, - 0b11 => Shape::Queen, - _ => unreachable!(), - }) - } - - #[inline] - fn flags(&self) -> u16 { - self.0 & 0b1111 - } - - #[inline] - fn special(&self) -> u16 { - self.0 & 0b11 - } -} - -impl fmt::Debug for Move { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Move") - .field(&format_args!("{:08b}", &self.0)) - .finish() - } -} - -#[derive(Clone, Debug)] -pub struct MoveBuilder { - piece: Piece, - from: Square, - to: Square, - kind: Kind, -} - -impl MoveBuilder { - pub fn new(piece: Piece, from: Square, to: Square) -> Self { - let kind = match piece.shape() { - Shape::Pawn => { - let from_rank = from.rank(); - let to_rank = to.rank(); - let is_white_double_push = from_rank == Rank::TWO && to_rank == Rank::FOUR; - let is_black_double_push = from_rank == Rank::SEVEN && to_rank == Rank::FIVE; - if is_white_double_push || is_black_double_push { - Kind::DoublePush - } else { - Kind::Quiet - } - } - _ => Kind::Quiet, - }; - - Self { - piece, - from, - to, - kind, - } - } - - pub fn castle(mut self, castle: Castle) -> Self { - self.kind = Kind::Castle(castle); - self - } - - pub fn capturing(mut self, captured_piece: PlacedPiece) -> Self { - self.kind = match self.kind { - Kind::Promotion(shape) => Kind::CapturePromotion(captured_piece, shape), - _ => Kind::Capture(captured_piece), - }; - self - } - - pub fn capturing_en_passant(mut self, captured_piece: PlacedPiece) -> Self { - self.kind = Kind::EnPassantCapture(captured_piece); - self - } - - pub fn promoting_to(mut self, shape: Shape) -> Self { - if let Some(shape) = PromotableShape::try_from(shape).ok() { - self.kind = match self.kind { - Kind::Capture(piece) => Kind::CapturePromotion(piece, shape), - Kind::CapturePromotion(piece, _) => Kind::CapturePromotion(piece, shape), - _ => Kind::Promotion(shape), - }; - } - self - } - - pub fn build(&self) -> Move { - Move( - self.kind.bits() - | ((self.from as u16 & 0b111111) << 4) - | ((self.to as u16 & 0b111111) << 10), - ) - } -} - -mod move_formatter { - use super::{Castle, Move}; - use crate::Position; - use chessfriend_core::Shape; - use std::fmt; - - enum Style { - Short, - Long, - } - - impl Default for Style { - fn default() -> Self { - Style::Long - } - } - - pub(crate) struct AlgebraicMoveFormatter<'m, 'pos> { - position: &'pos Position, - r#move: &'m Move, - style: Style, - } - - impl<'pos, 'm> AlgebraicMoveFormatter<'m, 'pos> { - pub(crate) fn new( - mv: &'m Move, - position: &'pos Position, - ) -> AlgebraicMoveFormatter<'m, 'pos> { - AlgebraicMoveFormatter { - position, - r#move: mv, - style: Style::default(), - } - } - - fn style(mut self, style: Style) -> Self { - self.style = style; - self - } - - fn fmt_kingside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0-0") - } - - fn fmt_queenside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0-0-0") - } - - fn fmt_short(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - unimplemented!() - } - - fn fmt_long(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Figure out how to write the short algebraic form, where a - // disambiguating coordiate is specified when two of the same piece - // cam move to the same square. - - // TODO: Write better pawn moves. - - let mv = self.r#move; - let from_square = mv.from_square(); - let to_square = mv.to_square(); - - let piece = self - .position - .piece_on_square(from_square) - .expect(&format!("No piece on {}", from_square)); - if piece.shape() != Shape::Pawn { - write!(f, "{}", piece.shape())?; - } - - write!( - f, - "{}{}{}", - from_square, - if mv.is_capture() { 'x' } else { '-' }, - to_square, - )?; - - if let Some(promotion) = mv.promotion() { - write!(f, "={}", promotion)?; - } - - // TODO: Write check (+) and checkmate (#) symbols - - Ok(()) - } - } - - impl<'pos, 'mv> fmt::Display for AlgebraicMoveFormatter<'mv, 'pos> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mv = self.r#move; - match mv.castle() { - Some(Castle::KingSide) => return self.fmt_kingside_castle(f), - Some(Castle::QueenSide) => return self.fmt_queenside_castle(f), - _ => {} - } - - match self.style { - Style::Short => self.fmt_short(f), - Style::Long => self.fmt_long(f), - } - } - } - - #[cfg(test)] - mod tests { - use super::{AlgebraicMoveFormatter, Style}; - use crate::position; - use chessfriend_core::piece; - - macro_rules! chess_move { - ($color:ident $shape:ident $from_square:ident - $to_square:ident) => { - $crate::MoveBuilder::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape, - ), - chessfriend_core::Square::$from_square, - chessfriend_core::Square::$to_square, - ) - .build() - }; - ($color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident) => { - $crate::MoveBuilder::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape, - ), - chessfriend_core::Square::$from_square, - chessfriend_core::Square::$to_square, - ) - .capturing(chessfriend_core::PlacedPiece::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$captured_color, - chessfriend_core::Shape::$captured_shape, - ), - chessfriend_core::Square::$to_square, - )) - .build() - }; - } - - macro_rules! test_algebraic_formatter { - ($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident, $output:expr) => { - #[test] - fn $test_name() { - let pos = position![ - $color $shape on $from_square, - $captured_color $captured_shape on $to_square, - ]; - let mv = chess_move!( - $color $shape $from_square x $to_square, - $captured_color $captured_shape - ); - - println!("{:?}", &mv); - - let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style); - assert_eq!(format!("{}", formatter), $output); - } - }; - ($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident - $to_square:ident, $output:expr) => { - #[test] - fn $test_name() { - let pos = position![ - $color $shape on $from_square, - ]; - - let mv = chess_move!($color $shape $from_square-$to_square); - println!("{:?}", &mv); - - let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style); - assert_eq!(format!("{}", formatter), $output); - } - }; - } - - test_algebraic_formatter!(long_pawn_move, Long, White Pawn E4 - E5, "e4-e5"); - test_algebraic_formatter!(long_bishop_move, Long, White Bishop A4 - D7, "Ba4-d7"); - test_algebraic_formatter!(long_bishop_capture, Long, White Bishop A2 x E6, Black Knight, "Ba2xe6"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use chessfriend_core::piece; - - 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() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::A5).build(); - assert_flags!(mv, true, false, false, false, false, false); - } - - #[test] - fn move_flags_double_push() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::C2, Square::C4).build(); - assert_flags!(mv, false, true, false, false, false, false); - } - - #[test] - fn move_flags_capture() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::B5) - .capturing(piece!(Black Bishop on B5)) - .build(); - assert_flags!(mv, false, false, false, true, false, false); - } - - #[test] - fn move_flags_en_passant_capture() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::A5, Square::B6) - .capturing_en_passant(piece!(Black Pawn on B5)) - .build(); - assert_flags!(mv, false, false, true, true, false, false); - } - - #[test] - fn move_flags_promotion() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::H8) - .promoting_to(Shape::Queen) - .build(); - assert_flags!(mv, false, false, false, false, false, true); - assert_eq!(mv.promotion(), Some(Shape::Queen)); - } - - #[test] - fn move_flags_capture_promotion() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::G8) - .capturing(piece!(Black Knight on G8)) - .promoting_to(Shape::Queen) - .build(); - assert_flags!(mv, false, false, false, true, false, true); - assert_eq!(mv.promotion(), Some(Shape::Queen)); - } - - #[test] - fn move_flags_castle() { - let mv = MoveBuilder::new(piece!(Black King), Square::E8, Square::G8) - .castle(Castle::KingSide) - .build(); - assert_flags!(mv, false, false, false, false, true, false); - } -} From 2a6b098cb8fd3fd3e21a75f409c8590983586469 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 10:51:27 -0800 Subject: [PATCH 24/29] Fix the pawn unit tests --- core/src/coordinates.rs | 11 +++ moves/src/en_passant.rs | 4 +- position/src/move_generator/move_set.rs | 106 ++++++++++++++++++------ position/src/move_generator/pawn.rs | 36 ++++---- 4 files changed, 112 insertions(+), 45 deletions(-) diff --git a/core/src/coordinates.rs b/core/src/coordinates.rs index 68ff3fe..54aeb1a 100644 --- a/core/src/coordinates.rs +++ b/core/src/coordinates.rs @@ -1,5 +1,6 @@ // Eryn Wells +use crate::Color; use std::fmt; use std::str::FromStr; @@ -168,6 +169,16 @@ impl Rank { /// assert_eq!(Rank::PAWN_STARTING_RANKS[Color::Black as usize], Rank::SEVEN); /// ``` pub const PAWN_STARTING_RANKS: [Rank; 2] = [Rank::TWO, Rank::SEVEN]; + + pub const PAWN_DOUBLE_PUSH_TARGET_RANKS: [Rank; 2] = [Rank::FOUR, Rank::FIVE]; + + pub fn is_pawn_starting_rank(&self, color: Color) -> bool { + self == &Self::PAWN_STARTING_RANKS[color as usize] + } + + pub fn is_pawn_double_push_target_rank(&self, color: Color) -> bool { + self == &Self::PAWN_DOUBLE_PUSH_TARGET_RANKS[color as usize] + } } #[rustfmt::skip] diff --git a/moves/src/en_passant.rs b/moves/src/en_passant.rs index ac57835..49e88a6 100644 --- a/moves/src/en_passant.rs +++ b/moves/src/en_passant.rs @@ -3,7 +3,7 @@ use chessfriend_core::{Rank, Square}; /// En passant information. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct EnPassant { target: Square, capture: Square, @@ -39,10 +39,12 @@ impl EnPassant { } } + /// The square the capturing piece will move to. pub fn target_square(&self) -> Square { self.target } + /// The square on which the captured pawn sits. pub fn capture_square(&self) -> Square { self.capture } diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs index f1042c9..5ba9467 100644 --- a/position/src/move_generator/move_set.rs +++ b/position/src/move_generator/move_set.rs @@ -2,7 +2,7 @@ use chessfriend_bitboard::BitBoard; use chessfriend_core::{PlacedPiece, Square}; -use chessfriend_moves::{Builder as MoveBuilder, Castle, Move}; +use chessfriend_moves::{Builder as MoveBuilder, Castle, EnPassant, Move}; /// A set of bitboards defining the moves for a single piece on the board. #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -11,12 +11,18 @@ struct BitBoardSet { captures: BitBoard, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum Special { + Pawn { en_passant: EnPassant }, + King { castles: u8 }, +} + /// A set of moves for a single piece on the board. #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct MoveSet { piece: PlacedPiece, bitboards: BitBoardSet, - special: u8, + special: Option, } impl MoveSet { @@ -24,7 +30,7 @@ impl MoveSet { MoveSet { piece, bitboards: BitBoardSet::default(), - special: 0, + special: None, } } @@ -33,9 +39,9 @@ impl MoveSet { } pub(crate) fn can_castle(&self, castle: Castle) -> bool { - match castle { - Castle::KingSide => (self.special & 0b1) != 0, - Castle::QueenSide => (self.special & 0b10) != 0, + match self.special { + Some(Special::King { castles }) => (castles & 1 << castle as u8) != 0, + _ => false, } } @@ -50,49 +56,99 @@ impl MoveSet { } pub(super) fn kingside_castle(&mut self) -> &mut MoveSet { - self.special |= 0b1; + match self.special { + Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::KingSide as u8, + _ => { + self.special = Some(Special::King { + castles: 1 << Castle::KingSide as u8, + }) + } + } + self } pub(super) fn queenside_castle(&mut self) -> &mut MoveSet { - self.special |= 0b10; + match self.special { + Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::QueenSide as u8, + _ => { + self.special = Some(Special::King { + castles: 1 << Castle::QueenSide as u8, + }) + } + } + self } - /// Return a BitBoard representing all possible moves. + pub(super) fn en_passant(&mut self, en_passant: EnPassant) -> &mut MoveSet { + self.special = Some(Special::Pawn { en_passant }); + self + } + + /// A `BitBoard` representing all possible moves. pub(super) fn bitboard(&self) -> BitBoard { self.bitboards.captures | self.bitboards.quiet } pub(crate) fn moves(&self) -> impl Iterator + '_ { let piece = &self.piece; + let color = piece.color(); + + let is_pawn_on_starting_rank = + piece.is_pawn() && piece.square().rank().is_pawn_starting_rank(color); self.bitboards .quiet .occupied_squares() - .filter_map(|to_square| MoveBuilder::push(&self.piece).to(to_square).build().ok()) + .filter_map(move |to_square| { + if is_pawn_on_starting_rank + && to_square.rank().is_pawn_double_push_target_rank(color) + { + MoveBuilder::double_push(piece.square().file(), color) + .build() + .ok() + } else { + MoveBuilder::push(piece).to(to_square).build().ok() + } + }) .chain( self.bitboards .captures .occupied_squares() - .filter_map(|to_square| MoveBuilder::push(piece).to(to_square).build().ok()), + .filter_map(|to_square| { + MoveBuilder::push(piece) + .capturing_on(to_square) + .build() + .ok() + }), ) - .chain( - if (self.special & 0b1) != 0 { - let mv = MoveBuilder::castling(Castle::KingSide).build(); - Some(mv) + .chain(self.castle_move(Castle::KingSide)) + .chain(self.castle_move(Castle::QueenSide)) + .chain(self.en_passant_move()) + } + + fn castle_move(&self, castle: Castle) -> Option { + match self.special { + Some(Special::King { castles }) => { + if (castles & 1 << castle as u8) != 0 { + Some(MoveBuilder::castling(castle).build()) } else { None } - .into_iter(), - ) - .chain( - if (self.special & 0b10) != 0 { - Some(MoveBuilder::castling(Castle::QueenSide).build()) - } else { - None - } - .into_iter(), - ) + } + _ => None, + } + } + + fn en_passant_move(&self) -> Option { + match self.special { + Some(Special::Pawn { en_passant }) => Some(unsafe { + MoveBuilder::push(&self.piece) + .capturing_en_passant_on(en_passant.target_square()) + .build_unchecked() + }), + _ => None, + } } } diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index 67fefb3..e5db4cf 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -4,7 +4,7 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; use crate::Position; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square}; -use chessfriend_moves::{Builder as MoveBuilder, Move}; +use chessfriend_moves::{EnPassant, Move}; use std::collections::BTreeMap; #[derive(Debug)] @@ -33,27 +33,33 @@ impl MoveGeneratorInternal for PawnMoveGenerator { let capture_moves = Self::attacks(position, &placed_piece) & capture_mask; let quiet_moves = Self::pushes(position, &placed_piece) & push_mask; - MoveSet::new(*placed_piece) + let mut move_set = MoveSet::new(*placed_piece) .quiet_moves(quiet_moves) - .capture_moves(capture_moves) + .capture_moves(capture_moves); + + if let Some(en_passant) = Self::en_passant(position, placed_piece) { + move_set.en_passant(en_passant); + } + + move_set } } impl PawnMoveGenerator { pub(super) fn new( position: &Position, - color: Color, + player_to_move: Color, capture_mask: BitBoard, push_mask: BitBoard, ) -> Self { let move_sets = if !capture_mask.is_empty() && !push_mask.is_empty() { - Self::move_sets(position, color, capture_mask, push_mask) + Self::move_sets(position, player_to_move, capture_mask, push_mask) } else { std::collections::BTreeMap::new() }; Self { - color, + color: player_to_move, move_sets, en_passant_captures: Vec::new(), } @@ -115,7 +121,7 @@ impl PawnMoveGenerator { BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces } - fn en_passant_attack(position: &Position, piece: &PlacedPiece) -> Option { + fn en_passant(position: &Position, piece: &PlacedPiece) -> Option { match position.en_passant() { Some(en_passant) => { let target_square = en_passant.target_square(); @@ -128,12 +134,7 @@ impl PawnMoveGenerator { } match position.piece_on_square(en_passant.capture_square()) { - Some(_) => Some( - MoveBuilder::push(piece) - .capturing_en_passant_on(target_square) - .build() - .ok()?, - ), + Some(_) => Some(en_passant), None => None, } } @@ -164,7 +165,7 @@ mod tests { let generated_moves: HashSet<_> = generator.iter().collect(); - assert_eq!(generated_moves, expected_moves); + assert_move_list!(generated_moves, expected_moves, pos); Ok(()) } @@ -278,15 +279,12 @@ mod tests { Black Pawn on E4, ], D3); - let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = PawnMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); let builder = MoveBuilder::push(&piece!(Black Pawn on E4)); let expected_moves = HashSet::from_iter([ - builder - .clone() - .capturing_en_passant_on(Square::D3) - .build()?, + builder.capturing_en_passant_on(Square::D3).build()?, builder.clone().to(Square::E3).build()?, ]); From d2fe546824bf18a773386090ed472bc1751d9602 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 10:52:10 -0800 Subject: [PATCH 25/29] Remove some unused variables from tests in the king move generator --- position/src/move_generator/king.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index 45c928e..33b9860 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -167,7 +167,6 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); - let king = piece!(White King); assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } @@ -187,7 +186,6 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); - let king = piece!(White King); assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } @@ -207,7 +205,6 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); - let king = piece!(White King); assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); } From 5f1fce6cc2d451d266662202a8de1a84cee610c7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 12:38:55 -0800 Subject: [PATCH 26/29] Fix the remaining tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Well… all the tests except the Peter Ellis Jones tests. Pass around whole EnPassant types instead of pulling out just the e.p. square. Make sure that Castling moves have their target and origin squares populated. Add a color field to the Castle move style to make this possible. --- moves/src/builder.rs | 41 ++++++++----- moves/tests/flags.rs | 2 +- position/src/fen.rs | 4 +- position/src/macros.rs | 3 +- position/src/move_generator/king.rs | 30 +++++++--- position/src/move_generator/move_set.rs | 44 ++++++++++++-- .../src/position/builders/move_builder.rs | 58 +++++++++---------- .../src/position/builders/position_builder.rs | 16 ++--- position/src/position/position.rs | 24 ++++---- position/src/sight.rs | 3 +- 10 files changed, 142 insertions(+), 83 deletions(-) diff --git a/moves/src/builder.rs b/moves/src/builder.rs index a1ab78e..5f02364 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -72,7 +72,7 @@ pub struct Capture { #[derive(Clone, Debug, Eq, PartialEq)] pub struct EnPassantCapture { push: Push, - capture: Option, + capture: Option, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -83,9 +83,16 @@ pub struct Promotion { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Castle { + color: Color, castle: castle::Castle, } +impl EnPassantCapture { + fn _build_move_bits(&self, origin_square: Square, target_square: Square) -> u16 { + (origin_square as u16 & 0b111111) << 4 | (target_square as u16 & 0b111111) << 10 + } +} + impl Style for Null {} impl Style for Push { @@ -108,7 +115,17 @@ impl Style for Capture { } } -impl Style for Castle {} +impl Style for Castle { + fn origin_square(&self) -> Option { + let parameters = self.castle.parameters(self.color); + Some(parameters.king_origin_square()) + } + + fn target_square(&self) -> Option { + let parameters = self.castle.parameters(self.color); + Some(parameters.king_target_square()) + } +} impl Style for DoublePush { fn origin_square(&self) -> Option { @@ -143,12 +160,6 @@ impl Style for EnPassantCapture { } } -impl EnPassantCapture { - fn _build_move_bits(&self, origin_square: Square, target_square: Square) -> u16 { - (origin_square as u16 & 0b111111) << 4 | (target_square as u16 & 0b111111) << 10 - } -} - impl Style for Promotion { fn origin_square(&self) -> Option { self.style.from @@ -236,9 +247,9 @@ impl Builder { } } - pub fn castling(castle: castle::Castle) -> Builder { + pub fn castling(color: Color, castle: castle::Castle) -> Builder { Builder { - style: Castle { castle }, + style: Castle { color, castle }, } } @@ -283,8 +294,8 @@ impl Builder { } } - pub fn capturing_en_passant_on(&self, square: Square) -> Builder { - match EnPassant::from_target_square(square) { + pub fn capturing_en_passant_on(&self, target_square: Square) -> Builder { + match EnPassant::from_target_square(target_square) { Some(en_passant) => { let mut style = self.style.clone(); style.to = Some(en_passant.target_square()); @@ -292,7 +303,7 @@ impl Builder { Builder { style: EnPassantCapture { push: style, - capture: Some(en_passant.capture_square()), + capture: Some(en_passant), }, } } @@ -333,8 +344,8 @@ impl Builder { bits as u16 } - pub fn build(&self) -> Move { - Move(self.bits()) + pub fn build(&self) -> Result { + Ok(Move(self.bits() | self.style.into_move_bits()?)) } } diff --git a/moves/tests/flags.rs b/moves/tests/flags.rs index cdcdd57..6d001cb 100644 --- a/moves/tests/flags.rs +++ b/moves/tests/flags.rs @@ -101,7 +101,7 @@ fn move_flags_capture_promotion() -> TestResult { #[test] fn move_flags_castle() -> TestResult { - let mv = Builder::castling(Castle::KingSide).build(); + let mv = Builder::castling(Color::White, Castle::KingSide).build()?; assert_flags!(mv, false, false, false, false, true, false); diff --git a/position/src/fen.rs b/position/src/fen.rs index fab45e1..8859324 100644 --- a/position/src/fen.rs +++ b/position/src/fen.rs @@ -2,7 +2,7 @@ use crate::{Position, PositionBuilder}; use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square}; -use chessfriend_moves::Castle; +use chessfriend_moves::{Castle, EnPassant}; use std::fmt::Write; macro_rules! fen { @@ -195,7 +195,7 @@ impl FromFen for Position { let en_passant_square = fields.next().ok_or(FromFenError)?; if en_passant_square != "-" { let square = Square::from_algebraic_str(en_passant_square).map_err(|_| FromFenError)?; - builder.en_passant_square(Some(square)); + builder.en_passant(Some(EnPassant::from_target_square(square).unwrap())); } let half_move_clock = fields.next().ok_or(FromFenError)?; diff --git a/position/src/macros.rs b/position/src/macros.rs index c7998d7..260a502 100644 --- a/position/src/macros.rs +++ b/position/src/macros.rs @@ -16,6 +16,7 @@ macro_rules! position { }; } +#[cfg(test)] #[macro_export] macro_rules! test_position { ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => { @@ -31,7 +32,7 @@ macro_rules! test_position { )) )* .to_move(chessfriend_core::Color::$to_move) - .en_passant_square(Some(chessfriend_core::Square::$en_passant)) + .en_passant(Some(chessfriend_moves::EnPassant::from_target_square(chessfriend_core::Square::$en_passant)).unwrap()) .build(); println!("{pos}"); diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index 33b9860..3443d46 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -154,7 +154,7 @@ mod tests { } #[test] - fn white_king_unobstructed_castles() { + fn white_king_unobstructed_castles() -> TestResult { let pos = test_position!( White King on E1, White Rook on A1, @@ -167,12 +167,16 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); - assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); - assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); + assert!(generated_moves + .contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?)); + assert!(generated_moves + .contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?)); + + Ok(()) } #[test] - fn white_king_obstructed_queenside_castle() { + fn white_king_obstructed_queenside_castle() -> TestResult { let pos = test_position!( White King on E1, White Knight on B1, @@ -186,12 +190,16 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); - assert!(generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); - assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); + assert!(generated_moves + .contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?)); + assert!(!generated_moves + .contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?)); + + Ok(()) } #[test] - fn white_king_obstructed_kingside_castle() { + fn white_king_obstructed_kingside_castle() -> TestResult { let pos = test_position!( White King on E1, White Rook on A1, @@ -205,7 +213,11 @@ mod tests { let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); - assert!(!generated_moves.contains(&MoveBuilder::castling(Castle::KingSide).build())); - assert!(generated_moves.contains(&MoveBuilder::castling(Castle::QueenSide).build())); + assert!(!generated_moves + .contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?)); + assert!(generated_moves + .contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?)); + + Ok(()) } } diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs index 5ba9467..61adb03 100644 --- a/position/src/move_generator/move_set.rs +++ b/position/src/move_generator/move_set.rs @@ -34,17 +34,49 @@ impl MoveSet { } } - pub(crate) fn can_move_to_square(&self, to: Square) -> bool { - self.bitboard().is_set(to) + pub(crate) fn can_move_to_square(&self, target_square: Square) -> bool { + match self.special { + Some(Special::King { castles }) => { + if self.check_castle_field(castles, Castle::KingSide) + && target_square + == Castle::KingSide + .parameters(self.piece.color()) + .king_target_square() + { + return true; + } + + if self.check_castle_field(castles, Castle::KingSide) + && target_square + == Castle::QueenSide + .parameters(self.piece.color()) + .king_target_square() + { + return true; + } + } + Some(Special::Pawn { en_passant }) => { + if target_square == en_passant.target_square() { + return true; + } + } + None => {} + } + + self.bitboard().is_set(target_square) } pub(crate) fn can_castle(&self, castle: Castle) -> bool { match self.special { - Some(Special::King { castles }) => (castles & 1 << castle as u8) != 0, + Some(Special::King { castles }) => self.check_castle_field(castles, castle), _ => false, } } + fn check_castle_field(&self, castle_field: u8, castle: Castle) -> bool { + (castle_field & 1 << castle as u8) != 0 + } + pub(super) fn quiet_moves(mut self, bitboard: BitBoard) -> MoveSet { self.bitboards.quiet = bitboard; self @@ -132,7 +164,11 @@ impl MoveSet { match self.special { Some(Special::King { castles }) => { if (castles & 1 << castle as u8) != 0 { - Some(MoveBuilder::castling(castle).build()) + Some( + MoveBuilder::castling(self.piece.color(), castle) + .build() + .ok()?, + ) } else { None } diff --git a/position/src/position/builders/move_builder.rs b/position/src/position/builders/move_builder.rs index 900c322..307bae6 100644 --- a/position/src/position/builders/move_builder.rs +++ b/position/src/position/builders/move_builder.rs @@ -3,7 +3,7 @@ use crate::{position::flags::Flags, Position}; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; -use chessfriend_moves::{Castle, Move}; +use chessfriend_moves::{Castle, EnPassant, Move}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum MakeMoveError { @@ -34,7 +34,7 @@ pub enum ValidatedMove { captured_piece: Option, promotion: Option, flags: Flags, - en_passant_square: Option, + en_passant: Option, increment_ply: bool, }, Castle { @@ -149,11 +149,12 @@ where }, }) } else { - let en_passant_square: Option = if mv.is_double_push() { + let en_passant = if mv.is_double_push() { match piece.color() { Color::White => target_square.neighbor(Direction::South), Color::Black => target_square.neighbor(Direction::North), } + .and_then(EnPassant::from_target_square) } else { None }; @@ -167,7 +168,7 @@ where captured_piece, promotion: mv.promotion(), flags, - en_passant_square, + en_passant, increment_ply: !(mv.is_capture() || piece.is_pawn()), }, }) @@ -190,7 +191,7 @@ impl<'p> Builder<'p, ValidatedMove> { captured_piece, promotion, flags, - en_passant_square, + en_passant, increment_ply, } => { let mut pieces = self.position.piece_bitboards().clone(); @@ -217,7 +218,7 @@ impl<'p> Builder<'p, ValidatedMove> { self.position.player_to_move().other(), flags, pieces, - en_passant_square, + en_passant, ply, updated_move_number, ) @@ -270,8 +271,8 @@ impl<'p> From<&'p Position> for Builder<'p, NoMove> { mod tests { use super::*; use crate::testing::*; - use crate::{position, PositionBuilder}; - use chessfriend_core::piece; + use crate::{position, test_position}; + use chessfriend_core::{piece, File}; use chessfriend_moves::Builder as MoveBuilder; #[test] @@ -299,22 +300,22 @@ mod tests { #[test] fn move_white_pawn_two_squares() -> TestResult { - let pos = position![White Pawn on E2]; - let mv = MoveBuilder::new() - .from(Square::E2) - .to(Square::E4) - .build() - .map_err(TestError::BuildMove)?; + let pos = test_position![White Pawn on E2]; - let new_position = Builder::::new(&pos).make(&mv)?.build(); + let mv = MoveBuilder::double_push(File::E, Color::White).build()?; + + let new_position = Builder::new(&pos).make(&mv)?.build(); println!("{}", &new_position); assert_eq!( new_position.piece_on_square(Square::E4), Some(piece!(White Pawn on E4)) ); + + let en_passant = new_position.en_passant(); + assert!(en_passant.is_some()); assert_eq!( - new_position.en_passant().map(|ep| ep.target_square()), + en_passant.as_ref().map(EnPassant::target_square), Some(Square::E3) ); @@ -322,8 +323,8 @@ mod tests { } #[test] - fn white_kingside_castle() -> Result<(), MakeMoveError> { - let pos = position![ + fn white_kingside_castle() -> TestResult { + let pos = test_position![ White King on E1, White Rook on H1, White Pawn on E2, @@ -331,11 +332,10 @@ mod tests { White Pawn on G2, White Pawn on H2 ]; - println!("{}", &pos); - let mv = MoveBuilder::castling(Castle::KingSide).build(); + let mv = MoveBuilder::castling(Color::White, Castle::KingSide).build()?; - let new_position = Builder::::new(&pos).make(&mv)?.build(); + let new_position = Builder::new(&pos).make(&mv)?.build(); println!("{}", &new_position); assert_eq!( @@ -352,14 +352,12 @@ mod tests { #[test] fn en_passant_capture() -> TestResult { - let pos = PositionBuilder::new() - .place_piece(piece!(White Pawn on B5)) - .place_piece(piece!(Black Pawn on A7)) - .to_move(Color::Black) - .build(); - println!("{pos}"); + let pos = test_position!(Black, [ + White Pawn on B5, + Black Pawn on A7, + ]); - let black_pawn_move = MoveBuilder::new().from(Square::A7).to(Square::A5).build()?; + let black_pawn_move = MoveBuilder::double_push(File::A, Color::Black).build()?; assert!(black_pawn_move.is_double_push()); assert!(!black_pawn_move.is_en_passant()); @@ -377,9 +375,9 @@ mod tests { ); let white_pawn_capture = MoveBuilder::push(&piece!(White Pawn on B5)) - .capturing_en_passant_on(Square::A5) + .capturing_en_passant_on(Square::A6) .build()?; - let en_passant_capture = Builder::::new(&en_passant_position) + let en_passant_capture = Builder::new(&en_passant_position) .make(&white_pawn_capture)? .build(); println!("{en_passant_capture}"); diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs index a4f8d2f..08fc193 100644 --- a/position/src/position/builders/position_builder.rs +++ b/position/src/position/builders/position_builder.rs @@ -5,7 +5,7 @@ use crate::{ Position, }; use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square}; -use chessfriend_moves::Castle; +use chessfriend_moves::{Castle, EnPassant}; use std::collections::BTreeMap; #[derive(Clone)] @@ -14,7 +14,7 @@ pub struct Builder { flags: Flags, pieces: BTreeMap, kings: [Option; 2], - en_passant_square: Option, + en_passant: Option, ply_counter: u16, move_number: u16, } @@ -30,7 +30,7 @@ impl Builder { flags: Flags::default(), pieces: BTreeMap::default(), kings: [None, None], - en_passant_square: None, + en_passant: None, ply_counter: 0, move_number: 1, } @@ -52,7 +52,7 @@ impl Builder { flags: position.flags(), pieces, kings: [Some(white_king), Some(black_king)], - en_passant_square: position.en_passant().map(|ep| ep.target_square()), + en_passant: position.en_passant(), ply_counter: position.ply_counter(), move_number: position.move_number(), } @@ -73,8 +73,8 @@ impl Builder { self } - pub fn en_passant_square(&mut self, square: Option) -> &mut Self { - self.en_passant_square = square; + pub fn en_passant(&mut self, en_passant: Option) -> &mut Self { + self.en_passant = en_passant; self } @@ -138,7 +138,7 @@ impl Builder { self.player_to_move, flags, pieces, - self.en_passant_square, + self.en_passant, self.ply_counter, self.move_number, ) @@ -173,7 +173,7 @@ impl Default for Builder { flags: Flags::default(), pieces: pieces, kings: [Some(white_king_square), Some(black_king_square)], - en_passant_square: None, + en_passant: None, ply_counter: 0, move_number: 1, } diff --git a/position/src/position/position.rs b/position/src/position/position.rs index d336703..a6fc369 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -17,7 +17,7 @@ pub struct Position { color_to_move: Color, flags: Flags, pieces: PieceBitBoards, - en_passant_square: Option, + en_passant: Option, moves: OnceCell, half_move_counter: u16, full_move_number: u16, @@ -183,15 +183,15 @@ impl Position { } pub fn has_en_passant_square(&self) -> bool { - self.en_passant_square.is_some() + self.en_passant.is_some() } pub fn en_passant(&self) -> Option { - EnPassant::from_target_square(self.en_passant_square?) + self.en_passant } fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard { - let en_passant_square = self.en_passant_square; + let en_passant_target_square = self.en_passant.map(|ep| ep.target_square()); Shape::ALL .iter() @@ -206,7 +206,7 @@ impl Position { }) .flat_map(|(piece, &bitboard)| { bitboard.occupied_squares().map(move |square| { - PlacedPiece::new(piece, square).sight(pieces, en_passant_square) + PlacedPiece::new(piece, square).sight(pieces, en_passant_target_square) }) }) .fold(BitBoard::empty(), |acc, sight| acc | sight) @@ -218,7 +218,7 @@ impl Position { #[cfg(test)] pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard { - piece.sight(&self.pieces, self.en_passant_square) + piece.sight(&self.pieces, self.en_passant.map(|ep| ep.target_square())) } /// A bitboard representing the squares where a king of the given color will @@ -299,14 +299,14 @@ impl Position { player_to_move: Color, flags: Flags, pieces: PieceBitBoards, - en_passant_square: Option, + en_passant: Option, half_move_counter: u16, full_move_number: u16, ) -> Self { Self { color_to_move: player_to_move, flags, - en_passant_square, + en_passant, pieces, half_move_counter, full_move_number, @@ -336,8 +336,8 @@ impl Position { #[cfg(test)] impl Position { - pub(crate) fn test_set_en_passant_square(&mut self, square: Square) { - self.en_passant_square = Some(square); + pub(crate) fn test_set_en_passant(&mut self, en_passant: EnPassant) { + self.en_passant = Some(en_passant); } } @@ -347,7 +347,7 @@ impl Default for Position { color_to_move: Color::White, flags: Flags::default(), pieces: PieceBitBoards::default(), - en_passant_square: None, + en_passant: None, moves: OnceCell::new(), half_move_counter: 0, full_move_number: 1, @@ -360,7 +360,7 @@ impl PartialEq for Position { self.pieces == other.pieces && self.color_to_move == other.color_to_move && self.flags == other.flags - && self.en_passant_square == other.en_passant_square + && self.en_passant == other.en_passant } } diff --git a/position/src/sight.rs b/position/src/sight.rs index 056ae45..29cbf91 100644 --- a/position/src/sight.rs +++ b/position/src/sight.rs @@ -242,6 +242,7 @@ mod tests { use crate::test_position; use chessfriend_bitboard::{bitboard, BitBoard}; use chessfriend_core::{piece, Square}; + use chessfriend_moves::EnPassant; sight_test!(e4_pawn, piece!(White Pawn on E4), bitboard!(D5, F5)); @@ -289,7 +290,7 @@ mod tests { White Pawn on E5, Black Pawn on D5, ); - pos.test_set_en_passant_square(Square::D6); + pos.test_set_en_passant(EnPassant::from_target_square(Square::D6).unwrap()); let piece = piece!(White Pawn on E5); let sight = pos.sight_of_piece(&piece); From 673d57c02e29998e7b1cfd1de8894c6e1c359277 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 14:12:51 -0800 Subject: [PATCH 27/29] Remove some unused imports from position::check --- position/src/check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/position/src/check.rs b/position/src/check.rs index 05e34b7..a3f2804 100644 --- a/position/src/check.rs +++ b/position/src/check.rs @@ -1,7 +1,7 @@ // Eryn Wells use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Direction, Shape, Square}; +use chessfriend_core::Shape; use crate::sight::SliderRayToSquareExt; From 8f07e08500d7ac67493a69e4a2bd5a6b85ec8857 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 14:51:25 -0800 Subject: [PATCH 28/29] Fix a couple of the obscure en passant cases from PEJ --- position/src/move_generator.rs | 2 +- position/src/move_generator/pawn.rs | 73 +++++++++++++++++-- .../move_generator/tests/peterellisjones.rs | 22 +++--- 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index 4d14ece..c4271ce 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -56,7 +56,7 @@ macro_rules! move_generator_declaration { push_mask: chessfriend_bitboard::BitBoard, ) -> $name { let move_sets = if Self::shape() == chessfriend_core::Shape::King - || (!capture_mask.is_empty() && !push_mask.is_empty()) + || !(capture_mask.is_empty() && push_mask.is_empty()) { Self::move_sets(position, color, capture_mask, push_mask) } else { diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index e5db4cf..1a07c4c 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -37,7 +37,9 @@ impl MoveGeneratorInternal for PawnMoveGenerator { .quiet_moves(quiet_moves) .capture_moves(capture_moves); - if let Some(en_passant) = Self::en_passant(position, placed_piece) { + if let Some(en_passant) = + Self::en_passant(position, placed_piece, &push_mask, &capture_mask) + { move_set.en_passant(en_passant); } @@ -52,7 +54,7 @@ impl PawnMoveGenerator { capture_mask: BitBoard, push_mask: BitBoard, ) -> Self { - let move_sets = if !capture_mask.is_empty() && !push_mask.is_empty() { + let move_sets = if !(capture_mask.is_empty() && push_mask.is_empty()) { Self::move_sets(position, player_to_move, capture_mask, push_mask) } else { std::collections::BTreeMap::new() @@ -121,14 +123,26 @@ impl PawnMoveGenerator { BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces } - fn en_passant(position: &Position, piece: &PlacedPiece) -> Option { + fn en_passant( + position: &Position, + piece: &PlacedPiece, + push_mask: &BitBoard, + capture_mask: &BitBoard, + ) -> Option { match position.en_passant() { Some(en_passant) => { - let target_square = en_passant.target_square(); + let target_square: BitBoard = en_passant.target_square().into(); + let capture_square: BitBoard = en_passant.capture_square().into(); - let en_passant_bitboard: BitBoard = target_square.into(); - let capture = - BitBoard::pawn_attacks(piece.square(), piece.color()) & en_passant_bitboard; + if (target_square & push_mask).is_empty() + && (capture_square & capture_mask).is_empty() + { + // Do not allow en passant if capturing would not either + // block an active check, or capture a checking pawn. + return None; + } + + let capture = BitBoard::pawn_attacks(piece.square(), piece.color()) & target_square; if capture.is_empty() { return None; } @@ -141,12 +155,28 @@ impl PawnMoveGenerator { None => None, } } + + #[cfg(none)] + fn does_en_passant_reveal_check(&self, position: &Position) -> bool { + let player_to_move = position.player_to_move(); + let opposing_player = player_to_move.other(); + + if position.king_square(opposing_player).rank() + != Rank::PAWN_DOUBLE_PUSH_TARGET_RANKS[player_to_move as usize] + { + return false; + } + false + } } #[cfg(test)] mod tests { use super::*; - use crate::{assert_move_list, position::DiagramFormatter, test_position, testing::*}; + use crate::{ + assert_move_list, formatted_move_list, position::DiagramFormatter, test_position, + testing::*, + }; use chessfriend_core::{piece, Color, Square}; use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::HashSet; @@ -292,4 +322,31 @@ mod tests { Ok(()) } + + /// Make sure the player cannot capture en passant if doing so would not resolve the check. + #[test] + fn cannot_capture_en_passant_while_in_check() -> TestResult { + let pos = test_position!(Black, [ + Black King on B5, + Black Pawn on E4, + White Pawn on D4, + White Rook on B1, + ], D3); + + assert!(pos.is_king_in_check()); + + let generated_moves: HashSet<_> = pos.moves().iter().collect(); + + assert!( + !generated_moves.contains( + &MoveBuilder::push(&piece!(Black Pawn on E4)) + .capturing_en_passant_on(Square::D3) + .build()? + ), + "Valid moves: {:?}", + formatted_move_list!(generated_moves, pos) + ); + + Ok(()) + } } diff --git a/position/src/move_generator/tests/peterellisjones.rs b/position/src/move_generator/tests/peterellisjones.rs index 31a878a..4a12b92 100644 --- a/position/src/move_generator/tests/peterellisjones.rs +++ b/position/src/move_generator/tests/peterellisjones.rs @@ -145,12 +145,12 @@ fn en_passant_check_capture() -> TestResult { assert!(pos.is_king_in_check()); - let generated_moves = pos.moves().iter().collect::>(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); assert!( generated_moves.contains( &MoveBuilder::push(&piece!(Black Pawn on E4)) - .capturing_en_passant_on(Square::D4) + .capturing_en_passant_on(Square::D3) .build()? ), "Valid moves: {:?}", @@ -171,12 +171,12 @@ fn en_passant_check_block() -> TestResult { assert!(pos.is_king_in_check()); - let generated_moves = pos.moves().iter().collect::>(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); assert!( generated_moves.contains( &MoveBuilder::push(&piece!(Black Pawn on E4)) - .capturing_en_passant_on(Square::D4) + .capturing_en_passant_on(Square::D3) .build()? ), "Valid moves: {:?}", @@ -187,7 +187,7 @@ fn en_passant_check_block() -> TestResult { } #[test] -fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> { +fn pinned_pieces_rook_cannot_move_out_of_pin() -> TestResult { let pos = test_position!(Black, [ Black King on E8, Black Rook on E6, @@ -200,15 +200,15 @@ fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> { let generated_moves = pos.moves(); let rook_moves = generated_moves .moves_for_piece(&piece!(Black Rook on E6)) - .ok_or("No valid rook moves")?; + .ok_or(TestError::NoLegalMoves)?; assert!(!rook_moves.can_move_to_square(Square::D6)); assert!(!rook_moves.can_move_to_square(Square::F6)); - assert!(rook_moves.can_move_to_square(Square::E7)); - assert!(rook_moves.can_move_to_square(Square::E5)); - assert!(rook_moves.can_move_to_square(Square::E4)); assert!(rook_moves.can_move_to_square(Square::E3)); + assert!(rook_moves.can_move_to_square(Square::E4)); + assert!(rook_moves.can_move_to_square(Square::E5)); + assert!(rook_moves.can_move_to_square(Square::E7)); Ok(()) } @@ -222,10 +222,10 @@ fn en_passant_discovered_check() -> TestResult { White Queen on H4, ], D3); - let generated_moves = pos.moves().iter().collect::>(); + let generated_moves: HashSet<_> = pos.moves().iter().collect(); let unexpected_move = MoveBuilder::push(&piece!(Black Pawn on E4)) - .capturing_en_passant_on(Square::D4) + .capturing_en_passant_on(Square::D3) .build()?; assert!( From 8a1b16d553c0ed2a36db6583c73e9e7ee457eb5e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 14:51:32 -0800 Subject: [PATCH 29/29] Export the fen! macro --- position/src/fen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/position/src/fen.rs b/position/src/fen.rs index 8859324..fde920c 100644 --- a/position/src/fen.rs +++ b/position/src/fen.rs @@ -5,6 +5,7 @@ use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square}; use chessfriend_moves::{Castle, EnPassant}; use std::fmt::Write; +#[macro_export] macro_rules! fen { ($fen_string:literal) => { Position::from_fen_str($fen_string)