Files
zeal/src/movegen/movegen.rs
2024-09-06 20:30:57 +03:00

587 lines
19 KiB
Rust

use crate::board::bitboard::{
have_common_bit, lsb, square_to_bitboard, NOT_FILE_A, NOT_FILE_H, RANK_1, RANK_2, RANK_4,
RANK_5, RANK_7, RANK_8,
};
use crate::board::board::{Board, Color};
use crate::board::state::Castle;
use crate::movegen::attack::{
fetch_bishop_attacks, fetch_king_attacks, fetch_knight_attacks, fetch_pawn_attacks,
fetch_queen_attacks, fetch_rook_attacks,
};
use crate::movegen::r#move::{Move, MoveType, Promote};
use u64 as Bitboard;
pub fn pawn_pseudo_moves(
pawns: Bitboard,
all_occupancies: Bitboard,
opponent_occupancies: Bitboard,
en_passant_square: Option<usize>,
color: Color,
) -> Vec<Move> {
let mut moves = vec![];
match color {
Color::White => {
moves.extend(white_pawn_quiet_moves(pawns, all_occupancies));
moves.extend(white_pawn_capture_moves(
pawns,
opponent_occupancies,
en_passant_square,
));
}
Color::Black => {
moves.extend(black_pawn_quiet_moves(pawns, all_occupancies));
moves.extend(black_pawn_capture_moves(
pawns,
opponent_occupancies,
en_passant_square,
));
}
}
moves
}
fn add_promotion_moves(moves: &mut Vec<Move>, src: usize, dst: usize, capture: bool) {
let promotion_type = if capture {
MoveType::PromotionCapture
} else {
MoveType::Promotion
};
moves.extend([
Move::new_with_type(src, dst, promotion_type(Promote::Knight)),
Move::new_with_type(src, dst, promotion_type(Promote::Bishop)),
Move::new_with_type(src, dst, promotion_type(Promote::Rook)),
Move::new_with_type(src, dst, promotion_type(Promote::Queen)),
]);
}
fn white_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec<Move> {
let mut moves = vec![];
let empty = !occupancies;
let mut single_push = (pawns << 8) & empty;
while single_push != 0 {
let dst = lsb(single_push);
let src = dst - 8;
if have_common_bit(square_to_bitboard(src), RANK_7) {
add_promotion_moves(&mut moves, src, dst, false);
} else {
moves.push(Move::new(src, dst));
}
single_push &= single_push - 1;
}
let single_push = (pawns << 8) & empty;
let mut double_push = (single_push << 8) & empty & RANK_4;
while double_push != 0 {
let dst = lsb(double_push);
let src = dst - 16;
moves.push(Move::new_with_type(src, dst, MoveType::DoublePush));
double_push &= double_push - 1;
}
moves
}
fn black_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec<Move> {
let mut moves = vec![];
let empty = !occupancies;
let mut single_push = (pawns >> 8) & empty;
while single_push != 0 {
let dst = lsb(single_push);
let src = dst + 8;
if have_common_bit(square_to_bitboard(src), RANK_2) {
add_promotion_moves(&mut moves, src, dst, false);
} else {
moves.push(Move::new(src, dst));
}
single_push &= single_push - 1;
}
let single_push = (pawns >> 8) & empty;
let mut double_push = (single_push >> 8) & empty & RANK_5;
while double_push != 0 {
let dst = lsb(double_push);
let src = dst + 16;
moves.push(Move::new_with_type(src, dst, MoveType::DoublePush));
double_push &= double_push - 1;
}
moves
}
fn white_pawn_capture_moves(
pawns: Bitboard,
opponent_occupancies: Bitboard,
en_passant_square: Option<usize>,
) -> Vec<Move> {
let mut moves = vec![];
let mut w_pawns_capture_east = pawns & ((opponent_occupancies >> 9) & NOT_FILE_H);
let mut w_pawns_capture_west = pawns & ((opponent_occupancies >> 7) & NOT_FILE_A);
while w_pawns_capture_east != 0 {
let src = lsb(w_pawns_capture_east);
let dst = src + 9;
if have_common_bit(square_to_bitboard(dst), RANK_8) {
add_promotion_moves(&mut moves, src, dst, true);
} else {
moves.push(Move::new_with_type(src, dst, MoveType::Capture));
}
w_pawns_capture_east &= w_pawns_capture_east - 1;
}
while w_pawns_capture_west != 0 {
let src = lsb(w_pawns_capture_west);
let dst = src + 7;
if have_common_bit(square_to_bitboard(dst), RANK_8) {
add_promotion_moves(&mut moves, src, dst, true);
} else {
moves.push(Move::new_with_type(src, dst, MoveType::Capture));
}
w_pawns_capture_west &= w_pawns_capture_west - 1;
}
if let Some(en_passant_sq) = en_passant_square {
let attacked_src = fetch_pawn_attacks(en_passant_sq, Color::Black);
let mut result = attacked_src & pawns;
while result != 0 {
moves.push(Move::new_with_type(
lsb(result),
en_passant_sq,
MoveType::EnPassant,
));
result &= result - 1;
}
};
moves
}
fn black_pawn_capture_moves(
pawns: Bitboard,
opponent_occupancies: Bitboard,
en_passant_square: Option<usize>,
) -> Vec<Move> {
let mut moves = vec![];
let mut b_pawns_capture_east = pawns & ((opponent_occupancies << 7) & NOT_FILE_H);
let mut b_pawns_capture_west = pawns & ((opponent_occupancies << 9) & NOT_FILE_A);
while b_pawns_capture_east != 0 {
let src = lsb(b_pawns_capture_east);
let dst = src - 7;
if have_common_bit(square_to_bitboard(dst), RANK_1) {
add_promotion_moves(&mut moves, src, dst, true);
} else {
moves.push(Move::new_with_type(src, dst, MoveType::Capture));
}
b_pawns_capture_east &= b_pawns_capture_east - 1;
}
while b_pawns_capture_west != 0 {
let src = lsb(b_pawns_capture_west);
let dst = src - 9;
if have_common_bit(square_to_bitboard(dst), RANK_1) {
add_promotion_moves(&mut moves, src, dst, true);
} else {
moves.push(Move::new_with_type(src, dst, MoveType::Capture));
}
b_pawns_capture_west &= b_pawns_capture_west - 1;
}
if let Some(en_passant_square) = en_passant_square {
let attacked_src = fetch_pawn_attacks(en_passant_square, Color::White);
let mut result = attacked_src & pawns;
while result != 0 {
moves.push(Move::new_with_type(
lsb(result),
en_passant_square,
MoveType::EnPassant,
));
result &= result - 1;
}
};
moves
}
pub fn knight_pseudo_moves(
mut knights: Bitboard,
all_occupancies: Bitboard,
own_occupancies: Bitboard,
) -> Vec<Move> {
let mut moves = vec![];
let opponent_occupancies = all_occupancies ^ own_occupancies;
while knights != 0 {
let knight_square = lsb(knights);
let src = knight_square;
let mut attacks = fetch_knight_attacks(knight_square) & !own_occupancies;
while attacks != 0 {
let attack_sq = lsb(attacks);
if have_common_bit(square_to_bitboard(attack_sq), opponent_occupancies) {
moves.push(Move::new_with_type(src, attack_sq, MoveType::Capture));
} else {
moves.push(Move::new(src, attack_sq));
}
attacks &= attacks - 1;
}
knights &= knights - 1;
}
moves
}
pub fn bishop_pseudo_moves(
mut bishops: Bitboard,
all_occupancies: Bitboard,
own_occupancies: Bitboard,
) -> Vec<Move> {
let mut moves = vec![];
let opponent_occupancies = all_occupancies ^ own_occupancies;
while bishops != 0 {
let bishop_square = lsb(bishops);
let src = bishop_square;
let mut attacks = fetch_bishop_attacks(all_occupancies, bishop_square) & !own_occupancies;
while attacks != 0 {
let attack_sq = lsb(attacks);
if have_common_bit(square_to_bitboard(attack_sq), opponent_occupancies) {
moves.push(Move::new_with_type(src, attack_sq, MoveType::Capture));
} else {
moves.push(Move::new(src, attack_sq));
}
attacks &= attacks - 1;
}
bishops &= bishops - 1;
}
moves
}
pub fn rook_pseudo_moves(
mut rooks: Bitboard,
all_occupancies: Bitboard,
own_occupancies: Bitboard,
) -> Vec<Move> {
let mut moves = vec![];
let opponent_occupancies = all_occupancies ^ own_occupancies;
while rooks != 0 {
let rook_square = lsb(rooks);
let src = rook_square;
let mut attacks = fetch_rook_attacks(all_occupancies, rook_square) & !own_occupancies;
while attacks != 0 {
let attack_sq = lsb(attacks);
if have_common_bit(square_to_bitboard(attack_sq), opponent_occupancies) {
moves.push(Move::new_with_type(src, attack_sq, MoveType::Capture));
} else {
moves.push(Move::new(src, attack_sq));
}
attacks &= attacks - 1;
}
rooks &= rooks - 1;
}
moves
}
pub fn queen_pseudo_moves(
mut queens: Bitboard,
all_occupancies: Bitboard,
own_occupancies: Bitboard,
) -> Vec<Move> {
let mut moves = vec![];
let opponent_occupancies = all_occupancies ^ own_occupancies;
while queens != 0 {
let queen_square = lsb(queens);
let src = queen_square;
let mut attacks = fetch_queen_attacks(all_occupancies, queen_square) & !own_occupancies;
while attacks != 0 {
let attack_sq = lsb(attacks);
if have_common_bit(square_to_bitboard(attack_sq), opponent_occupancies) {
moves.push(Move::new_with_type(src, attack_sq, MoveType::Capture));
} else {
moves.push(Move::new(src, attack_sq));
}
attacks &= attacks - 1;
}
queens &= queens - 1;
}
moves
}
pub fn king_pseudo_moves(
king: Bitboard,
all_occupancies: Bitboard,
own_occupancies: Bitboard,
board: &Board,
color: Color,
) -> Vec<Move> {
let mut moves = vec![];
let king_square = lsb(king);
let src = king_square;
let mut attacks = fetch_king_attacks(king_square) & !own_occupancies;
let opponent_occupancies = all_occupancies ^ own_occupancies;
while attacks != 0 {
let attack_sq = lsb(attacks);
if have_common_bit(square_to_bitboard(attack_sq), opponent_occupancies) {
moves.push(Move::new_with_type(src, attack_sq, MoveType::Capture));
} else {
moves.push(Move::new(src, attack_sq));
}
attacks &= attacks - 1;
}
moves.extend(king_castling_moves(board, color, all_occupancies));
moves
}
fn king_castling_moves(board: &Board, color: Color, all_occupancies: Bitboard) -> Vec<Move> {
let mut moves = vec![];
let (king_src, king_dst_short, king_dst_long, path_short, path_long) = match color {
Color::White => (4, 6, 2, 0x60_u64, 0xe_u64),
Color::Black => (60, 62, 58, 0x6000000000000000_u64, 0xe00000000000000_u64),
};
let mut add_move_if_empty_path = |path_mask, king_dst| {
if !have_common_bit(all_occupancies, path_mask) {
moves.push(Move::new_with_type(king_src, king_dst, MoveType::Castle));
}
};
match (
board.state.castling_ability(color),
king_and_adj_square_safety(board, color),
) {
(Castle::Both, Castle::Both) => {
add_move_if_empty_path(path_short, king_dst_short);
add_move_if_empty_path(path_long, king_dst_long);
}
(Castle::Both | Castle::Short, Castle::Short) | (Castle::Short, Castle::Both) => {
add_move_if_empty_path(path_short, king_dst_short);
}
(Castle::Both | Castle::Long, Castle::Long) | (Castle::Long, Castle::Both) => {
add_move_if_empty_path(path_long, king_dst_long);
}
_ => (),
};
moves
}
fn king_and_adj_square_safety(board: &Board, color: Color) -> Castle {
let (king_at, opponent_color) = match color {
Color::White => (4, Color::Black),
Color::Black => (60, Color::White),
};
if board.is_attacked(king_at, opponent_color) {
return Castle::None;
}
let short_attacked = board.is_attacked(king_at + 1, opponent_color);
let long_attacked = board.is_attacked(king_at - 1, opponent_color);
match (short_attacked, long_attacked) {
(true, true) => Castle::None,
(true, false) => Castle::Long,
(false, true) => Castle::Short,
(false, false) => Castle::Both,
}
}
#[cfg(test)]
mod tests {
use crate::{board::fen::from_fen, movegen::attack::init_attacks};
use super::*;
use crate::board::board::PieceType;
const FEN_PAWN_MOVES: &str = "r1bqk2r/p4pbp/1pnp1np1/P3P1B1/3NNP2/8/1PP3PP/R2QKB1R w - - 0 1";
#[test]
fn test_pawn_pseudo_moves() -> Result<(), String> {
init_attacks();
let new_game = from_fen(FEN_PAWN_MOVES)?;
let expected = vec![
Move::new(9, 17),
Move::new_with_type(9, 25, MoveType::DoublePush),
Move::new(10, 18),
Move::new_with_type(10, 26, MoveType::DoublePush),
Move::new(14, 22),
Move::new_with_type(14, 30, MoveType::DoublePush),
Move::new(15, 23),
Move::new_with_type(15, 31, MoveType::DoublePush),
Move::new(29, 37),
Move::new(32, 40),
Move::new_with_type(32, 41, MoveType::Capture),
Move::new_with_type(36, 43, MoveType::Capture),
Move::new(36, 44),
Move::new_with_type(36, 45, MoveType::Capture),
];
let mut actual = new_game.board.pseudo_moves(Color::White, PieceType::Pawn);
actual.sort();
assert_eq!(expected, actual);
let expected = vec![
Move::new_with_type(41, 32, MoveType::Capture),
Move::new(41, 33),
Move::new(43, 35),
Move::new_with_type(43, 36, MoveType::Capture),
Move::new(48, 40),
Move::new_with_type(55, 39, MoveType::DoublePush),
Move::new(55, 47),
];
let mut actual = new_game.board.pseudo_moves(Color::Black, PieceType::Pawn);
actual.sort();
assert_eq!(expected, actual);
Ok(())
}
const FEN_KNIGHT_MOVES: &str = "r1bqkbnr/ppp2p1p/2np4/6p1/3PPp2/5N2/PPP3PP/RNBQKB1R w - - 0 1";
#[test]
fn test_knight_pseudo_moves() -> Result<(), String> {
init_attacks();
let new_game = from_fen(FEN_KNIGHT_MOVES)?;
let expected = vec![
Move::new(1, 11),
Move::new(1, 16),
Move::new(1, 18),
Move::new(21, 6),
Move::new(21, 11),
Move::new(21, 31),
Move::new(21, 36),
Move::new_with_type(21, 38, MoveType::Capture),
];
let mut actual = new_game.board.pseudo_moves(Color::White, PieceType::Knight);
actual.sort();
assert_eq!(expected, actual);
Ok(())
}
const FEN_BISHOP_MOVES: &str = "rn1qk1nr/pbppbppp/1p6/8/2B1Pp2/3P1N2/PPB3PP/RN1QK2R w - - 0 1";
#[test]
fn test_bishop_pseudo_moves() -> Result<(), String> {
init_attacks();
let new_game = from_fen(FEN_BISHOP_MOVES)?;
let expected = vec![
Move::new(10, 17),
Move::new(10, 24),
Move::new(26, 17),
Move::new(26, 33),
Move::new(26, 35),
Move::new(26, 40),
Move::new(26, 44),
Move::new_with_type(26, 53, MoveType::Capture),
];
let mut actual = new_game.board.pseudo_moves(Color::White, PieceType::Bishop);
actual.sort();
assert_eq!(expected, actual);
Ok(())
}
const FEN_ROOK_MOVES: &str = "rnbqkb1r/pp3p1p/4pn2/6p1/2P5/2N5/P1BRR3/3QK1N1 w kq - 0 1";
#[test]
fn test_rook_pseudo_moves() -> Result<(), String> {
init_attacks();
let new_game = from_fen(FEN_ROOK_MOVES)?;
let expected = vec![
Move::new(11, 19),
Move::new(11, 27),
Move::new(11, 35),
Move::new(11, 43),
Move::new(11, 51),
Move::new_with_type(11, 59, MoveType::Capture),
Move::new(12, 13),
Move::new(12, 14),
Move::new(12, 15),
Move::new(12, 20),
Move::new(12, 28),
Move::new(12, 36),
Move::new_with_type(12, 44, MoveType::Capture),
];
let mut actual = new_game.board.pseudo_moves(Color::White, PieceType::Rook);
actual.sort();
assert_eq!(expected, actual);
Ok(())
}
const FEN_QUEEN_MOVES: &str = "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/1QN5/PP2PPPP/R1B1KBNR w - - 0 1";
#[test]
fn test_queen_pseudo_moves() -> Result<(), String> {
init_attacks();
let new_game = from_fen(FEN_QUEEN_MOVES)?;
let expected = vec![
Move::new(17, 3),
Move::new(17, 10),
Move::new(17, 16),
Move::new(17, 24),
Move::new(17, 25),
Move::new(17, 33),
Move::new(17, 41),
Move::new_with_type(17, 49, MoveType::Capture),
];
let mut actual = new_game.board.pseudo_moves(Color::White, PieceType::Queen);
actual.sort();
assert_eq!(expected, actual);
Ok(())
}
const FEN_KING_MOVES: [&str; 3] = [
"R7/6k1/8/8/P6P/6K1/8/4r3 w - - 0 1",
"rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/1QN5/PP1BPPPP/R3KBNR w KQkq - 0 1",
"rnq1k2r/pp2p2p/2pbb1p1/5pB1/3P2n1/2N3P1/PPQ1PPBP/R3K1NR b KQkq - 0 1",
];
#[test]
fn test_king_pseudo_moves() -> Result<(), String> {
init_attacks();
let new_game = from_fen(FEN_KING_MOVES[0])?;
let expected = vec![
Move::new(22, 13),
Move::new(22, 14),
Move::new(22, 15),
Move::new(22, 21),
Move::new(22, 23),
Move::new(22, 29),
Move::new(22, 30),
];
let mut actual = new_game.board.pseudo_moves(Color::White, PieceType::King);
actual.sort();
assert_eq!(expected, actual);
let new_game_2 = from_fen(FEN_KING_MOVES[1])?;
let expected = vec![Move::new_with_type(4, 2, MoveType::Castle), Move::new(4, 3)];
let mut actual = new_game_2.board.pseudo_moves(Color::White, PieceType::King);
actual.sort();
assert_eq!(expected, actual);
let new_game_3 = from_fen(FEN_KING_MOVES[2])?;
let expected = vec![
Move::new(60, 51),
Move::new(60, 53),
Move::new(60, 59),
Move::new(60, 61),
Move::new_with_type(60, 62, MoveType::Castle),
];
let mut actual = new_game_3.board.pseudo_moves(Color::Black, PieceType::King);
actual.sort();
assert_eq!(expected, actual);
Ok(())
}
}