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);