From 5f1fce6cc2d451d266662202a8de1a84cee610c7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 25 Feb 2024 12:38:55 -0800 Subject: [PATCH] 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);