Files
zeal/src/movegen/attack_generator.rs

476 lines
16 KiB
Rust

use crate::board::{
bitboard::{
bitboard_to_coords, have_common_bit, lsb, square_to_bitboard, square_to_bitboard_wrapping,
EMPTY, NOT_FILE_A, NOT_FILE_AB, NOT_FILE_GH, NOT_FILE_H,
},
board::Color,
square::coords_to_square,
};
use u64 as Bitboard;
#[rustfmt::skip]
pub const ROOK_RELEVANT_BITS: [usize; 64] = [
12, 11, 11, 11, 11, 11, 11, 12,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
12, 11, 11, 11, 11, 11, 11, 12,
];
#[rustfmt::skip]
pub const BISHOP_RELEVANT_BITS: [usize; 64] = [
6, 5, 5, 5, 5, 5, 5, 6,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 7, 7, 7, 7, 5, 5,
5, 5, 7, 9, 9, 7, 5, 5,
5, 5, 7, 9, 9, 7, 5, 5,
5, 5, 7, 7, 7, 7, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 5, 5, 5, 5, 5, 5, 6,
];
static mut PAWN_ATTACKS: [[Bitboard; 2]; 64] = [[0; 2]; 64];
static mut KNIGHT_ATTACKS: [Bitboard; 64] = [0; 64];
static mut KING_ATTACKS: [Bitboard; 64] = [0; 64];
static mut BISHOP_ATTACKS: [[Bitboard; 512]; 64] = [[0; 512]; 64];
static mut ROOK_ATTACKS: [[Bitboard; 4096]; 64] = [[0; 4096]; 64];
const fn pawn_attacks(bitboard: Bitboard, color: Color) -> Bitboard {
let mut attacks = EMPTY;
match color {
Color::Black => {
attacks |= (bitboard & NOT_FILE_H) >> 7;
attacks |= (bitboard & NOT_FILE_A) >> 9;
}
Color::White => {
attacks |= (bitboard & NOT_FILE_A) << 7;
attacks |= (bitboard & NOT_FILE_H) << 9;
}
};
attacks
}
const fn knight_attacks(bitboard: Bitboard) -> Bitboard {
let mut attacks = EMPTY;
attacks |= (bitboard & NOT_FILE_AB) << 6;
attacks |= (bitboard & NOT_FILE_GH) << 10;
attacks |= (bitboard & NOT_FILE_A) << 15;
attacks |= (bitboard & NOT_FILE_H) << 17;
attacks |= (bitboard & NOT_FILE_GH) >> 6;
attacks |= (bitboard & NOT_FILE_AB) >> 10;
attacks |= (bitboard & NOT_FILE_H) >> 15;
attacks |= (bitboard & NOT_FILE_A) >> 17;
attacks
}
const fn king_attacks(bitboard: Bitboard) -> Bitboard {
let mut attacks = EMPTY;
attacks |= (bitboard & NOT_FILE_H) << 1;
attacks |= (bitboard & NOT_FILE_A) << 7;
attacks |= bitboard << 8;
attacks |= (bitboard & NOT_FILE_H) << 9;
attacks |= (bitboard & NOT_FILE_A) >> 1;
attacks |= (bitboard & NOT_FILE_H) >> 7;
attacks |= bitboard >> 8;
attacks |= (bitboard & NOT_FILE_A) >> 9;
attacks
}
pub fn mask_bishop_attacks(bitboard: Bitboard) -> Bitboard {
let mut attacks = EMPTY;
let (rank_dst, file_dst) = bitboard_to_coords(bitboard);
let mut add_attacks_in_direction = |rank_step: isize, file_step: isize| {
let mut rank = rank_dst as isize;
let mut file = file_dst as isize;
while (1..=6).contains(&(rank + rank_step)) && (1..=6).contains(&(file + file_step)) {
rank += rank_step;
file += file_step;
attacks |= square_to_bitboard(coords_to_square(rank as usize, file as usize));
}
};
add_attacks_in_direction(1, 1);
add_attacks_in_direction(-1, 1);
add_attacks_in_direction(1, -1);
add_attacks_in_direction(-1, -1);
attacks
}
pub fn mask_rook_attacks(bitboard: Bitboard) -> Bitboard {
let mut attacks = EMPTY;
let (rank_dst, file_dst) = bitboard_to_coords(bitboard);
let mut add_attacks_in_direction = |mut coord: usize, step: isize, is_vertical: bool| {
while (1..=6).contains(&(coord as isize + step)) {
coord = (coord as isize + step) as usize;
let attack_square = if is_vertical {
square_to_bitboard(coords_to_square(coord, file_dst))
} else {
square_to_bitboard(coords_to_square(rank_dst, coord))
};
attacks |= attack_square;
}
};
add_attacks_in_direction(rank_dst, 1, true);
add_attacks_in_direction(rank_dst, -1, true);
add_attacks_in_direction(file_dst, 1, false);
add_attacks_in_direction(file_dst, -1, false);
attacks
}
pub fn bishop_attacks_on_the_fly(bitboard: Bitboard, blocker: Bitboard) -> Bitboard {
let mut attacks = EMPTY;
let (rank_dst, file_dst) = bitboard_to_coords(bitboard);
let mut add_attacks_in_direction = |rank_step: isize, file_step: isize| {
let mut rank = rank_dst as isize;
let mut file = file_dst as isize;
while (0..=7).contains(&(rank + rank_step)) && (0..=7).contains(&(file + file_step)) {
rank += rank_step;
file += file_step;
let attack_square = square_to_bitboard(coords_to_square(rank as usize, file as usize));
attacks |= attack_square;
if have_common_bit(attack_square, blocker) {
break;
}
}
};
add_attacks_in_direction(1, 1);
add_attacks_in_direction(-1, 1);
add_attacks_in_direction(1, -1);
add_attacks_in_direction(-1, -1);
attacks
}
pub fn rook_attacks_on_the_fly(bitboard: Bitboard, blocker: Bitboard) -> Bitboard {
let mut attacks = EMPTY;
let (rank_dst, file_dst) = bitboard_to_coords(bitboard);
let mut add_attacks_in_direction = |mut coord: usize, step: isize, is_vertical: bool| {
while (0..=7).contains(&(coord as isize + step)) {
coord = (coord as isize + step) as usize;
let attack_square = if is_vertical {
square_to_bitboard(coords_to_square(coord, file_dst))
} else {
square_to_bitboard(coords_to_square(rank_dst, coord))
};
attacks |= attack_square;
if have_common_bit(attack_square, blocker) {
break;
}
}
};
add_attacks_in_direction(rank_dst, 1, true);
add_attacks_in_direction(rank_dst, -1, true);
add_attacks_in_direction(file_dst, 1, false);
add_attacks_in_direction(file_dst, -1, false);
attacks
}
pub fn set_occupancy(index: u64, relevant_bits: usize, mut attack_mask: Bitboard) -> Bitboard {
let mut occupancy = EMPTY;
for bit in 0..relevant_bits {
let square = lsb(attack_mask);
attack_mask &= !square_to_bitboard(square);
if have_common_bit(index, square_to_bitboard(bit)) {
occupancy |= square_to_bitboard(square);
}
}
occupancy
}
pub fn fetch_pawn_attacks(square: usize, color: Color) -> Bitboard {
unsafe {
match color {
Color::White => PAWN_ATTACKS[square][0],
Color::Black => PAWN_ATTACKS[square][1],
}
}
}
pub fn fetch_knight_attacks(square: usize) -> Bitboard {
unsafe { KNIGHT_ATTACKS[square] }
}
pub fn fetch_king_attacks(square: usize) -> Bitboard {
unsafe { KING_ATTACKS[square] }
}
use crate::movegen::magic_bitboards::{BISHOP_MAGIC, ROOK_MAGIC};
pub fn fetch_bishop_attacks(mut occupancy: Bitboard, square: usize) -> Bitboard {
unsafe {
occupancy &= mask_bishop_attacks(square_to_bitboard_wrapping(square));
occupancy = occupancy.wrapping_mul(BISHOP_MAGIC[square]);
occupancy >>= 64 - BISHOP_RELEVANT_BITS[square];
BISHOP_ATTACKS[square][occupancy as usize]
}
}
pub fn fetch_rook_attacks(mut occupancy: Bitboard, square: usize) -> Bitboard {
unsafe {
occupancy &= mask_rook_attacks(square_to_bitboard_wrapping(square));
occupancy = occupancy.wrapping_mul(ROOK_MAGIC[square]);
occupancy >>= 64 - ROOK_RELEVANT_BITS[square];
ROOK_ATTACKS[square][occupancy as usize]
}
}
pub fn fetch_queen_attacks(occupancy: Bitboard, square: usize) -> Bitboard {
fetch_rook_attacks(occupancy, square) | fetch_bishop_attacks(occupancy, square)
}
pub fn init_pawn_attacks() {
(0..64).for_each(|square| unsafe {
PAWN_ATTACKS[square][0] = pawn_attacks(square_to_bitboard(square), Color::White);
PAWN_ATTACKS[square][1] = pawn_attacks(square_to_bitboard(square), Color::Black);
});
}
pub fn init_knight_attacks() {
(0..64).for_each(|square| unsafe {
KNIGHT_ATTACKS[square] = knight_attacks(square_to_bitboard(square));
});
}
pub fn init_king_attacks() {
(0..64).for_each(|square| unsafe {
KING_ATTACKS[square] = king_attacks(square_to_bitboard(square));
});
}
pub fn init_bishop_attacks() {
let mut bishop_masks = [0; 64];
for square in 0..64 {
bishop_masks[square] = mask_bishop_attacks(square_to_bitboard(square));
let attack_mask = bishop_masks[square];
let occupancy_indices = square_to_bitboard(BISHOP_RELEVANT_BITS[square]);
for index in 0..occupancy_indices {
let occupancy = set_occupancy(index, BISHOP_RELEVANT_BITS[square], attack_mask);
unsafe {
let magic_index = (occupancy.wrapping_mul(BISHOP_MAGIC[square])
>> (64 - BISHOP_RELEVANT_BITS[square]))
as usize;
BISHOP_ATTACKS[square][magic_index] =
bishop_attacks_on_the_fly(square_to_bitboard(square), occupancy);
}
}
}
}
pub fn init_rook_attacks() {
let mut rook_masks = [0; 64];
for square in 0..64 {
rook_masks[square] = mask_rook_attacks(square_to_bitboard(square));
let attack_mask = rook_masks[square];
let occupancy_indices = square_to_bitboard(ROOK_RELEVANT_BITS[square]);
for idx in 0..occupancy_indices {
let occupancy = set_occupancy(idx, ROOK_RELEVANT_BITS[square], attack_mask);
unsafe {
let magic_index = (occupancy.wrapping_mul(ROOK_MAGIC[square])
>> (64 - ROOK_RELEVANT_BITS[square]))
as usize;
ROOK_ATTACKS[square][magic_index] =
rook_attacks_on_the_fly(square_to_bitboard(square), occupancy);
}
}
}
}
pub fn init_attacks() {
init_pawn_attacks();
init_knight_attacks();
init_king_attacks();
init_bishop_attacks();
init_rook_attacks()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::board::board::{Color, Piece, PieceType};
#[test]
fn test_pawn_attacks() -> Result<(), String> {
let white_pawn_a_file = Piece::new(0x100, PieceType::Pawn, Color::White);
let attacks = pawn_attacks(white_pawn_a_file.bitboard, white_pawn_a_file.color);
assert_eq!(attacks, 0x20000);
let white_pawn_b_file = Piece::new(0x200, PieceType::Pawn, Color::White);
let attacks = pawn_attacks(white_pawn_b_file.bitboard, white_pawn_b_file.color);
assert_eq!(attacks, 0x50000);
let white_pawn_h_file = Piece::new(0x8000, PieceType::Pawn, Color::White);
let attacks = pawn_attacks(white_pawn_h_file.bitboard, white_pawn_h_file.color);
assert_eq!(attacks, 0x400000);
let black_pawn_a_file = Piece::new(0x1000000000000, PieceType::Pawn, Color::Black);
let attacks = pawn_attacks(black_pawn_a_file.bitboard, black_pawn_a_file.color);
assert_eq!(attacks, 0x20000000000);
let black_pawn_b_file = Piece::new(0x2000000000000, PieceType::Pawn, Color::Black);
let attacks = pawn_attacks(black_pawn_b_file.bitboard, black_pawn_b_file.color);
assert_eq!(attacks, 0x50000000000);
let black_pawn_h_file = Piece::new(0x80000000000000, PieceType::Pawn, Color::Black);
let attacks = pawn_attacks(black_pawn_h_file.bitboard, black_pawn_h_file.color);
assert_eq!(attacks, 0x400000000000);
Ok(())
}
#[test]
fn test_knight_attacks() -> Result<(), String> {
let knight_two_attacks = Piece::new(0x1, PieceType::Knight, Color::White);
let attacks = knight_attacks(knight_two_attacks.bitboard);
assert_eq!(attacks, 0x20400);
let knight_three_attacks = Piece::new(0x2, PieceType::Knight, Color::White);
let attacks = knight_attacks(knight_three_attacks.bitboard);
assert_eq!(attacks, 0x50800);
let knight_three_attacks = Piece::new(0x4, PieceType::Knight, Color::White);
let attacks = knight_attacks(knight_three_attacks.bitboard);
assert_eq!(attacks, 0xa1100);
let knight_six_attacks = Piece::new(0x400, PieceType::Knight, Color::White);
let attacks = knight_attacks(knight_six_attacks.bitboard);
assert_eq!(attacks, 0xa110011);
let knight_eight_attacks = Piece::new(0x40000, PieceType::Knight, Color::White);
let attacks = knight_attacks(knight_eight_attacks.bitboard);
assert_eq!(attacks, 0xa1100110a);
Ok(())
}
#[test]
fn test_king_attacks() -> Result<(), String> {
let king_three_attacks = Piece::new(0x1, PieceType::King, Color::White);
let attacks = king_attacks(king_three_attacks.bitboard);
assert_eq!(attacks, 0x302);
let king_five_attacks = Piece::new(0x2, PieceType::King, Color::White);
let attacks = king_attacks(king_five_attacks.bitboard);
assert_eq!(attacks, 0x705);
let king_eight_attacks = Piece::new(0x200, PieceType::King, Color::White);
let attacks = king_attacks(king_eight_attacks.bitboard);
assert_eq!(attacks, 0x70507);
Ok(())
}
#[test]
fn test_mask_bishop_attacks() -> Result<(), String> {
let bishop_d4 = Piece::new(0x8000000, PieceType::Bishop, Color::White);
let all_directions_mask = mask_bishop_attacks(bishop_d4.bitboard);
assert_eq!(all_directions_mask, 0x40221400142200);
Ok(())
}
#[test]
fn test_bishop_attacks_on_the_fly() -> Result<(), String> {
let bishop_d4 = Piece::new(0x8000000, PieceType::Bishop, Color::White);
let blocker_c5 = 0x400000000_u64;
let attacks = bishop_attacks_on_the_fly(bishop_d4.bitboard, blocker_c5);
assert_eq!(attacks, 0x8040201400142241);
let bishop_a1 = Piece::new(0x0, PieceType::Bishop, Color::White);
let blocker_none = 0x0_u64;
let attacks = bishop_attacks_on_the_fly(bishop_a1.bitboard, blocker_none);
assert_eq!(attacks, 0x8040201008040200);
Ok(())
}
#[test]
fn test_mask_rook_attacks() -> Result<(), String> {
let rook_d4 = Piece::new(0x8000000, PieceType::Rook, Color::White);
let all_directions_mask = mask_rook_attacks(rook_d4.bitboard);
assert_eq!(all_directions_mask, 0x8080876080800);
Ok(())
}
#[test]
fn test_rook_attacks_on_the_fly() -> Result<(), String> {
let rook_d4 = Piece::new(0x8000000, PieceType::Rook, Color::White);
let blocker_c4 = 0x4000000_u64;
let attacks = rook_attacks_on_the_fly(rook_d4.bitboard, blocker_c4);
assert_eq!(attacks, 0x8080808f4080808);
Ok(())
}
#[test]
fn test_bishop_attacks() -> Result<(), String> {
init_bishop_attacks();
let bishop_d3_square = 0x80000_u64.trailing_zeros() as usize;
let blockers = 0x602000020;
let attacks = fetch_bishop_attacks(blockers, bishop_d3_square);
assert_eq!(attacks, 0x80402214001422);
Ok(())
}
#[test]
fn test_rook_attacks() -> Result<(), String> {
init_rook_attacks();
let rook_d3_square = 0x80000_u64.trailing_zeros() as usize;
let blockers = 0x800000000600800;
let attacks = fetch_rook_attacks(blockers, rook_d3_square);
assert_eq!(attacks, 0x808080808370800);
Ok(())
}
#[test]
fn test_queen_attacks() -> Result<(), String> {
init_rook_attacks();
init_bishop_attacks();
let queen_d3_square = 0x80000_u64.trailing_zeros() as usize;
let blockers = 0x800000602600820;
let queen_attacks = fetch_queen_attacks(blockers, queen_d3_square);
assert_eq!(queen_attacks, 0x888482a1c371c22);
Ok(())
}
}