use crate::{
constants::{FILE_A, FILE_H},
model::{bitboard::BitBoard, gameboard::GameBoard, piecemove::PieceMove},
movegen::add_move_to_list,
};
pub const MAX_KING_MOVES: usize = 8;
pub(crate) fn generate_king_moves(state: &GameBoard) -> ([PieceMove; MAX_KING_MOVES], usize) {
let mut moves = [PieceMove::NULL; MAX_KING_MOVES];
let mut count = 0;
let all_occupied =
state.pawns | state.knights | state.bishops | state.rooks | state.queens | state.kings;
let (my_king, other_pieces): (BitBoard, u64) = if state.playing {
(
state.kings & state.colour,
(all_occupied & !state.colour).into(),
)
} else {
(
state.kings & !state.colour,
(all_occupied & state.colour).into(),
)
};
let king_move_data: [(i8, Option<u64>); 8] = [
(-8, None), (-7, Some(FILE_H)), (1, Some(FILE_H)), (9, Some(FILE_H)), (8, None), (7, Some(FILE_A)), (-1, Some(FILE_A)), (-9, Some(FILE_A)), ];
for (dir, mask) in king_move_data {
let new_pos = if dir > 0 {
(my_king & !mask.unwrap_or(u64::MIN)) << (dir as u8)
} else {
(my_king & !mask.unwrap_or(u64::MIN)) >> ((-dir) as u8)
};
let blockers = new_pos.raw() & all_occupied.raw();
let mut attackers = blockers & 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_KING_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_KING_MOVES,
PieceMove::new(from_board, to_board, false, None),
);
quiet_moves &= !(1 << to_board);
}
}
let (queen_side, king_side) = if state.playing {
state.casling_right_white()
} else {
state.casling_right_black()
};
let my_rooks = if state.playing {
state.rooks & state.colour
} else {
state.rooks & !state.colour
};
if queen_side {
let (king_pos, rook_pos, empty_squares) = if state.playing {
(
crate::constants::E1,
crate::constants::A1,
(1u64 << crate::constants::B1)
| (1u64 << crate::constants::C1)
| (1u64 << crate::constants::D1),
)
} else {
(
crate::constants::E8,
crate::constants::A8,
(1u64 << crate::constants::B8)
| (1u64 << crate::constants::C8)
| (1u64 << crate::constants::D8),
)
};
if my_rooks.get_bit(rook_pos).unwrap_or(false) && (all_occupied.raw() & empty_squares) == 0 {
let king_to = if state.playing {
crate::constants::C1
} else {
crate::constants::C8
};
add_move_to_list(
&mut moves,
&mut count,
MAX_KING_MOVES,
PieceMove::new_castling(king_pos, king_to),
);
}
}
if king_side {
let (king_pos, rook_pos, empty_squares) = if state.playing {
(
crate::constants::E1,
crate::constants::H1,
(1u64 << crate::constants::F1) | (1u64 << crate::constants::G1),
)
} else {
(
crate::constants::E8,
crate::constants::H8,
(1u64 << crate::constants::F8) | (1u64 << crate::constants::G8),
)
};
if my_rooks.get_bit(rook_pos).unwrap_or(false) && (all_occupied.raw() & empty_squares) == 0 {
let king_to = if state.playing {
crate::constants::G1
} else {
crate::constants::G8
};
add_move_to_list(
&mut moves,
&mut count,
MAX_KING_MOVES,
PieceMove::new_castling(king_pos, king_to),
);
}
}
(moves, count)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
constants::*,
model::{
gameboard::{GameBoard, PieceType},
gamedata::GameData,
piecemove::PieceMove,
},
};
fn sort_and_compare_moves(mut moves: Vec<PieceMove>) -> Vec<PieceMove> {
moves.sort();
moves
}
fn moves_to_vec(moves: &[PieceMove; MAX_KING_MOVES], count: usize) -> Vec<PieceMove> {
moves[0..count].to_vec()
}
fn board_from_fen(fen: &str) -> GameBoard {
let gamedata = GameData::from_fen(fen).unwrap_or_else(|e| panic!("Failed to parse FEN: {e:?}"));
gamedata.board
}
#[test]
fn test_generate_king_moves_empty_board() {
let board = GameBoard::new(); let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 0);
assert!(generated_moves.is_empty());
}
#[test]
fn test_generate_king_moves_white_king_center() {
let mut board = GameBoard::new();
board.set_square(D4, PieceType::King, true); board.playing = true;
let (moves, count) = generate_king_moves(&board);
let expected_moves = vec![
PieceMove::new(D4, C3, false, None), PieceMove::new(D4, C4, false, None), PieceMove::new(D4, C5, false, None), PieceMove::new(D4, D3, false, None), PieceMove::new(D4, D5, false, None), PieceMove::new(D4, E3, false, None), PieceMove::new(D4, E4, false, None), PieceMove::new(D4, E5, 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_king_moves_white_king_corner_a1() {
let mut board = GameBoard::new();
board.set_square(A1, PieceType::King, true); board.playing = true;
let (moves, count) = generate_king_moves(&board);
let expected_moves = vec![
PieceMove::new(A1, A2, false, None), PieceMove::new(A1, B1, false, None), PieceMove::new(A1, B2, false, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 3);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_king_moves_black_king_corner_h8() {
let mut board = GameBoard::new();
board.set_square(H8, PieceType::King, false); board.playing = false;
let (moves, count) = generate_king_moves(&board);
let expected_moves = vec![
PieceMove::new(H8, G7, false, None), PieceMove::new(H8, G8, false, None), PieceMove::new(H8, H7, false, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 3);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_king_moves_white_king_blocked_by_friendly() {
let board = board_from_fen("8/8/8/2PPP3/2PKP3/2PPP3/8/8 w - - 0 1"); let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 0); assert!(generated_moves.is_empty());
}
#[test]
fn test_generate_king_moves_white_king_captures() {
let board = board_from_fen("8/8/8/2ppp3/2pKp3/2ppp3/8/8 w - - 0 1"); let (moves, count) = generate_king_moves(&board);
let expected_moves = vec![
PieceMove::new(D4, C3, true, None), PieceMove::new(D4, C4, true, None), PieceMove::new(D4, C5, true, None), PieceMove::new(D4, D3, true, None), PieceMove::new(D4, D5, true, None), PieceMove::new(D4, E3, true, None), PieceMove::new(D4, E4, true, None), PieceMove::new(D4, E5, 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_king_moves_black_king_captures() {
let board = board_from_fen("8/8/8/2PPP3/2PkP3/2PPP3/8/8 b - - 0 1"); let (moves, count) = generate_king_moves(&board);
let expected_moves = vec![
PieceMove::new(D4, C3, true, None), PieceMove::new(D4, C4, true, None), PieceMove::new(D4, C5, true, None), PieceMove::new(D4, D3, true, None), PieceMove::new(D4, D5, true, None), PieceMove::new(D4, E3, true, None), PieceMove::new(D4, E4, true, None), PieceMove::new(D4, E5, 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_king_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_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 0);
assert!(generated_moves.is_empty());
}
#[test]
fn test_generate_king_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_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 0);
assert!(generated_moves.is_empty());
}
#[test]
fn test_generate_king_moves_white_kingside_castling() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK2R w KQkq - 0 1");
let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let has_castling = generated_moves
.iter()
.any(|m| m.from_square() == E1 && m.to_square() == G1);
assert!(has_castling, "Should be able to castle kingside");
assert!(count > 0);
}
#[test]
fn test_generate_king_moves_white_queenside_castling() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/R3KBNR w KQkq - 0 1");
let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let has_castling = generated_moves
.iter()
.any(|m| m.from_square() == E1 && m.to_square() == C1);
assert!(has_castling, "Should be able to castle queenside");
assert!(count > 0);
}
#[test]
fn test_generate_king_moves_black_kingside_castling() {
let board = board_from_fen("rnbqk2r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1");
let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let has_castling = generated_moves
.iter()
.any(|m| m.from_square() == E8 && m.to_square() == G8);
assert!(has_castling, "Should be able to castle kingside");
assert!(count > 0);
}
#[test]
fn test_generate_king_moves_black_queenside_castling() {
let board = board_from_fen("r3kbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1");
let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let has_castling = generated_moves
.iter()
.any(|m| m.from_square() == E8 && m.to_square() == C8);
assert!(has_castling, "Should be able to castle queenside");
assert!(count > 0);
}
#[test]
fn test_generate_king_moves_castling_blocked() {
let board = board_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let has_kingside_castling = generated_moves
.iter()
.any(|m| m.from_square() == E1 && m.to_square() == G1);
let has_queenside_castling = generated_moves
.iter()
.any(|m| m.from_square() == E1 && m.to_square() == C1);
assert!(
!has_kingside_castling,
"Should not be able to castle kingside when blocked"
);
assert!(
!has_queenside_castling,
"Should not be able to castle queenside when blocked"
);
}
#[test]
fn test_generate_king_moves_no_castling_rights() {
let board = board_from_fen("rnbqk2r/pppppppp/8/8/8/8/PPPPPPPP/RNBQK2R w - - 0 1");
let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let has_castling = generated_moves.iter().any(|m| {
(m.from_square() == E1 && m.to_square() == G1)
|| (m.from_square() == E1 && m.to_square() == C1)
});
assert!(!has_castling, "Should not be able to castle without rights");
}
#[test]
fn test_generate_king_moves_mixed_scenario() {
let board = board_from_fen("8/8/8/8/2pK4/3P4/8/8 w - - 0 1"); let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let expected_moves = vec![
PieceMove::new(D4, C3, false, None), PieceMove::new(D4, C4, true, None), PieceMove::new(D4, C5, false, None), PieceMove::new(D4, D5, false, None), PieceMove::new(D4, E3, false, None), PieceMove::new(D4, E4, false, None), PieceMove::new(D4, E5, false, None), ];
assert_eq!(count, 7);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_king_moves_edge_of_board() {
let mut board = GameBoard::new();
board.set_square(A4, PieceType::King, true); board.playing = true;
let (moves, count) = generate_king_moves(&board);
let expected_moves = vec![
PieceMove::new(A4, A3, false, None), PieceMove::new(A4, A5, false, None), PieceMove::new(A4, B3, false, None), PieceMove::new(A4, B4, false, None), PieceMove::new(A4, B5, false, None), ];
let generated_moves = moves_to_vec(&moves, count);
assert_eq!(count, 5);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
#[test]
fn test_generate_king_moves_complex_scenario() {
let board = board_from_fen("8/8/8/3k4/2pKp3/3r4/8/8 w - - 0 1"); let (moves, count) = generate_king_moves(&board);
let generated_moves = moves_to_vec(&moves, count);
let expected_moves = vec![
PieceMove::new(D4, C5, false, None), PieceMove::new(D4, D5, true, None), PieceMove::new(D4, E5, false, None), PieceMove::new(D4, C4, true, None), PieceMove::new(D4, E4, true, None), PieceMove::new(D4, C3, false, None), PieceMove::new(D4, D3, true, None), PieceMove::new(D4, E3, false, None), ];
assert_eq!(count, 8);
assert_eq!(
sort_and_compare_moves(generated_moves),
sort_and_compare_moves(expected_moves)
);
}
}