238 lines
6.2 KiB
Rust
238 lines
6.2 KiB
Rust
// 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, test_position, testing::*};
|
|
use chessfriend_core::{piece, Square};
|
|
use chessfriend_moves::Builder as MoveBuilder;
|
|
use std::collections::HashSet;
|
|
|
|
#[test]
|
|
fn pseudo_legal_move_generation() -> TestResult {
|
|
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(TestError::NoLegalMoves)?;
|
|
|
|
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() -> TestResult {
|
|
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(TestError::NoLegalMoves)?;
|
|
|
|
assert!(!king_moves.can_move_to_square(Square::E8));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn check_evasions_1() -> TestResult {
|
|
let pos = test_position!(Black, [
|
|
Black King on E8,
|
|
White King on E1,
|
|
White Knight on F6,
|
|
]);
|
|
|
|
let generated_moves = pos.moves();
|
|
|
|
let builder = MoveBuilder::push(&piece!(Black King on E8));
|
|
let expected_moves = HashSet::from_iter([
|
|
builder.clone().to(Square::D8).build()?,
|
|
builder.clone().to(Square::E7).build()?,
|
|
builder.clone().to(Square::F7).build()?,
|
|
builder.clone().to(Square::F8).build()?,
|
|
]);
|
|
|
|
assert_move_list!(
|
|
generated_moves.iter().collect::<HashSet<_>>(),
|
|
expected_moves,
|
|
pos
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn check_evasions_double_check() -> TestResult {
|
|
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 builder = MoveBuilder::push(&piece!(Black King on E8));
|
|
let expected_moves = HashSet::from_iter([
|
|
builder.clone().to(Square::D8).build()?,
|
|
builder.clone().to(Square::D7).build()?,
|
|
builder.clone().to(Square::F7).build()?,
|
|
builder.clone().to(Square::F8).build()?,
|
|
]);
|
|
|
|
assert_move_list!(
|
|
generated_moves.iter().collect::<HashSet<_>>(),
|
|
expected_moves,
|
|
pos
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn single_check_with_blocker() -> TestResult {
|
|
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 king_builder = MoveBuilder::push(&piece!(Black King on E8));
|
|
let knight_builder = MoveBuilder::push(&piece!(Black Knight on G6));
|
|
let expected_moves = HashSet::from_iter([
|
|
king_builder.clone().to(Square::D8).build()?,
|
|
king_builder.clone().to(Square::D7).build()?,
|
|
king_builder.clone().to(Square::F7).build()?,
|
|
king_builder.clone().to(Square::F8).build()?,
|
|
knight_builder.clone().to(Square::E7).build()?,
|
|
knight_builder.clone().capturing_on(Square::E5).build()?,
|
|
]);
|
|
|
|
assert_move_list!(
|
|
generated_moves.iter().collect::<HashSet<_>>(),
|
|
expected_moves,
|
|
pos
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn en_passant_check_capture() -> TestResult {
|
|
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: HashSet<_> = pos.moves().iter().collect();
|
|
|
|
assert!(
|
|
generated_moves.contains(
|
|
&MoveBuilder::push(&piece!(Black Pawn on E4))
|
|
.capturing_en_passant_on(Square::D3)
|
|
.build()?
|
|
),
|
|
"Valid moves: {:?}",
|
|
formatted_move_list!(generated_moves, pos)
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn en_passant_check_block() -> TestResult {
|
|
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: HashSet<_> = pos.moves().iter().collect();
|
|
|
|
assert!(
|
|
generated_moves.contains(
|
|
&MoveBuilder::push(&piece!(Black Pawn on E4))
|
|
.capturing_en_passant_on(Square::D3)
|
|
.build()?
|
|
),
|
|
"Valid moves: {:?}",
|
|
formatted_move_list!(generated_moves, pos)
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn pinned_pieces_rook_cannot_move_out_of_pin() -> TestResult {
|
|
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(TestError::NoLegalMoves)?;
|
|
|
|
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::E3));
|
|
assert!(rook_moves.can_move_to_square(Square::E4));
|
|
assert!(rook_moves.can_move_to_square(Square::E5));
|
|
assert!(rook_moves.can_move_to_square(Square::E7));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn en_passant_discovered_check() -> TestResult {
|
|
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: HashSet<_> = pos.moves().iter().collect();
|
|
|
|
let unexpected_move = MoveBuilder::push(&piece!(Black Pawn on E4))
|
|
.capturing_en_passant_on(Square::D3)
|
|
.build()?;
|
|
|
|
assert!(
|
|
!generated_moves.contains(&unexpected_move),
|
|
"Valid moves: {:?}",
|
|
formatted_move_list!(generated_moves, pos)
|
|
);
|
|
|
|
Ok(())
|
|
}
|