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(()) } }