use crate::{
constants::{FILE_A, FILE_B, FILE_G, FILE_H}, model::{bitboard::BitBoard, gameboard::GameBoard, piecemove::PieceMove},
movegen::add_move_to_list,
};
pub const MAX_KNIGHT_MOVES: usize = 16;
pub(crate) fn generate_knight_moves(state: &GameBoard) -> ([PieceMove; MAX_KNIGHT_MOVES], usize) {
let mut moves = [PieceMove::NULL; MAX_KNIGHT_MOVES];
let mut count = 0;
let all_occupied =
state.pawns | state.knights | state.bishops | state.rooks | state.queens | state.kings;
let (my_knights, other_pieces): (BitBoard, u64) = if state.playing {
(
state.knights & state.colour,
(all_occupied & !state.colour).into(),
)
} else {
(
state.knights & !state.colour,
(all_occupied & state.colour).into(),
)
};
let knight_moves_data: [(i8, u64); 8] = [
(-17, FILE_A), (-15, FILE_H), (-10, FILE_A | FILE_B), (-6, FILE_G | FILE_H), (6, FILE_A | FILE_B), (10, FILE_G | FILE_H), (15, FILE_A), (17, FILE_H), ];
for (dir, mask) in knight_moves_data {
let new_pos = if dir > 0 {
(my_knights & !mask) << (dir as u8)
} else {
(my_knights & !mask) >> ((-dir) as u8)
};
let blockers = new_pos & all_occupied.raw();
let mut attackers = blockers.raw() & other_pieces;
while attackers != 0 {
let to_board = attackers.trailing_zeros() as u8;
let from_board = if dir > 0 {
to_board - (dir as u8) } else {
to_board + ((-dir) as u8) };
add_move_to_list(
&mut moves,
&mut count,
MAX_KNIGHT_MOVES,
PieceMove::new(from_board, to_board, true, None),
);
attackers &= !(1 << to_board);
}
let mut quiet_moves = new_pos.raw() & !all_occupied.raw();
while quiet_moves != 0 {
let to_board = quiet_moves.trailing_zeros() as u8;
let from_board = if dir > 0 {
to_board - (dir as u8) } else {
to_board + ((-dir) as u8) };
add_move_to_list(
&mut moves,
&mut count,
MAX_KNIGHT_MOVES,
PieceMove::new(from_board, to_board, false, None),
);
quiet_moves &= !(1 << to_board);
}
}
(moves, count)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
constants::*,
model::{
gameboard::{GameBoard, PieceType},
piecemove::PieceMove,
},
};
fn board_from_fen(fen: &str) -> GameBoard {
let gamedata = crate::model::gamedata::GameData::from_fen(fen)
.unwrap_or_else(|e| panic!("Failed to parse FEN: {e:?}"));
gamedata.board
}
fn sort_and_compare_moves(mut moves: Vec<PieceMove>) -> Vec<PieceMove> {
moves.sort();
moves
}
fn moves_to_vec(moves: &[PieceMove; MAX_KNIGHT_MOVES], count: usize) -> Vec<PieceMove> {
moves[0..count].to_vec()
}
#[test]
fn test_generate_knight_moves_empty_board() {
let board = GameBoard::new(); let (moves, count) = generate_knight_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 0);
assert!(generated_moves.is_empty());
}
#[test]
fn test_generate_knight_moves_white_knight_d4() {
let mut board = GameBoard::new();
board.set_square(D4, PieceType::Knight, true); board.playing = true;
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(D4, B3, false, None), PieceMove::new(D4, C2, false, None), PieceMove::new(D4, E2, false, None), PieceMove::new(D4, F3, false, None), PieceMove::new(D4, B5, false, None), PieceMove::new(D4, C6, false, None), PieceMove::new(D4, E6, false, None), PieceMove::new(D4, F5, false, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 8);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_white_knight_a1() {
let mut board = GameBoard::new();
board.set_square(A1, PieceType::Knight, true); board.playing = true;
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(A1, B3, false, None), PieceMove::new(A1, C2, false, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 2);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_black_knight_h8() {
let mut board = GameBoard::new();
board.set_square(H8, PieceType::Knight, false); board.playing = false;
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(H8, G6, false, None), PieceMove::new(H8, F7, false, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 2);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_white_knight_blocked_by_friendly() {
let board = board_from_fen("8/8/2P1P3/1P3P2/3N4/1P3P2/2P1P3/8 w - - 0 1"); let (moves, count) = generate_knight_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 0); assert!(generated_moves.is_empty());
}
#[test]
fn test_generate_knight_moves_white_knight_captures() {
let board = board_from_fen("8/8/2p1p3/3P1p2/2PNP3/8/8/8 w - - 0 1"); let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(D4, E2, false, None), PieceMove::new(D4, C2, false, None), PieceMove::new(D4, F3, false, None), PieceMove::new(D4, B3, false, None), PieceMove::new(D4, F5, true, None), PieceMove::new(D4, B5, false, None), PieceMove::new(D4, E6, true, None), PieceMove::new(D4, C6, true, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 8);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_black_knight_captures() {
let board = board_from_fen("8/8/2P1P3/3P1P2/2pnP3/8/8/8 b - - 0 1"); let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(D4, E2, false, None), PieceMove::new(D4, C2, false, None), PieceMove::new(D4, F3, false, None), PieceMove::new(D4, B3, false, None), PieceMove::new(D4, F5, true, None), PieceMove::new(D4, B5, false, None), PieceMove::new(D4, E6, true, None), PieceMove::new(D4, C6, true, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 8);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_initial_position_white() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(B1, A3, false, None),
PieceMove::new(B1, C3, false, None),
PieceMove::new(G1, F3, false, None),
PieceMove::new(G1, H3, false, None),
];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 4);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_initial_position_black() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1");
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(B8, A6, false, None),
PieceMove::new(B8, C6, false, None),
PieceMove::new(G8, F6, false, None),
PieceMove::new(G8, H6, false, None),
];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_complex_scenario() {
let board = board_from_fen("2n5/8/8/4N3/8/8/8/8 w - - 0 1");
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(E5, C4, false, None),
PieceMove::new(E5, C6, false, None),
PieceMove::new(E5, D3, false, None),
PieceMove::new(E5, D7, false, None),
PieceMove::new(E5, F3, false, None),
PieceMove::new(E5, F7, false, None),
PieceMove::new(E5, G4, false, None),
PieceMove::new(E5, G6, false, None),
];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 8);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_complex_scenario_black_to_move() {
let board = board_from_fen("2n5/8/8/4N3/8/8/8/8 b - - 0 1");
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(C8, A7, false, None),
PieceMove::new(C8, B6, false, None),
PieceMove::new(C8, D6, false, None),
PieceMove::new(C8, E7, false, None),
];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 4);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_capture_black_knight_c8_captures_e5() {
let board = board_from_fen("2n5/8/8/4N3/8/8/8/8 b - - 0 1");
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(C8, A7, false, None),
PieceMove::new(C8, B6, false, None),
PieceMove::new(C8, D6, false, None),
PieceMove::new(C8, E7, false, None),
];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 4);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_knight_moves_full() {
let board = board_from_fen("8/8/8/3N4/4N3/8/8/8 w - - 0 1");
let (moves, count) = generate_knight_moves(&board);
let expected_moves = vec![
PieceMove::new(D5, C7, false, None), PieceMove::new(D5, E7, false, None), PieceMove::new(D5, B6, false, None), PieceMove::new(D5, F6, false, None), PieceMove::new(D5, B4, false, None), PieceMove::new(D5, F4, false, None), PieceMove::new(D5, C3, false, None), PieceMove::new(D5, E3, false, None), PieceMove::new(E4, D6, false, None), PieceMove::new(E4, F6, false, None), PieceMove::new(E4, C5, false, None), PieceMove::new(E4, G5, false, None), PieceMove::new(E4, C3, false, None), PieceMove::new(E4, G3, false, None), PieceMove::new(E4, D2, false, None), PieceMove::new(E4, F2, false, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 16);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
}