500 lines
15 KiB
Rust
500 lines
15 KiB
Rust
use crate::attack::{
|
|
get_bishop_attacks, get_king_attacks, get_knight_attacks, get_pawn_attacks, get_queen_attacks,
|
|
get_rook_attacks,
|
|
};
|
|
use crate::board::Color;
|
|
use crate::game::CastlingRights;
|
|
use u64 as Bitboard;
|
|
|
|
const NOT_A_FILE: Bitboard = 0xfefefefefefefefe;
|
|
const NOT_H_FILE: Bitboard = 0x7f7f7f7f7f7f7f7f;
|
|
const RANK_4: Bitboard = 0x00000000FF000000;
|
|
const RANK_5: Bitboard = 0x000000FF00000000;
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct Move {
|
|
pub source: u32,
|
|
pub target: u32,
|
|
}
|
|
|
|
impl Move {
|
|
pub const fn new(source: u32, target: u32) -> Self {
|
|
Self { source, target }
|
|
}
|
|
}
|
|
|
|
pub fn pawn_pseudo_moves(
|
|
pawns: Bitboard,
|
|
all_occupancies: Bitboard,
|
|
enemy_occupancies: Bitboard,
|
|
en_passant_square: Option<u8>,
|
|
color: Color,
|
|
) -> Vec<Move> {
|
|
let mut moves: Vec<Move> = vec![];
|
|
match color {
|
|
Color::White => {
|
|
moves.extend(white_pawn_quiet_moves(pawns, all_occupancies));
|
|
moves.extend(white_pawn_capture_moves(
|
|
pawns,
|
|
enemy_occupancies,
|
|
en_passant_square,
|
|
));
|
|
}
|
|
Color::Black => {
|
|
moves.extend(black_pawn_capture_moves(
|
|
pawns,
|
|
enemy_occupancies,
|
|
en_passant_square,
|
|
));
|
|
moves.extend(black_pawn_quiet_moves(pawns, all_occupancies));
|
|
}
|
|
}
|
|
moves
|
|
}
|
|
|
|
fn white_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec<Move> {
|
|
let mut moves: Vec<Move> = vec![];
|
|
let empty = !occupancies;
|
|
|
|
let mut single_push_targets = (pawns << 8) & empty;
|
|
while single_push_targets != 0 {
|
|
let to = single_push_targets.trailing_zeros();
|
|
let from = to - 8;
|
|
moves.push(Move::new(from, to));
|
|
single_push_targets &= single_push_targets - 1;
|
|
}
|
|
let single_push_targets = (pawns << 8) & empty;
|
|
let mut double_push_targets = (single_push_targets << 8) & empty & RANK_4;
|
|
while double_push_targets != 0 {
|
|
let to = double_push_targets.trailing_zeros();
|
|
let from = to - 16;
|
|
moves.push(Move::new(from, to));
|
|
double_push_targets &= double_push_targets - 1;
|
|
}
|
|
moves
|
|
}
|
|
|
|
fn black_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec<Move> {
|
|
let mut moves: Vec<Move> = vec![];
|
|
let empty = !occupancies;
|
|
|
|
let mut single_push_targets = (pawns >> 8) & empty;
|
|
while single_push_targets != 0 {
|
|
let to = single_push_targets.trailing_zeros();
|
|
let from = to + 8;
|
|
moves.push(Move::new(from, to));
|
|
single_push_targets &= single_push_targets - 1;
|
|
}
|
|
let single_push_targets = (pawns >> 8) & empty;
|
|
let mut double_push_targets = (single_push_targets >> 8) & empty & RANK_5;
|
|
while double_push_targets != 0 {
|
|
let to = double_push_targets.trailing_zeros();
|
|
let from = to + 16;
|
|
moves.push(Move::new(from, to));
|
|
double_push_targets &= double_push_targets - 1;
|
|
}
|
|
moves
|
|
}
|
|
|
|
fn white_pawn_capture_moves(
|
|
pawns: Bitboard,
|
|
enemy_occupancies: Bitboard,
|
|
en_passant_square: Option<u8>,
|
|
) -> Vec<Move> {
|
|
let mut moves: Vec<Move> = vec![];
|
|
let mut w_pawns_capture_east = pawns & ((enemy_occupancies >> 9) & NOT_H_FILE);
|
|
let mut w_pawns_capture_west = pawns & ((enemy_occupancies >> 7) & NOT_A_FILE);
|
|
|
|
while w_pawns_capture_east != 0 {
|
|
let from = w_pawns_capture_east.trailing_zeros();
|
|
let to = from + 9;
|
|
moves.push(Move::new(from, to));
|
|
w_pawns_capture_east &= w_pawns_capture_east - 1;
|
|
}
|
|
while w_pawns_capture_west != 0 {
|
|
let from = w_pawns_capture_west.trailing_zeros();
|
|
let to = from + 7;
|
|
moves.push(Move::new(from, to));
|
|
w_pawns_capture_west &= w_pawns_capture_west - 1;
|
|
}
|
|
|
|
if let Some(en_passant_square) = en_passant_square {
|
|
let attacked_from = get_pawn_attacks(en_passant_square as usize, Color::Black);
|
|
let result = attacked_from & pawns;
|
|
moves.push(Move::new(result.trailing_zeros(), en_passant_square as u32));
|
|
};
|
|
moves
|
|
}
|
|
|
|
fn black_pawn_capture_moves(
|
|
pawns: Bitboard,
|
|
enemy_occupancies: Bitboard,
|
|
en_passant_square: Option<u8>,
|
|
) -> Vec<Move> {
|
|
let mut moves: Vec<Move> = vec![];
|
|
let mut b_pawns_capture_east = pawns & ((enemy_occupancies << 7) & NOT_H_FILE);
|
|
let mut b_pawns_capture_west = pawns & ((enemy_occupancies << 9) & NOT_A_FILE);
|
|
|
|
while b_pawns_capture_east != 0 {
|
|
let from = b_pawns_capture_east.trailing_zeros();
|
|
let to = from - 7;
|
|
moves.push(Move::new(from, to));
|
|
b_pawns_capture_east &= b_pawns_capture_east - 1;
|
|
}
|
|
|
|
while b_pawns_capture_west != 0 {
|
|
let from = b_pawns_capture_west.trailing_zeros();
|
|
let to = from - 9;
|
|
moves.push(Move::new(from, to));
|
|
b_pawns_capture_west &= b_pawns_capture_west - 1;
|
|
}
|
|
|
|
if let Some(en_passant_square) = en_passant_square {
|
|
let attacked_from = get_pawn_attacks(en_passant_square as usize, Color::White);
|
|
let result = attacked_from & pawns;
|
|
moves.push(Move::new(result.trailing_zeros(), en_passant_square as u32));
|
|
};
|
|
moves
|
|
}
|
|
|
|
pub fn knight_pseudo_moves(mut knights: Bitboard, occupancies: Bitboard) -> Vec<Move> {
|
|
let mut moves: Vec<Move> = vec![];
|
|
let empty = !occupancies;
|
|
|
|
while knights != 0 {
|
|
let knight_square = knights.trailing_zeros() as usize;
|
|
let from = knight_square as u32;
|
|
let mut attacks = get_knight_attacks(knight_square) & empty;
|
|
|
|
while attacks != 0 {
|
|
moves.push(Move::new(from, attacks.trailing_zeros()));
|
|
|
|
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<Move> = vec![];
|
|
let empty = !own_occupancies;
|
|
|
|
while bishops != 0 {
|
|
let bishop_square = bishops.trailing_zeros() as usize;
|
|
let from = bishop_square as u32;
|
|
let mut attacks = get_bishop_attacks(all_occupancies, bishop_square) & empty;
|
|
|
|
while attacks != 0 {
|
|
moves.push(Move::new(from, attacks.trailing_zeros()));
|
|
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<Move> = vec![];
|
|
let empty = !own_occupancies;
|
|
|
|
while rooks != 0 {
|
|
let rook_square = rooks.trailing_zeros() as usize;
|
|
let from = rook_square as u32;
|
|
let mut attacks = get_rook_attacks(all_occupancies, rook_square) & empty;
|
|
|
|
while attacks != 0 {
|
|
moves.push(Move::new(from, attacks.trailing_zeros()));
|
|
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<Move> = vec![];
|
|
let empty = !own_occupancies;
|
|
|
|
while queens != 0 {
|
|
let queen_square = queens.trailing_zeros() as usize;
|
|
let from = queen_square as u32;
|
|
let mut attacks = get_queen_attacks(all_occupancies, queen_square) & empty;
|
|
|
|
while attacks != 0 {
|
|
moves.push(Move::new(from, attacks.trailing_zeros()));
|
|
attacks &= attacks - 1;
|
|
}
|
|
queens &= queens - 1;
|
|
}
|
|
moves
|
|
}
|
|
|
|
pub fn king_pseudo_moves(
|
|
king: Bitboard,
|
|
all_occupancies: Bitboard,
|
|
own_occupancies: Bitboard,
|
|
castling_rights: CastlingRights,
|
|
color: Color,
|
|
) -> Vec<Move> {
|
|
let mut moves: Vec<Move> = vec![];
|
|
let empty: u64 = !own_occupancies;
|
|
let king_square = king.trailing_zeros() as usize;
|
|
let from = king_square as u32;
|
|
let mut attacks = get_king_attacks(king_square) & empty;
|
|
|
|
while attacks != 0 {
|
|
moves.push(Move::new(from, attacks.trailing_zeros()));
|
|
attacks &= attacks - 1;
|
|
}
|
|
|
|
match color {
|
|
Color::White => match castling_rights {
|
|
CastlingRights::Both => {
|
|
if all_occupancies & 0x60 == 0 {
|
|
moves.push(Move::new(4, 6))
|
|
} else if all_occupancies & 0xe == 0 {
|
|
moves.push(Move::new(4, 2))
|
|
}
|
|
}
|
|
CastlingRights::Short => {
|
|
if all_occupancies & 0x60 == 0 {
|
|
moves.push(Move::new(4, 6))
|
|
}
|
|
}
|
|
CastlingRights::Long => {
|
|
if all_occupancies & 0xe == 0 {
|
|
moves.push(Move::new(4, 2))
|
|
}
|
|
}
|
|
CastlingRights::None => (),
|
|
},
|
|
Color::Black => match castling_rights {
|
|
CastlingRights::Both => {
|
|
if all_occupancies & 0x6000000000000000 == 0 {
|
|
moves.push(Move::new(60, 62))
|
|
} else if all_occupancies & 0xe00000000000000 == 0 {
|
|
moves.push(Move::new(60, 58))
|
|
}
|
|
}
|
|
CastlingRights::Short => {
|
|
if all_occupancies & 0x6000000000000000 == 0 {
|
|
moves.push(Move::new(60, 62))
|
|
}
|
|
}
|
|
CastlingRights::Long => {
|
|
if all_occupancies & 0xe00000000000000 == 0 {
|
|
moves.push(Move::new(60, 58))
|
|
}
|
|
}
|
|
CastlingRights::None => (),
|
|
},
|
|
}
|
|
|
|
moves
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{attack::init_attacks, fen::from_fen};
|
|
|
|
use super::*;
|
|
use crate::board::Kind;
|
|
|
|
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(9, 25),
|
|
Move::new(10, 18),
|
|
Move::new(10, 26),
|
|
Move::new(14, 22),
|
|
Move::new(14, 30),
|
|
Move::new(15, 23),
|
|
Move::new(15, 31),
|
|
Move::new(29, 37),
|
|
Move::new(32, 40),
|
|
Move::new(32, 41),
|
|
Move::new(36, 43),
|
|
Move::new(36, 44),
|
|
Move::new(36, 45),
|
|
];
|
|
|
|
let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Pawn);
|
|
actual.sort();
|
|
assert_eq!(expected, actual);
|
|
|
|
let expected = vec![
|
|
Move::new(41, 32),
|
|
Move::new(41, 33),
|
|
Move::new(43, 35),
|
|
Move::new(43, 36),
|
|
Move::new(48, 40),
|
|
Move::new(55, 39),
|
|
Move::new(55, 47),
|
|
];
|
|
let mut actual = new_game.board.pseudo_moves(Color::Black, Kind::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(21, 38),
|
|
];
|
|
let mut actual = new_game.board.pseudo_moves(Color::White, Kind::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(26, 53),
|
|
];
|
|
let mut actual = new_game.board.pseudo_moves(Color::White, Kind::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(11, 59),
|
|
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(12, 44),
|
|
];
|
|
let mut actual = new_game.board.pseudo_moves(Color::White, Kind::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(17, 49),
|
|
];
|
|
let mut actual = new_game.board.pseudo_moves(Color::White, Kind::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, Kind::King);
|
|
actual.sort();
|
|
assert_eq!(expected, actual);
|
|
|
|
let new_game_2 = from_fen(FEN_KING_MOVES[1])?;
|
|
let expected = vec![Move::new(4, 2), Move::new(4, 3)];
|
|
let mut actual = new_game_2.board.pseudo_moves(Color::White, Kind::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(60, 62),
|
|
];
|
|
let mut actual = new_game_3.board.pseudo_moves(Color::Black, Kind::King);
|
|
actual.sort();
|
|
assert_eq!(expected, actual);
|
|
|
|
Ok(())
|
|
}
|
|
}
|