[position] Implement all the example positions from Peter Ellis Jones' blog post
https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/
This commit is contained in:
parent
6c14851806
commit
f4e57d7d6c
5 changed files with 282 additions and 3 deletions
|
@ -12,7 +12,6 @@ mod sight;
|
|||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod tests;
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
mod peterellisjones;
|
||||
mod single_pieces;
|
||||
|
|
226
position/src/move_generator/tests/peterellisjones.rs
Normal file
226
position/src/move_generator/tests/peterellisjones.rs
Normal file
|
@ -0,0 +1,226 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
//! 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::<HashSet<_>>(),
|
||||
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::<HashSet<_>>(),
|
||||
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::<HashSet<_>>(),
|
||||
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::<HashSet<_>>();
|
||||
|
||||
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::<HashSet<_>>();
|
||||
|
||||
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::<HashSet<_>>();
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
|
@ -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::<Vec<String>>()
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue