[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