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, color: Color, ) -> Vec { 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, 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 { 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 { 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, ) -> Vec { 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, ) -> Vec { 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 { 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 { 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 { 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 { 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 { 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 { 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(()) } }