[bitboard, board, core, moves] Implement SliderMoveGenerator
This generator produces moves for slider pieces: bishops, rooks, and queens. All of these pieces behave identically, though with different sets of rays that emanate from the origin square. Claude helped me significantly with the implementation and unit testing. All the unit tests that took advantage of Claude for implementation are marked as such with an _ai_claude suffix to the test name. One unique aspect of this move generator that Claude suggested to me was to use loop { } instead of a recursive call to next() when the internal iterators expire. I may try to port this to the other move generators in the future. To support this move generator, implement a Slider enum in core that represents one of the three slider pieces. Add Board::bishops(), Board::rooks() and Board::queens() to return BitBoards of those pieces. These are analogous to the pawns() and knights() methods that return their corresponding pieces. Also in the board create, replace the separate sight method implementations with a macro. These are all the same, but with a different sight method called under the hood. Finally, derive Clone and Debug for the bit_scanner types.
This commit is contained in:
parent
2c6a7828bc
commit
f005d94fc2
7 changed files with 676 additions and 1 deletions
|
@ -4,6 +4,7 @@ use chessfriend_core::Square;
|
|||
|
||||
macro_rules! bit_scanner {
|
||||
($name:ident) => {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct $name {
|
||||
bits: u64,
|
||||
shift: usize,
|
||||
|
@ -21,6 +22,7 @@ bit_scanner!(LeadingBitScanner);
|
|||
bit_scanner!(TrailingBitScanner);
|
||||
|
||||
fn index_to_square(index: usize) -> Square {
|
||||
debug_assert!(index < Square::NUM);
|
||||
unsafe {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
Square::from_index_unchecked(index as u8)
|
||||
|
|
|
@ -119,6 +119,18 @@ impl Board {
|
|||
pub fn knights(&self, color: Color) -> BitBoard {
|
||||
self.find_pieces(Piece::knight(color))
|
||||
}
|
||||
|
||||
pub fn bishops(&self, color: Color) -> BitBoard {
|
||||
self.find_pieces(Piece::bishop(color))
|
||||
}
|
||||
|
||||
pub fn rooks(&self, color: Color) -> BitBoard {
|
||||
self.find_pieces(Piece::rook(color))
|
||||
}
|
||||
|
||||
pub fn queens(&self, color: Color) -> BitBoard {
|
||||
self.find_pieces(Piece::queen(color))
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
|
|
|
@ -94,6 +94,28 @@ impl Sight for Piece {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! sight_method {
|
||||
($name:ident) => {
|
||||
pub fn $name(&self, square: Square, color: Option<Color>) -> BitBoard {
|
||||
let color = self.unwrap_color(color);
|
||||
|
||||
let info = SightInfo {
|
||||
square,
|
||||
occupancy: self.occupancy(),
|
||||
friendly_occupancy: self.friendly_occupancy(color),
|
||||
};
|
||||
|
||||
$name(&info)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Board {
|
||||
sight_method!(bishop_sight);
|
||||
sight_method!(rook_sight);
|
||||
sight_method!(queen_sight);
|
||||
}
|
||||
|
||||
struct SightInfo {
|
||||
square: Square,
|
||||
occupancy: BitBoard,
|
||||
|
|
|
@ -10,4 +10,4 @@ mod macros;
|
|||
pub use colors::Color;
|
||||
pub use coordinates::{Direction, File, Rank, Square, Wing};
|
||||
pub use pieces::{Piece, PlacedPiece};
|
||||
pub use shapes::Shape;
|
||||
pub use shapes::{Shape, Slider};
|
||||
|
|
|
@ -69,6 +69,36 @@ impl Shape {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Slider {
|
||||
Bishop,
|
||||
Rook,
|
||||
Queen,
|
||||
}
|
||||
|
||||
impl From<Slider> for Shape {
|
||||
fn from(value: Slider) -> Self {
|
||||
match value {
|
||||
Slider::Bishop => Shape::Bishop,
|
||||
Slider::Rook => Shape::Rook,
|
||||
Slider::Queen => Shape::Queen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Shape> for Slider {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Shape) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Shape::Bishop => Ok(Slider::Bishop),
|
||||
Shape::Rook => Ok(Slider::Rook),
|
||||
Shape::Queen => Ok(Slider::Queen),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
||||
#[error("no matching piece shape for character '{0:?}'")]
|
||||
pub struct ShapeFromCharError(char);
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
mod knight;
|
||||
mod pawn;
|
||||
mod slider;
|
||||
|
||||
#[cfg(test)]
|
||||
mod testing;
|
||||
|
||||
pub use knight::KnightMoveGenerator;
|
||||
pub use pawn::PawnMoveGenerator;
|
||||
pub use slider::{BishopMoveGenerator, QueenMoveGenerator, RookMoveGenerator};
|
||||
|
||||
use crate::Move;
|
||||
|
||||
|
|
607
moves/src/generators/slider.rs
Normal file
607
moves/src/generators/slider.rs
Normal file
|
@ -0,0 +1,607 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
//! Sliders in chess are the pieces that move (a.k.a. "slide") along straight
|
||||
//! line paths. Bishops, Rooks, and Queens all do this. All of these pieces
|
||||
//! function identically, though with different sets of rays emanating outward
|
||||
//! from their origin squares: rooks along orthogonal lines, bishops along
|
||||
//! diagonals, and queens along both orthogonal and diagonal lines.
|
||||
//!
|
||||
//! This module implements the [`SliderMoveGenerator`] which iterates all the
|
||||
//! slider moves from a given square. This module also exports
|
||||
//! [`BishopMoveGenerator`], [`RookMoveGenerator`], and [`QueenMoveGenerator`]
|
||||
//! that emit moves for their corresponding pieces.
|
||||
|
||||
use super::GeneratedMove;
|
||||
use crate::Move;
|
||||
use chessfriend_bitboard::{bit_scanner::TrailingBitScanner, BitBoard};
|
||||
use chessfriend_board::Board;
|
||||
use chessfriend_core::{Color, Slider, Square};
|
||||
|
||||
macro_rules! slider_move_generator {
|
||||
($vis:vis $name:ident, $slider:ident) => {
|
||||
#[must_use]
|
||||
$vis struct $name(SliderMoveGenerator);
|
||||
|
||||
impl $name {
|
||||
pub fn new(board: &Board, color: Option<Color>) -> Self {
|
||||
Self(SliderMoveGenerator::new(board, Slider::$slider, color))
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for $name {
|
||||
type Item = $crate::GeneratedMove;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
slider_move_generator!(pub BishopMoveGenerator, Bishop);
|
||||
slider_move_generator!(pub RookMoveGenerator, Rook);
|
||||
slider_move_generator!(pub QueenMoveGenerator, Queen);
|
||||
|
||||
#[must_use]
|
||||
struct SliderMoveGenerator {
|
||||
sliders: Vec<SliderInfo>,
|
||||
next_sliders_index: usize,
|
||||
current_slider: Option<SliderInfo>,
|
||||
enemies: BitBoard,
|
||||
friends: BitBoard,
|
||||
}
|
||||
|
||||
impl SliderMoveGenerator {
|
||||
fn new(board: &Board, slider: Slider, color: Option<Color>) -> Self {
|
||||
let color = board.unwrap_color(color);
|
||||
|
||||
let pieces = match slider {
|
||||
Slider::Bishop => board.bishops(color),
|
||||
Slider::Rook => board.rooks(color),
|
||||
Slider::Queen => board.queens(color),
|
||||
};
|
||||
|
||||
let enemies = board.enemies(color);
|
||||
let friends = board.friendly_occupancy(color);
|
||||
|
||||
let sliders = pieces
|
||||
.occupied_squares_trailing()
|
||||
.map(|origin| SliderInfo::new(board, origin, slider, color))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
sliders,
|
||||
next_sliders_index: 0,
|
||||
current_slider: None,
|
||||
enemies,
|
||||
friends,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SliderMoveGenerator {
|
||||
type Item = GeneratedMove;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if self.current_slider.is_none() {
|
||||
if self.next_sliders_index < self.sliders.len() {
|
||||
self.current_slider = Some(self.sliders[self.next_sliders_index].clone());
|
||||
self.next_sliders_index += 1;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(current_slider) = self.current_slider.as_mut() {
|
||||
if let Some(target) = current_slider.next() {
|
||||
let target_bitboard: BitBoard = target.into();
|
||||
|
||||
let is_targeting_friendly_piece =
|
||||
(target_bitboard & self.friends).is_populated();
|
||||
if is_targeting_friendly_piece {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_targeting_enemy_piece = (target_bitboard & self.enemies).is_populated();
|
||||
return Some(if is_targeting_enemy_piece {
|
||||
Move::capture(current_slider.origin, target).into()
|
||||
} else {
|
||||
Move::quiet(current_slider.origin, target).into()
|
||||
});
|
||||
}
|
||||
|
||||
self.current_slider = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SliderInfo {
|
||||
origin: Square,
|
||||
iterator: TrailingBitScanner,
|
||||
}
|
||||
|
||||
impl SliderInfo {
|
||||
fn new(board: &Board, origin: Square, slider: Slider, color: Color) -> Self {
|
||||
let color = Some(color);
|
||||
|
||||
let sight = match slider {
|
||||
Slider::Bishop => board.bishop_sight(origin, color),
|
||||
Slider::Rook => board.rook_sight(origin, color),
|
||||
Slider::Queen => board.queen_sight(origin, color),
|
||||
};
|
||||
|
||||
Self {
|
||||
origin,
|
||||
iterator: sight.occupied_squares_trailing(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SliderInfo {
|
||||
type Item = Square;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iterator.next()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_move_list, ply};
|
||||
use chessfriend_board::test_board;
|
||||
|
||||
#[test]
|
||||
fn white_b5_rook() {
|
||||
let board = test_board!(White Rook on B5);
|
||||
assert_move_list!(
|
||||
RookMoveGenerator::new(&board, None),
|
||||
[
|
||||
ply!(B5 - A5),
|
||||
ply!(B5 - C5),
|
||||
ply!(B5 - D5),
|
||||
ply!(B5 - E5),
|
||||
ply!(B5 - F5),
|
||||
ply!(B5 - G5),
|
||||
ply!(B5 - H5),
|
||||
ply!(B5 - B8),
|
||||
ply!(B5 - B7),
|
||||
ply!(B5 - B6),
|
||||
ply!(B5 - B4),
|
||||
ply!(B5 - B3),
|
||||
ply!(B5 - B2),
|
||||
ply!(B5 - B1),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn black_f4_bishop() {
|
||||
let board = test_board!(White Bishop on F4);
|
||||
assert_move_list!(
|
||||
BishopMoveGenerator::new(&board, None),
|
||||
[
|
||||
ply!(F4 - B8),
|
||||
ply!(F4 - C7),
|
||||
ply!(F4 - D6),
|
||||
ply!(F4 - E5),
|
||||
ply!(F4 - H6),
|
||||
ply!(F4 - G5),
|
||||
ply!(F4 - C1),
|
||||
ply!(F4 - D2),
|
||||
ply!(F4 - E3),
|
||||
ply!(F4 - H2),
|
||||
ply!(F4 - G3),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_d4_queen_ai_claude() {
|
||||
let board = test_board!(White Queen on D4);
|
||||
assert_move_list!(
|
||||
QueenMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves (rook-like)
|
||||
ply!(D4 - A4),
|
||||
ply!(D4 - B4),
|
||||
ply!(D4 - C4),
|
||||
ply!(D4 - E4),
|
||||
ply!(D4 - F4),
|
||||
ply!(D4 - G4),
|
||||
ply!(D4 - H4),
|
||||
// Vertical moves (rook-like)
|
||||
ply!(D4 - D1),
|
||||
ply!(D4 - D2),
|
||||
ply!(D4 - D3),
|
||||
ply!(D4 - D5),
|
||||
ply!(D4 - D6),
|
||||
ply!(D4 - D7),
|
||||
ply!(D4 - D8),
|
||||
// Diagonal moves (bishop-like)
|
||||
ply!(D4 - A1),
|
||||
ply!(D4 - B2),
|
||||
ply!(D4 - C3),
|
||||
ply!(D4 - E5),
|
||||
ply!(D4 - F6),
|
||||
ply!(D4 - G7),
|
||||
ply!(D4 - H8),
|
||||
ply!(D4 - A7),
|
||||
ply!(D4 - B6),
|
||||
ply!(D4 - C5),
|
||||
ply!(D4 - E3),
|
||||
ply!(D4 - F2),
|
||||
ply!(D4 - G1),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_f3_bishop_with_capture_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Bishop on F3,
|
||||
Black Pawn on C6
|
||||
);
|
||||
assert_move_list!(
|
||||
BishopMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Diagonal towards C6 (stops at capture)
|
||||
ply!(F3 - E4),
|
||||
ply!(F3 - D5),
|
||||
ply!(F3 x C6),
|
||||
// Diagonal towards H1
|
||||
ply!(F3 - G2),
|
||||
ply!(F3 - H1),
|
||||
// Diagonal towards A8
|
||||
ply!(F3 - G4),
|
||||
ply!(F3 - H5),
|
||||
// Diagonal towards E2
|
||||
ply!(F3 - E2),
|
||||
ply!(F3 - D1),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_e6_rook_with_capture_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Rook on E6,
|
||||
Black Knight on E3
|
||||
);
|
||||
assert_move_list!(
|
||||
RookMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves
|
||||
ply!(E6 - A6),
|
||||
ply!(E6 - B6),
|
||||
ply!(E6 - C6),
|
||||
ply!(E6 - D6),
|
||||
ply!(E6 - F6),
|
||||
ply!(E6 - G6),
|
||||
ply!(E6 - H6),
|
||||
// Vertical moves up
|
||||
ply!(E6 - E7),
|
||||
ply!(E6 - E8),
|
||||
// Vertical moves down (stops at capture)
|
||||
ply!(E6 - E5),
|
||||
ply!(E6 - E4),
|
||||
ply!(E6 x E3),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_c4_queen_with_capture_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Queen on C4,
|
||||
Black Bishop on F7
|
||||
);
|
||||
assert_move_list!(
|
||||
QueenMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves
|
||||
ply!(C4 - A4),
|
||||
ply!(C4 - B4),
|
||||
ply!(C4 - D4),
|
||||
ply!(C4 - E4),
|
||||
ply!(C4 - F4),
|
||||
ply!(C4 - G4),
|
||||
ply!(C4 - H4),
|
||||
// Vertical moves
|
||||
ply!(C4 - C1),
|
||||
ply!(C4 - C2),
|
||||
ply!(C4 - C3),
|
||||
ply!(C4 - C5),
|
||||
ply!(C4 - C6),
|
||||
ply!(C4 - C7),
|
||||
ply!(C4 - C8),
|
||||
// Diagonal moves (A2-G8 diagonal)
|
||||
ply!(C4 - A2),
|
||||
ply!(C4 - B3),
|
||||
ply!(C4 - D5),
|
||||
ply!(C4 - E6),
|
||||
ply!(C4 x F7),
|
||||
// Diagonal moves (F1-A6 diagonal)
|
||||
ply!(C4 - B5),
|
||||
ply!(C4 - A6),
|
||||
ply!(C4 - D3),
|
||||
ply!(C4 - E2),
|
||||
ply!(C4 - F1),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_rook_blocked_by_friendly_piece_ai_claude() {
|
||||
let board = test_board!(White Rook on D4, White Pawn on D6);
|
||||
assert_move_list!(
|
||||
RookMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves (unblocked)
|
||||
ply!(D4 - A4),
|
||||
ply!(D4 - B4),
|
||||
ply!(D4 - C4),
|
||||
ply!(D4 - E4),
|
||||
ply!(D4 - F4),
|
||||
ply!(D4 - G4),
|
||||
ply!(D4 - H4),
|
||||
// Vertical moves down (unblocked)
|
||||
ply!(D4 - D1),
|
||||
ply!(D4 - D2),
|
||||
ply!(D4 - D3),
|
||||
// Vertical moves up (blocked by friendly pawn on D6)
|
||||
ply!(D4 - D5),
|
||||
// Cannot move to D6 (occupied by friendly piece)
|
||||
// Cannot move to D7 or D8 (blocked by friendly piece on D6)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_bishop_blocked_by_friendly_pieces_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Bishop on E4,
|
||||
White Knight on C2, // Blocks one diagonal
|
||||
White Pawn on G6 // Blocks another diagonal
|
||||
);
|
||||
assert_move_list!(
|
||||
BishopMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Diagonal towards H1 (unblocked)
|
||||
ply!(E4 - F3),
|
||||
ply!(E4 - G2),
|
||||
ply!(E4 - H1),
|
||||
// Diagonal towards A8 (blocked by pawn on G6)
|
||||
ply!(E4 - F5),
|
||||
// Cannot move to G6 (friendly pawn)
|
||||
// Cannot move to H7 (blocked by friendly pawn)
|
||||
// Diagonal towards H7 is blocked at G6
|
||||
// Diagonal towards A8 (unblocked on the other side)
|
||||
ply!(E4 - D5),
|
||||
ply!(E4 - C6),
|
||||
ply!(E4 - B7),
|
||||
ply!(E4 - A8),
|
||||
// Diagonal towards D3 (blocked by knight on C2)
|
||||
ply!(E4 - D3),
|
||||
// Cannot move to C2 (friendly knight)
|
||||
// Cannot move to B1 (blocked by friendly knight)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_queen_multiple_friendly_blocks_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Queen on D4,
|
||||
White Pawn on D6, // Blocks vertical up
|
||||
White Bishop on F4, // Blocks horizontal right
|
||||
White Knight on F6 // Blocks diagonal
|
||||
);
|
||||
assert_move_list!(
|
||||
QueenMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves left (unblocked)
|
||||
ply!(D4 - A4),
|
||||
ply!(D4 - B4),
|
||||
ply!(D4 - C4),
|
||||
// Horizontal moves right (blocked by bishop on F4)
|
||||
ply!(D4 - E4),
|
||||
// Cannot move to F4 (friendly bishop)
|
||||
// Cannot move to G4, H4 (blocked by friendly bishop)
|
||||
|
||||
// Vertical moves down (unblocked)
|
||||
ply!(D4 - D1),
|
||||
ply!(D4 - D2),
|
||||
ply!(D4 - D3),
|
||||
// Vertical moves up (blocked by pawn on D6)
|
||||
ply!(D4 - D5),
|
||||
// Cannot move to D6 (friendly pawn)
|
||||
// Cannot move to D7, D8 (blocked by friendly pawn)
|
||||
|
||||
// Diagonal moves (some blocked, some not)
|
||||
// Towards A1
|
||||
ply!(D4 - C3),
|
||||
ply!(D4 - B2),
|
||||
ply!(D4 - A1),
|
||||
// Towards G1
|
||||
ply!(D4 - E3),
|
||||
ply!(D4 - F2),
|
||||
ply!(D4 - G1),
|
||||
// Towards A7
|
||||
ply!(D4 - C5),
|
||||
ply!(D4 - B6),
|
||||
ply!(D4 - A7),
|
||||
// Towards G7 (blocked by knight on F6)
|
||||
ply!(D4 - E5),
|
||||
// Cannot move to F6 (friendly knight)
|
||||
// Cannot move to G7, H8 (blocked by friendly knight)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rook_ray_stops_at_first_piece_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Rook on A1,
|
||||
Black Pawn on A4, // First obstruction
|
||||
Black Queen on A7 // Should be unreachable
|
||||
);
|
||||
assert_move_list!(
|
||||
RookMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves (unblocked)
|
||||
ply!(A1 - B1),
|
||||
ply!(A1 - C1),
|
||||
ply!(A1 - D1),
|
||||
ply!(A1 - E1),
|
||||
ply!(A1 - F1),
|
||||
ply!(A1 - G1),
|
||||
ply!(A1 - H1),
|
||||
// Vertical moves up (terminated at A4)
|
||||
ply!(A1 - A2),
|
||||
ply!(A1 - A3),
|
||||
ply!(A1 x A4),
|
||||
// Cannot reach A5, A6, A7, A8 due to pawn blocking at A4
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bishop_ray_terminated_by_enemy_piece() {
|
||||
let board = test_board!(
|
||||
White Bishop on C1,
|
||||
Black Knight on F4, // Terminates one diagonal
|
||||
Black Rook on H6, // Should be unreachable behind the knight
|
||||
);
|
||||
assert_move_list!(
|
||||
BishopMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Diagonal towards A3
|
||||
ply!(C1 - B2),
|
||||
ply!(C1 - A3),
|
||||
// Diagonal towards H6 (terminated at F4)
|
||||
ply!(C1 - D2),
|
||||
ply!(C1 - E3),
|
||||
ply!(C1 x F4),
|
||||
// Cannot reach G5, H6 due to knight blocking at F4
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queen_multiple_ray_terminations_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Queen on D4,
|
||||
Black Pawn on D7, // Terminates vertical ray
|
||||
Black Bishop on A7, // Capturable along diagonal
|
||||
Black Knight on G4, // Terminates horizontal ray
|
||||
Black Rook on H4, // Capturable along horizontal ray
|
||||
White Pawn on F6, // Terminates diagonal ray (friendly)
|
||||
Black Queen on H8,
|
||||
);
|
||||
assert_move_list!(
|
||||
QueenMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves left (unblocked)
|
||||
ply!(D4 - A4),
|
||||
ply!(D4 - B4),
|
||||
ply!(D4 - C4),
|
||||
// Horizontal moves right (terminated at G4)
|
||||
ply!(D4 - E4),
|
||||
ply!(D4 - F4),
|
||||
ply!(D4 x G4),
|
||||
// Cannot reach H4 due to knight blocking
|
||||
// Vertical moves down (unblocked)
|
||||
ply!(D4 - D1),
|
||||
ply!(D4 - D2),
|
||||
ply!(D4 - D3),
|
||||
// Vertical moves up (terminated at D7)
|
||||
ply!(D4 - D5),
|
||||
ply!(D4 - D6),
|
||||
ply!(D4 x D7),
|
||||
// Cannot reach D8 due to pawn blocking
|
||||
// Diagonal moves towards A1
|
||||
ply!(D4 - C3),
|
||||
ply!(D4 - B2),
|
||||
ply!(D4 - A1),
|
||||
// Diagonal moves towards G1
|
||||
ply!(D4 - E3),
|
||||
ply!(D4 - F2),
|
||||
ply!(D4 - G1),
|
||||
// Diagonal moves towards A7
|
||||
ply!(D4 - C5),
|
||||
ply!(D4 - B6),
|
||||
ply!(D4 x A7),
|
||||
// Diagonal moves towards H8 (terminated at F6 by friendly pawn)
|
||||
ply!(D4 - E5),
|
||||
// Cannot move to F6 (friendly pawn)
|
||||
// Cannot reach G7, H8 due to friendly pawn blocking
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rook_chain_of_pieces_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Rook on A1,
|
||||
Black Pawn on A3, // First enemy piece
|
||||
White Knight on A5, // Friendly piece behind enemy
|
||||
Black Queen on A7 // Enemy piece behind friendly
|
||||
);
|
||||
assert_move_list!(
|
||||
RookMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Horizontal moves (unblocked)
|
||||
ply!(A1 - B1),
|
||||
ply!(A1 - C1),
|
||||
ply!(A1 - D1),
|
||||
ply!(A1 - E1),
|
||||
ply!(A1 - F1),
|
||||
ply!(A1 - G1),
|
||||
ply!(A1 - H1),
|
||||
// Vertical moves (ray terminates at first piece)
|
||||
ply!(A1 - A2),
|
||||
ply!(A1 x A3),
|
||||
// Cannot reach A4, A5, A6, A7, A8 - ray terminated at A3
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bishop_ray_termination_all_directions_ai_claude() {
|
||||
let board = test_board!(
|
||||
White Bishop on D4,
|
||||
Black Pawn on B2, // Terminates towards A1
|
||||
Black Knight on F6, // Terminates towards H8
|
||||
Black Rook on B6, // Terminates towards A7
|
||||
Black Queen on F2 // Terminates towards G1
|
||||
);
|
||||
assert_move_list!(
|
||||
BishopMoveGenerator::new(&board, None),
|
||||
[
|
||||
// Diagonal towards A1 (terminated at B2)
|
||||
ply!(D4 - C3),
|
||||
ply!(D4 x B2), // Capture
|
||||
// Cannot reach A1
|
||||
|
||||
// Diagonal towards H8 (terminated at F6)
|
||||
ply!(D4 - E5),
|
||||
ply!(D4 x F6), // Capture
|
||||
// Cannot reach G7, H8
|
||||
|
||||
// Diagonal towards A7 (terminated at B6)
|
||||
ply!(D4 - C5),
|
||||
ply!(D4 x B6), // Capture
|
||||
// Cannot reach A7
|
||||
|
||||
// Diagonal towards G1 (terminated at F2)
|
||||
ply!(D4 - E3),
|
||||
ply!(D4 x F2),
|
||||
// Cannot reach G1
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue