[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:
Eryn Wells 2024-02-03 15:16:00 -08:00
parent 6c14851806
commit f4e57d7d6c
5 changed files with 282 additions and 3 deletions

View file

@ -12,7 +12,6 @@ mod sight;
#[macro_use]
mod macros;
#[cfg(test)]
#[macro_use]
mod tests;

View file

@ -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(

View file

@ -1,3 +1,4 @@
// Eryn Wells <eryn@erynwells.me>
mod peterellisjones;
mod single_pieces;

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

View file

@ -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>>()
};
}