use crate::{
constants::{FILE_A, FILE_H},
model::{bitboard::BitBoard, gameboard::GameBoard, piecemove::PieceMove},
};
pub const MAX_QUEEN_MOVES: usize = 56;
pub(crate) fn generate_queen_moves(state: &GameBoard) -> ([PieceMove; MAX_QUEEN_MOVES], usize) {
let mut moves = [PieceMove::NULL; MAX_QUEEN_MOVES];
let mut count = 0;
let all_occupied =
state.pawns | state.knights | state.bishops | state.rooks | state.queens | state.kings;
let (my_queens, other_pieces): (BitBoard, u64) = if state.playing {
(
state.queens & state.colour,
(all_occupied & !state.colour).into(),
)
} else {
(
state.queens & !state.colour,
(all_occupied & state.colour).into(),
)
};
let queen_directions: [(i8, u64, bool); 8] = [
(8, 0, true), (1, FILE_A, true), (-8, 0, false), (-1, FILE_H, false), (7, FILE_H, true), (9, FILE_A, true), (-9, FILE_H, false), (-7, FILE_A, false), ];
for (shift, mask, is_positive) in queen_directions {
let mut ray_attackers: u64 = my_queens.into();
for i in 1..8 {
if is_positive {
ray_attackers <<= shift as u8;
} else {
ray_attackers >>= (-shift) as u8;
}
ray_attackers &= !mask;
let mut captures = ray_attackers & other_pieces;
while captures != 0 {
let to_board = captures.trailing_zeros() as u8;
let from_board = if is_positive {
to_board - (i * (shift as u8))
} else {
to_board + (i * ((-shift) as u8))
};
if count < MAX_QUEEN_MOVES {
moves[count] = PieceMove::new(from_board, to_board, true, None);
count += 1;
}
captures &= captures - 1;
}
let blockers = ray_attackers & all_occupied.raw();
ray_attackers &= !blockers;
let mut quiet_moves = ray_attackers;
while quiet_moves != 0 {
let to_board = quiet_moves.trailing_zeros() as u8;
let from_board = if is_positive {
to_board - (i * (shift as u8))
} else {
to_board + (i * ((-shift) as u8))
};
if count < MAX_QUEEN_MOVES {
moves[count] = PieceMove::new(from_board, to_board, false, None);
count += 1;
}
quiet_moves &= quiet_moves - 1;
}
if ray_attackers == 0 {
break;
}
}
}
(moves, count)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::*;
use crate::model::gamedata::GameData;
use crate::model::piecemove::PieceMove;
fn sort_and_compare_moves(mut moves: Vec<PieceMove>) -> Vec<PieceMove> {
moves.sort();
moves
}
#[test]
fn test_single_white_queen_center() {
let board = GameData::from_fen("8/8/8/8/3Q4/8/8/8 w - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let expected_moves = vec![
PieceMove::new(D4, D5, false, None),
PieceMove::new(D4, D6, false, None),
PieceMove::new(D4, D7, false, None),
PieceMove::new(D4, D8, false, None),
PieceMove::new(D4, E4, false, None),
PieceMove::new(D4, F4, false, None),
PieceMove::new(D4, G4, false, None),
PieceMove::new(D4, H4, false, None),
PieceMove::new(D4, D3, false, None),
PieceMove::new(D4, D2, false, None),
PieceMove::new(D4, D1, false, None),
PieceMove::new(D4, C4, false, None),
PieceMove::new(D4, B4, false, None),
PieceMove::new(D4, A4, false, None),
PieceMove::new(D4, C5, false, None),
PieceMove::new(D4, B6, false, None),
PieceMove::new(D4, A7, false, None),
PieceMove::new(D4, E5, false, None),
PieceMove::new(D4, F6, false, None),
PieceMove::new(D4, G7, false, None),
PieceMove::new(D4, H8, false, None),
PieceMove::new(D4, C3, false, None),
PieceMove::new(D4, B2, false, None),
PieceMove::new(D4, A1, false, None),
PieceMove::new(D4, E3, false, None),
PieceMove::new(D4, F2, false, None),
PieceMove::new(D4, G1, false, None),
];
assert_eq!(count, 27); assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_single_black_queen_center() {
let board = GameData::from_fen("8/8/8/8/3q4/8/8/8 b - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let expected_moves = vec![
PieceMove::new(D4, D5, false, None),
PieceMove::new(D4, D6, false, None),
PieceMove::new(D4, D7, false, None),
PieceMove::new(D4, D8, false, None),
PieceMove::new(D4, E4, false, None),
PieceMove::new(D4, F4, false, None),
PieceMove::new(D4, G4, false, None),
PieceMove::new(D4, H4, false, None),
PieceMove::new(D4, D3, false, None),
PieceMove::new(D4, D2, false, None),
PieceMove::new(D4, D1, false, None),
PieceMove::new(D4, C4, false, None),
PieceMove::new(D4, B4, false, None),
PieceMove::new(D4, A4, false, None),
PieceMove::new(D4, C5, false, None),
PieceMove::new(D4, B6, false, None),
PieceMove::new(D4, A7, false, None),
PieceMove::new(D4, E5, false, None),
PieceMove::new(D4, F6, false, None),
PieceMove::new(D4, G7, false, None),
PieceMove::new(D4, H8, false, None),
PieceMove::new(D4, C3, false, None),
PieceMove::new(D4, B2, false, None),
PieceMove::new(D4, A1, false, None),
PieceMove::new(D4, E3, false, None),
PieceMove::new(D4, F2, false, None),
PieceMove::new(D4, G1, false, None),
];
assert_eq!(count, 27);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_white_queen_corner_a1() {
let board = GameData::from_fen("8/8/8/8/8/8/8/Q7 w - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let expected_moves = vec![
PieceMove::new(A1, A2, false, None),
PieceMove::new(A1, A3, false, None),
PieceMove::new(A1, A4, false, None),
PieceMove::new(A1, A5, false, None),
PieceMove::new(A1, A6, false, None),
PieceMove::new(A1, A7, false, None),
PieceMove::new(A1, A8, false, None),
PieceMove::new(A1, B1, false, None),
PieceMove::new(A1, C1, false, None),
PieceMove::new(A1, D1, false, None),
PieceMove::new(A1, E1, false, None),
PieceMove::new(A1, F1, false, None),
PieceMove::new(A1, G1, false, None),
PieceMove::new(A1, H1, false, None),
PieceMove::new(A1, B2, false, None),
PieceMove::new(A1, C3, false, None),
PieceMove::new(A1, D4, false, None),
PieceMove::new(A1, E5, false, None),
PieceMove::new(A1, F6, false, None),
PieceMove::new(A1, G7, false, None),
PieceMove::new(A1, H8, false, None),
];
assert_eq!(count, 21); assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_white_queen_captures() {
let board = GameData::from_fen("8/3p1p1p/8/1p1Q1p2/8/1p1p1p2/3p1p1p/8 w - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let expected_moves = vec![
PieceMove::new(D5, A8, false, None),
PieceMove::new(D5, B7, false, None),
PieceMove::new(D5, C6, false, None),
PieceMove::new(D5, D7, true, None), PieceMove::new(D5, D6, false, None),
PieceMove::new(D5, F7, true, None), PieceMove::new(D5, E6, false, None),
PieceMove::new(D5, B5, true, None), PieceMove::new(D5, C5, false, None),
PieceMove::new(D5, F5, true, None), PieceMove::new(D5, E5, false, None),
PieceMove::new(D5, B3, true, None), PieceMove::new(D5, C4, false, None),
PieceMove::new(D5, D3, true, None), PieceMove::new(D5, D4, false, None),
PieceMove::new(D5, F3, true, None), PieceMove::new(D5, E4, false, None),
];
assert!(count > 0);
let captures_count = generated_moves.iter().filter(|m| m.is_capture()).count();
assert!(captures_count > 0);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_queen_blocked_by_own_pieces() {
let board = GameData::from_fen("8/8/8/2PPP3/2PQP3/2PPP3/8/8 w - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let expected_moves = vec![];
assert_eq!(count, 0);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_queen_partially_blocked() {
let board = GameData::from_fen("8/8/8/8/3Q4/8/1P6/8 w - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let captures_and_quiet = generated_moves.len();
assert!(captures_and_quiet > 20);
let has_d8_move = generated_moves.iter().any(|m| m.to_square() == D8);
let has_h4_move = generated_moves.iter().any(|m| m.to_square() == H4);
let has_h8_move = generated_moves.iter().any(|m| m.to_square() == H8);
assert!(has_d8_move, "Should be able to move to D8");
assert!(has_h4_move, "Should be able to move to H4");
assert!(has_h8_move, "Should be able to move to H8");
}
#[test]
fn test_no_queens() {
let board = GameData::from_fen("8/8/8/8/8/8/8/8 w - - 0 1").unwrap();
let (_moves, count) = generate_queen_moves(&board.board);
assert_eq!(count, 0);
}
#[test]
fn test_queen_initial_position() {
let board =
GameData::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
let (_moves, count) = generate_queen_moves(&board.board);
assert_eq!(count, 0);
}
#[test]
fn test_queen_after_pawn_moves() {
let board =
GameData::from_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let expected_moves = vec![
PieceMove::new(D1, E2, false, None),
PieceMove::new(D1, F3, false, None),
PieceMove::new(D1, G4, false, None),
PieceMove::new(D1, H5, false, None),
];
assert_eq!(count, 4);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_multiple_queens() {
let board = GameData::from_fen("8/8/8/8/3Q4/8/8/Q7 w - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
assert!(count > 27);
let d4_moves = generated_moves
.iter()
.filter(|m| m.from_square() == D4)
.count();
let a1_moves = generated_moves
.iter()
.filter(|m| m.from_square() == A1)
.count();
assert!(d4_moves > 0, "D4 queen should have moves");
assert!(a1_moves > 0, "A1 queen should have moves");
}
#[test]
fn test_complex_queen_position() {
let board =
GameData::from_fen("r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1")
.unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let mut found_queen_moves = false;
for move_item in &generated_moves {
if move_item.from_square() == D1 {
found_queen_moves = true;
break;
}
}
assert!(
found_queen_moves || count == 0,
"Should find moves for queen on D1 or no moves if blocked"
);
}
#[test]
fn test_queen_x_ray_attacks() {
let board = GameData::from_fen("8/7p/2ppp3/2pQp3/2ppp3/8/7p/8 w - - 0 1").unwrap();
let (moves, count) = generate_queen_moves(&board.board);
let generated_moves: Vec<PieceMove> = moves[..count].to_vec();
let captures_count = generated_moves.iter().filter(|m| m.is_capture()).count();
assert!(captures_count > 0, "Should have some captures");
let has_invalid_move = generated_moves.iter().any(|m| {
(m.from_square() == D5 && m.to_square() == A8) || (m.from_square() == D5 && m.to_square() == H1) });
assert!(
!has_invalid_move,
"Queen should not move beyond captured pieces"
);
}
}