[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

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