diff --git a/position/src/lib.rs b/position/src/lib.rs index dde3e5d..5776e05 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -12,7 +12,6 @@ mod sight; #[macro_use] mod macros; -#[cfg(test)] #[macro_use] mod tests; diff --git a/position/src/macros.rs b/position/src/macros.rs index 178b4c4..c7998d7 100644 --- a/position/src/macros.rs +++ b/position/src/macros.rs @@ -16,10 +16,48 @@ macro_rules! position { }; } -#[cfg(test)] #[macro_export] macro_rules! test_position { - [$($color:ident $shape:ident on $square:ident),* $(,)?] => { + ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => { + { + let pos = $crate::PositionBuilder::new() + $(.place_piece( + chessfriend_core::PlacedPiece::new( + chessfriend_core::Piece::new( + chessfriend_core::Color::$color, + chessfriend_core::Shape::$shape + ), + chessfriend_core::Square::$square + )) + )* + .to_move(chessfriend_core::Color::$to_move) + .en_passant_square(Some(chessfriend_core::Square::$en_passant)) + .build(); + println!("{pos}"); + + pos + } + }; + ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ]) => { + { + let pos = $crate::PositionBuilder::new() + $(.place_piece( + chessfriend_core::PlacedPiece::new( + chessfriend_core::Piece::new( + chessfriend_core::Color::$color, + chessfriend_core::Shape::$shape + ), + chessfriend_core::Square::$square + )) + )* + .to_move(chessfriend_core::Color::$to_move) + .build(); + println!("{pos}"); + + pos + } + }; + ($($color:ident $shape:ident on $square:ident),* $(,)?) => { { let pos = $crate::PositionBuilder::new() $(.place_piece( diff --git a/position/src/move_generator/tests.rs b/position/src/move_generator/tests.rs index 777faf8..e6674d3 100644 --- a/position/src/move_generator/tests.rs +++ b/position/src/move_generator/tests.rs @@ -1,3 +1,4 @@ // Eryn Wells +mod peterellisjones; mod single_pieces; diff --git a/position/src/move_generator/tests/peterellisjones.rs b/position/src/move_generator/tests/peterellisjones.rs new file mode 100644 index 0000000..52b0269 --- /dev/null +++ b/position/src/move_generator/tests/peterellisjones.rs @@ -0,0 +1,226 @@ +// Eryn Wells + +//! Move generator tests based on board positions described in [Peter Ellis +//! Jones][1]' excellent [blog post][2] on generated legal chess moves. +//! +//! [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 chessfriend_core::{piece, Square}; +use std::collections::HashSet; + +#[test] +fn pseudo_legal_move_generation() -> Result<(), String> { + let pos = test_position!(Black, [ + Black King on E8, + White King on E1, + White Rook on F5, + ]); + + let generated_moves = pos.moves(); + let king_moves = generated_moves + .moves_for_piece(&piece!(Black King on E8)) + .ok_or("No valid king moves")?; + + assert!(!king_moves.can_move_to_square(Square::F8)); + assert!(!king_moves.can_move_to_square(Square::F7)); + + Ok(()) +} + +#[test] +fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> { + let pos = test_position!(Black, [ + Black King on E7, + White King on E1, + White Rook on E4, + ]); + + let generated_moves = pos.moves(); + let king_moves = generated_moves + .moves_for_piece(&piece!(Black King on E7)) + .ok_or("No valid king moves")?; + + assert!(!king_moves.can_move_to_square(Square::E8)); + + Ok(()) +} + +#[test] +fn check_evasions_1() { + let pos = test_position!(Black, [ + Black King on E8, + White King on E1, + White Knight on F6, + ]); + + let generated_moves = pos.moves(); + + 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(), + ]); + + assert_move_list!( + generated_moves.iter().collect::>(), + expected_moves, + pos + ); +} + +#[test] +fn check_evasions_double_check() { + let pos = test_position!(Black, [ + Black King on E8, + Black Bishop on F6, + White King on E1, + White Knight on G7, + White Rook on E5, + ]); + + let generated_moves = pos.moves(); + + 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(), + ]); + + assert_move_list!( + generated_moves.iter().collect::>(), + expected_moves, + pos + ); +} + +#[test] +fn single_check_with_blocker() { + let pos = test_position!(Black, [ + Black King on E8, + Black Knight on G6, + White King on E1, + White Rook on E5, + ]); + + let generated_moves = pos.moves(); + + 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(), + ]); + + assert_move_list!( + generated_moves.iter().collect::>(), + expected_moves, + pos + ); +} + +#[test] +fn en_passant_check_capture() { + let pos = test_position!(Black, [ + Black King on C5, + Black Pawn on E4, + White Pawn on D4, + ], D3); + + assert!(pos.is_king_in_check()); + + let generated_moves = pos.moves().iter().collect::>(); + + assert!( + generated_moves.contains( + &MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) + .capturing_en_passant(piece!(White Pawn on D4)) + .build() + ), + "Valid moves: {:?}", + formatted_move_list!(generated_moves, pos) + ); +} + +#[test] +fn en_passant_check_block() { + let pos = test_position!(Black, [ + Black King on B5, + Black Pawn on E4, + White Pawn on D4, + White Queen on F1, + ], D3); + + assert!(pos.is_king_in_check()); + + let generated_moves = pos.moves().iter().collect::>(); + + assert!( + generated_moves.contains( + &MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) + .capturing_en_passant(piece!(White Pawn on D4)) + .build() + ), + "Valid moves: {:?}", + formatted_move_list!(generated_moves, pos) + ); +} + +#[test] +fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> { + let pos = test_position!(Black, [ + Black King on E8, + Black Rook on E6, + White Queen on E3, + White King on C1, + ]); + + assert!(!pos.is_king_in_check()); + + let generated_moves = pos.moves(); + let rook_moves = generated_moves + .moves_for_piece(&piece!(Black Rook on E6)) + .ok_or("No valid rook moves")?; + + 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)); + + Ok(()) +} + +#[test] +fn en_passant_discovered_check() { + let pos = test_position!(Black, [ + Black King on A4, + Black Pawn on E4, + White Pawn on D4, + White Queen on H4, + ], D3); + + let generated_moves = pos.moves().iter().collect::>(); + + assert!( + generated_moves.contains( + &MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3) + .capturing_en_passant(piece!(White Pawn on D4)) + .build() + ), + "Valid moves: {:?}", + formatted_move_list!(generated_moves, pos) + ); +} diff --git a/position/src/tests.rs b/position/src/tests.rs index 8663afa..962b9bc 100644 --- a/position/src/tests.rs +++ b/position/src/tests.rs @@ -17,3 +17,18 @@ macro_rules! assert_move_list { ) }; } + +#[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::>() + }; +}