From 9876fc046578d8a9e074708638aa0e6671b467da Mon Sep 17 00:00:00 2001 From: stefiosif Date: Wed, 29 May 2024 20:37:15 +0300 Subject: [PATCH] Add piece attack tables and magic bitboards --- Cargo.lock | 68 +++++++ Cargo.toml | 10 + src/attack.rs | 505 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/magic.rs | 187 +++++++++++++++++++ src/main.rs | 2 + 5 files changed, 772 insertions(+) create mode 100644 src/attack.rs create mode 100644 src/magic.rs diff --git a/Cargo.lock b/Cargo.lock index dc00e3d..d321259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,74 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06fddc2749e0528d2813f95e050e87e52c8cbbae56223b9babf73b3e53b0cc6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "ippos" version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index fdc4663..8be0db1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rand = { version = "0.8.5", features = ["small_rng"] } + +[lints.rust] +# unsafe_code = "forbid" + +[lints.clippy] +enum_glob_use = "deny" +#pedantic = "deny" +#nursery = "deny" +#unwrap_used = "deny" \ No newline at end of file diff --git a/src/attack.rs b/src/attack.rs new file mode 100644 index 0000000..b45214d --- /dev/null +++ b/src/attack.rs @@ -0,0 +1,505 @@ +use crate::board::Color; +use u64 as Bitboard; + +const NOT_A_FILE: Bitboard = 0xfefefefefefefefe; +const NOT_AB_FILE: Bitboard = 0xfcfcfcfcfcfcfcfc; +const NOT_H_FILE: Bitboard = 0x7f7f7f7f7f7f7f7f; +const NOT_GH_FILE: Bitboard = 0x3f3f3f3f3f3f3f3f; + +#[rustfmt::skip] +pub const ROOK_RELEVANT_BITS: [u8; 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: [u8; 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]; + +fn pawn_attacks(bitboard: Bitboard, color: Color) -> Bitboard { + let mut attacks = 0_u64; + + match color { + Color::Black => { + attacks |= (bitboard & NOT_H_FILE) >> (7); + attacks |= (bitboard & NOT_A_FILE) >> (9); + } + Color::White => { + attacks |= (bitboard & NOT_A_FILE) << (7); + attacks |= (bitboard & NOT_H_FILE) << (9); + } + }; + + attacks +} + +fn knight_attacks(bitboard: Bitboard) -> Bitboard { + let mut attacks = 0_u64; + + attacks |= (bitboard & NOT_AB_FILE) << (6); + attacks |= (bitboard & NOT_GH_FILE) << (10); + attacks |= (bitboard & NOT_A_FILE) << (15); + attacks |= (bitboard & NOT_H_FILE) << (17); + attacks |= (bitboard & NOT_GH_FILE) >> (6); + attacks |= (bitboard & NOT_AB_FILE) >> (10); + attacks |= (bitboard & NOT_H_FILE) >> (15); + attacks |= (bitboard & NOT_A_FILE) >> (17); + + attacks +} + +fn king_attacks(bitboard: Bitboard) -> Bitboard { + let mut attacks = 0_u64; + + attacks |= (bitboard & NOT_H_FILE) << (1); + attacks |= (bitboard & NOT_A_FILE) << (7); + attacks |= bitboard << (8); + attacks |= (bitboard & NOT_H_FILE) << (9); + attacks |= (bitboard & NOT_A_FILE) >> (1); + attacks |= (bitboard & NOT_H_FILE) >> (7); + attacks |= bitboard >> (8); + attacks |= (bitboard & NOT_A_FILE) >> (9); + + attacks +} + +pub fn mask_bishop_attacks(bitboard: Bitboard) -> Bitboard { + let mut attacks = 0_u64; + let target_rank = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() / 8); + let target_file = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() % 8); + + let (mut rank, mut file) = (target_rank + 1, target_file + 1); + for (rank, file) in (rank..=6).zip(file..=6) { + attacks |= 1_u64 << (rank * 8 + file); + } + + (rank, file) = (target_rank.saturating_sub(1), target_file + 1); + for (rank, file) in (1..=rank).rev().zip(file..=6) { + attacks |= 1_u64 << (rank * 8 + file); + } + + (rank, file) = (target_rank + 1, target_file.saturating_sub(1)); + for (rank, file) in (rank..=6).zip((1..=file).rev()) { + attacks |= 1_u64 << (rank * 8 + file); + } + + (rank, file) = (target_rank.saturating_sub(1), target_file.saturating_sub(1)); + for (rank, file) in (1..=rank).rev().zip((1..=file).rev()) { + attacks |= 1_u64 << (rank * 8 + file); + } + + attacks +} + +pub fn mask_rook_attacks(bitboard: Bitboard) -> Bitboard { + let mut attacks = 0_u64; + let target_rank = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() / 8); + let target_file = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() % 8); + + let (mut rank, mut file) = (target_rank + 1, target_file + 1); + for rank in rank..=6 { + attacks |= 1_u64 << (rank * 8 + target_file); + } + for file in file..=6 { + attacks |= 1_u64 << (target_rank * 8 + file); + } + + (rank, file) = (target_rank.saturating_sub(1), target_file.saturating_sub(1)); + for rank in (1..=rank).rev() { + attacks |= 1_u64 << (rank * 8 + target_file); + } + for file in (1..=file).rev() { + attacks |= 1_u64 << (target_rank * 8 + file); + } + + attacks +} + +pub fn bishop_attacks_on_the_fly(bitboard: Bitboard, blocker: Bitboard) -> Bitboard { + let mut attacks = 0_u64; + let target_rank = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() / 8); + let target_file = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() % 8); + + let (mut rank, mut file) = (target_rank + 1, target_file + 1); + for (rank, file) in (rank..=7).zip(file..=7) { + let attack_square = 1_u64 << (rank * 8 + file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + + (rank, file) = (target_rank.saturating_sub(1), target_file + 1); + for (rank, file) in (0..=rank).rev().zip(file..=7) { + let attack_square = 1_u64 << (rank * 8 + file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + + (rank, file) = (target_rank + 1, target_file.saturating_sub(1)); + for (rank, file) in (rank..=7).zip((0..=file).rev()) { + let attack_square = 1_u64 << (rank * 8 + file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + + (rank, file) = (target_rank.saturating_sub(1), target_file.saturating_sub(1)); + for (rank, file) in (0..=rank).rev().zip((0..=file).rev()) { + let attack_square = 1_u64 << (rank * 8 + file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + + attacks +} + +pub fn rook_attacks_on_the_fly(bitboard: Bitboard, blocker: Bitboard) -> Bitboard { + let mut attacks = 0_u64; + let target_rank = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() / 8); + let target_file = (bitboard == 0) + .then(|| 0) + .unwrap_or_else(|| bitboard.trailing_zeros() % 8); + + let (mut rank, mut file) = (target_rank + 1, target_file + 1); + for rank in rank..=7 { + let attack_square = 1_u64 << (rank * 8 + target_file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + for file in file..=7 { + let attack_square = 1_u64 << (target_rank * 8 + file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + + (rank, file) = (target_rank.saturating_sub(1), target_file.saturating_sub(1)); + + for rank in (0..=rank).rev() { + let attack_square = 1_u64 << (rank * 8 + target_file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + for file in (0..=file).rev() { + let attack_square = 1_u64 << (target_rank * 8 + file); + attacks |= attack_square; + if attack_square & blocker != 0 { + break; + } + } + + attacks +} + +pub fn set_occupancy(index: u64, bits_in_mask: u8, mut attack_mask: Bitboard) -> Bitboard { + let mut occupancy = 0_u64; + + for count in 0..bits_in_mask { + let square = attack_mask.trailing_zeros() as u8; + attack_mask &= !1_u64 << (square); + if index & (1_u64 << (count)) != 0 { + occupancy |= 1_u64 << (square); + } + } + + occupancy +} + +pub fn get_pawn_attacks(sq: usize, color: Color) -> Bitboard { + unsafe { + match color { + Color::White => PAWN_ATTACKS[sq][0], + Color::Black => PAWN_ATTACKS[sq][1], + } + } +} + +pub fn get_knight_attacks(sq: usize) -> Bitboard { + unsafe { KNIGHT_ATTACKS[sq] } +} + +pub fn get_king_attacks(sq: usize) -> Bitboard { + unsafe { KING_ATTACKS[sq] } +} + +use crate::magic::{BISHOP_MAGIC, ROOK_MAGIC}; + +pub fn get_bishop_attacks(mut occupancy: Bitboard, sq: usize) -> Bitboard { + unsafe { + occupancy &= mask_bishop_attacks(1_u64.wrapping_shl(sq as u32)); + occupancy = occupancy.wrapping_mul(BISHOP_MAGIC[sq]); + occupancy >>= 64 - BISHOP_RELEVANT_BITS[sq]; + BISHOP_ATTACKS[sq][occupancy as usize] + } +} + +pub fn get_rook_attacks(mut occupancy: Bitboard, sq: usize) -> Bitboard { + unsafe { + occupancy &= mask_rook_attacks(1_u64.wrapping_shl(sq as u32)); + occupancy = occupancy.wrapping_mul(ROOK_MAGIC[sq]); + occupancy >>= 64 - ROOK_RELEVANT_BITS[sq]; + ROOK_ATTACKS[sq][occupancy as usize] + } +} + +pub fn get_queen_attacks(occupancy: Bitboard, sq: usize) -> Bitboard { + get_rook_attacks(occupancy, sq) | get_bishop_attacks(occupancy, sq) +} + +pub fn init_pawn_attacks() { + (0..64).for_each(|sq| unsafe { + PAWN_ATTACKS[sq][0] = pawn_attacks(1_u64 << (sq), Color::White); + PAWN_ATTACKS[sq][1] = pawn_attacks(1_u64 << (sq), Color::Black); + }); +} + +pub fn init_knight_attacks() { + (0..64).for_each(|sq| unsafe { + KNIGHT_ATTACKS[sq] = knight_attacks(1_u64 << (sq)); + }); +} + +pub fn init_king_attacks() { + (0..64).for_each(|sq| unsafe { + KING_ATTACKS[sq] = king_attacks(1_u64 << (sq)); + }); +} + +pub fn init_bishop_attacks() { + let mut bishop_masks = [0; 64]; + + for sq in 0..64 { + bishop_masks[sq] = mask_bishop_attacks(1_u64 << (sq)); + let attack_mask = bishop_masks[sq]; + let relevant_bits_count = attack_mask.count_ones() as u8; + let occupancy_indices = 1_u64 << (relevant_bits_count); + + for idx in 0..occupancy_indices { + let occupancy = set_occupancy(idx, relevant_bits_count, attack_mask); + unsafe { + let magic_index = + occupancy.wrapping_mul(BISHOP_MAGIC[sq]) >> (64 - BISHOP_RELEVANT_BITS[sq]); + + BISHOP_ATTACKS[sq][magic_index as usize] = + bishop_attacks_on_the_fly(1_u64 << (sq), occupancy); + } + } + } +} + +pub fn init_rook_attacks() { + let mut rook_masks = [0; 64]; + + for sq in 0..64 { + rook_masks[sq] = mask_rook_attacks(1_u64 << (sq)); + let attack_mask = rook_masks[sq]; + let relevant_bits_count = attack_mask.count_ones() as u8; + let occupancy_indices = 1_u64 << (relevant_bits_count); + + for idx in 0..occupancy_indices { + let occupancy = set_occupancy(idx, relevant_bits_count, attack_mask); + unsafe { + let magic_index = + occupancy.wrapping_mul(ROOK_MAGIC[sq]) >> (64 - ROOK_RELEVANT_BITS[sq]); + + ROOK_ATTACKS[sq][magic_index as usize] = + rook_attacks_on_the_fly(1_u64 << (sq), occupancy); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::board::{Color, Kind, Piece}; + + #[test] + fn test_pawn_attacks() -> Result<(), String> { + let white_pawn_a_file = Piece::new(0x100, Kind::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, Kind::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, Kind::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, Kind::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, Kind::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, Kind::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, Kind::Knight, Color::White); + let attacks = knight_attacks(knight_two_attacks.bitboard); + assert_eq!(attacks, 0x20400); + + let knight_three_attacks = Piece::new(0x2, Kind::Knight, Color::White); + let attacks = knight_attacks(knight_three_attacks.bitboard); + assert_eq!(attacks, 0x50800); + + let knight_three_attacks = Piece::new(0x4, Kind::Knight, Color::White); + let attacks = knight_attacks(knight_three_attacks.bitboard); + assert_eq!(attacks, 0xa1100); + + let knight_six_attacks = Piece::new(0x400, Kind::Knight, Color::White); + let attacks = knight_attacks(knight_six_attacks.bitboard); + assert_eq!(attacks, 0xa110011); + + let knight_eight_attacks = Piece::new(0x40000, Kind::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, Kind::King, Color::White); + let attacks = king_attacks(king_three_attacks.bitboard); + assert_eq!(attacks, 0x302); + + let king_five_attacks = Piece::new(0x2, Kind::King, Color::White); + let attacks = king_attacks(king_five_attacks.bitboard); + assert_eq!(attacks, 0x705); + + let king_eight_attacks = Piece::new(0x200, Kind::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, Kind::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, Kind::Bishop, Color::White); + let blocker_c5 = 0x400000000_u64; + let attacks = bishop_attacks_on_the_fly(bishop_d4.bitboard, blocker_c5); + assert_eq!(attacks, 0x8040201400142241); + + Ok(()) + } + + #[test] + fn test_mask_rook_attacks() -> Result<(), String> { + let rook_d4 = Piece::new(0x8000000, Kind::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, Kind::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 = get_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 = get_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 = get_queen_attacks(blockers, queen_d3_square); + assert_eq!(queen_attacks, 0x888482a1c371c22); + + Ok(()) + } +} diff --git a/src/magic.rs b/src/magic.rs new file mode 100644 index 0000000..7ff971b --- /dev/null +++ b/src/magic.rs @@ -0,0 +1,187 @@ +use crate::attack::{ + bishop_attacks_on_the_fly, mask_bishop_attacks, mask_rook_attacks, rook_attacks_on_the_fly, + set_occupancy, BISHOP_RELEVANT_BITS, ROOK_RELEVANT_BITS, +}; +use rand::rngs::SmallRng; +use rand::{RngCore, SeedableRng}; +use u64 as Bitboard; + +static mut BISHOP_MAGIC_INIT: [Bitboard; 64] = [0; 64]; +static mut ROOK_MAGIC_INIT: [Bitboard; 64] = [0; 64]; + +#[rustfmt::skip] +pub const ROOK_MAGIC: [u64; 64] = [ + 0x80008010284000, 0x540024820001000, 0x1300092000124100, 0x480240800500080, + 0x1001004020081080, 0x500040021000208, 0x6280010000800200, 0x8006a100104180, + 0x420800080204000, 0x2802000804008, 0x101805000600080, 0x1000801000080082, + 0x102808044000800, 0x280800200040080, 0x1003000100040200, 0xd201000092005100, + 0x80004000200041, 0x8400810040002100, 0x210048020008010, 0x220042001008, + 0x20850011000800, 0x80808004000200, 0x100c440008424110, 0x2020010885904, + 0x120400080002080, 0x40400100210888, 0x4860080040100041, 0x10100080080084, + 0x8008080080400, 0x124008080020004, 0x100010400021008, 0x820008200004401, + 0x404000a2800080, 0x802000804008, 0x8040809002802004, 0x1000801000800800, + 0x40080800800, 0x202800200800400, 0x100204000108, 0x48010c44020000b1, + 0x800040008024, 0x402010004000, 0x4209002000110040, 0x12002040aa0050, + 0x2002008260010, 0x80a001008020004, 0x701000a00050004, 0x241a040080420001, + 0x50043a0800100, 0x8021c002a0148080, 0x200010008080, 0x8080410008080, + 0x5180080004110100, 0x1164000402008080, 0x1002800100020080, 0x4100840200, + 0x82829101412206, 0x2209004000801021, 0x84010802202, 0x1a208900100005, + 0x1001002880005, 0x9008204000841, 0x2c9508221014, 0x8010470400402092, +]; + +#[rustfmt::skip] +pub const BISHOP_MAGIC: [u64; 64] = [ + 0x10041000802d00, 0x3410820e4100b0, 0x1008080120200000, 0x4080a0020104000, + 0x14152000000050, 0x1010841202844, 0x80480210100208, 0x8042410050022000, + 0x2400204810a00, 0x1003020204010e, 0xa012044104010800, 0x804280a042108c2, + 0x11040010810, 0x2010228820298000, 0x21440100c210400c, 0x910002104108414, + 0x249012002102220, 0x210000421082108, 0xa001001083040100, 0x880c007801441100, + 0xe00082ac00a01800, 0x40020004a2032001, 0x82aa016048040580, 0x40205301291000, + 0x1060110004100200, 0x8002035010040800, 0x8401001a080508, 0xc0044004050020, + 0x409010080104008, 0x28029232004400, 0x4008888002121000, 0x8041220041004118, + 0x8018241008404200, 0x108021820348e, 0x108c0104100040, 0x8320080800460a00, + 0x10008200802200, 0x284208080980800, 0x42020402122080, 0x9702004040410400, + 0x1092070012122, 0x2214024619001121, 0x491420140208d000, 0x7460009414003802, + 0x4022084104000040, 0x1084248085020200, 0x8808102400602480, 0x120410a400428508, + 0x4014808050804, 0x408406804101008, 0x41081044d500031, 0x421080284044020, + 0x4060188813041110, 0x40202041024080, 0xa0212002108100a4, 0x3020202020480, + 0x1a802808062882, 0x80110101012100, 0xa40100200420890, 0x48025100208801, + 0x4000000004208200, 0x804a10011602, 0x200a24c410041500, 0x8408080088061020, +]; + +pub fn random_uint64(state: &mut SmallRng) -> u64 { + let n1 = state.next_u32() as u64 & 0xFFFF; + let n2 = state.next_u32() as u64 & 0xFFFF; + let n3 = state.next_u32() as u64 & 0xFFFF; + let n4 = state.next_u32() as u64 & 0xFFFF; + + n1 | (n2 << (16)) | (n3 << (32)) | (n4 << (48)) +} + +pub fn generate_magic_number_candidate(state: &mut SmallRng) -> u64 { + random_uint64(state) & random_uint64(state) & random_uint64(state) +} + +pub fn find_rook_magic_numbers(relevant_bits: u8, square: Bitboard, state: &mut SmallRng) -> u64 { + let mut occupancies: [u64; 4096] = [0; 4096]; + let mut attacks: [u64; 4096] = [0; 4096]; + + let attack_mask = mask_rook_attacks(square); + let occupancy_indices = 1_u64 << (relevant_bits); + + for index in 0..occupancy_indices { + occupancies[index as usize] = + set_occupancy(index, attack_mask.count_ones() as u8, attack_mask); + attacks[index as usize] = rook_attacks_on_the_fly(square, occupancies[index as usize]); + } + + for _ in 0..100_000_000 { + let magic_number = generate_magic_number_candidate(state); + if 6 > (attack_mask.wrapping_mul(magic_number) & 0xFF00000000000000_u64).count_ones() { + continue; + } + let mut used_attacks = [0; 4096]; + + let mut fail = false; + for idx in 0..occupancy_indices { + let magic_idx = (occupancies[idx as usize].wrapping_mul(magic_number) + >> ((64 - relevant_bits) as u32)) as usize; + + if used_attacks[magic_idx] == 0_u64 { + used_attacks[magic_idx] = attacks[idx as usize]; + } else if used_attacks[magic_idx] != attacks[idx as usize] { + fail = true; + break; + } + } + + if !fail { + return magic_number; + } + } + + 0_u64 +} + +pub fn find_bishop_magic_numbers(relevant_bits: u8, square: Bitboard, state: &mut SmallRng) -> u64 { + let mut occupancies: [u64; 4096] = [0; 4096]; + let mut attacks: [u64; 4096] = [0; 4096]; + + let attack_mask = mask_bishop_attacks(square); + let occupancy_indices = 1_u64 << (relevant_bits); + + for index in 0..occupancy_indices { + occupancies[index as usize] = + set_occupancy(index, attack_mask.count_ones() as u8, attack_mask); + attacks[index as usize] = bishop_attacks_on_the_fly(square, occupancies[index as usize]); + } + + for _ in 0..100_000_000 { + let magic_number = generate_magic_number_candidate(state); + if 6 > (attack_mask.wrapping_mul(magic_number) & 0xFF00000000000000_u64).count_ones() { + continue; + } + let mut used_attacks = [0; 4096]; + + let mut fail = false; + for idx in 0..occupancy_indices { + let magic_idx = (occupancies[idx as usize].wrapping_mul(magic_number) + >> ((64 - relevant_bits) as u32)) as usize; + + if used_attacks[magic_idx] == 0_u64 { + used_attacks[magic_idx] = attacks[idx as usize]; + } else if used_attacks[magic_idx] != attacks[idx as usize] { + fail = true; + break; + } + } + + if !fail { + return magic_number; + } + } + + 0_u64 +} + +pub fn init_magic_arrays() { + let mut state = SmallRng::seed_from_u64(1804289383); + + for idx in 0..64 { + let rook_magic_number = + find_rook_magic_numbers(ROOK_RELEVANT_BITS[idx], 1_u64 << (idx), &mut state); + println!("0x{:x},", rook_magic_number); + unsafe { + ROOK_MAGIC_INIT[idx] = rook_magic_number; + } + } + println!("\n\n"); + for idx in 0..64 { + let bishop_magic_number = + find_bishop_magic_numbers(BISHOP_RELEVANT_BITS[idx], 1_u64 << (idx), &mut state); + println!("0x{:x},", bishop_magic_number); + unsafe { + BISHOP_MAGIC_INIT[idx] = bishop_magic_number; + } + } +} + +#[cfg(test)] +mod tests { + use super::init_magic_arrays; + use crate::magic::{BISHOP_MAGIC, BISHOP_MAGIC_INIT, ROOK_MAGIC, ROOK_MAGIC_INIT}; + + #[test] + fn test_init_magic_arrays() -> Result<(), String> { + init_magic_arrays(); + + for idx in 0..64 { + unsafe { + assert_eq!(ROOK_MAGIC[idx], ROOK_MAGIC_INIT[idx]); + assert_eq!(BISHOP_MAGIC[idx], BISHOP_MAGIC_INIT[idx]); + } + } + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index ba43516..8272d91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ +pub mod attack; pub mod board; pub mod fen; pub mod game; +pub mod magic; pub mod r#move; use game::Game;