From 1204427d4e179b9037c6d2be1980c2d83b7042d4 Mon Sep 17 00:00:00 2001 From: Bruno Mendes Date: Sat, 7 Dec 2024 21:20:11 +0000 Subject: [PATCH] Port magics --- src/moves/gen/leapers.rs | 2 +- src/moves/gen/magics.rs | 181 +++++++++++++++++++++++++++++++++++++++ src/moves/gen/mod.rs | 1 + src/position/bitboard.rs | 4 + 4 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/moves/gen/magics.rs diff --git a/src/moves/gen/leapers.rs b/src/moves/gen/leapers.rs index 92009b68..0f6a9f30 100644 --- a/src/moves/gen/leapers.rs +++ b/src/moves/gen/leapers.rs @@ -13,7 +13,7 @@ use super::MoveStage; pub type LeaperAttackMap = [Bitboard; 64]; pub const fn init_leaper_attacks(move_directions: &[Direction]) -> LeaperAttackMap { - let mut attacks: LeaperAttackMap = [Bitboard::new(0); 64]; + let mut attacks: LeaperAttackMap = [Bitboard::empty(); 64]; let mut square = 0; while square < 64 { diff --git a/src/moves/gen/magics.rs b/src/moves/gen/magics.rs new file mode 100644 index 00000000..571d1a40 --- /dev/null +++ b/src/moves/gen/magics.rs @@ -0,0 +1,181 @@ +use super::sliders::{slider_attacks_from_square, BISHOP_MOVE_DIRECTIONS, ROOK_MOVE_DIRECTIONS}; +use crate::position::{bitboard::Bitboard, piece::Piece, square::Square}; +use ctor::ctor; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::thread; + +#[ctor] +static BISHOP_MAGICS: [SquareMagic; 64] = find_magics(Piece::Bishop); + +#[ctor] +static ROOK_MAGICS: [SquareMagic; 64] = find_magics(Piece::Rook); + +#[derive(Debug)] +struct SquareMagic { + shift: u8, + mask: u64, + magic: u64, + attacks: Vec, +} + +fn bitsets(bitboard: Bitboard) -> Vec { + let bitboard = bitboard.raw(); + let mut bitsets = Vec::new(); + let mut current_bb = 0; + + loop { + bitsets.push(Bitboard::new(current_bb)); + current_bb = (current_bb.wrapping_sub(bitboard)) & bitboard; + if current_bb == 0 { + break; + } + } + + bitsets +} + +fn sparse_random(seed: u64) -> u64 { + let mut rng = StdRng::seed_from_u64(seed); + let r1 = rng.gen::(); + let r2 = rng.gen::(); + let r3 = rng.gen::(); + r1 & r2 & r3 +} + +fn magic_index(occupancy: Bitboard, magic: &SquareMagic) -> usize { + let occupancy = occupancy.raw() & magic.mask; + let index = (occupancy.wrapping_mul(magic.magic)) >> (64 - magic.shift); + index as usize +} + +fn find_magic(square: Square, piece: Piece) -> SquareMagic { + let directions = match piece { + Piece::Bishop => &BISHOP_MOVE_DIRECTIONS, + Piece::Rook => &ROOK_MOVE_DIRECTIONS, + _ => panic!("There are no magics for that piece."), + }; + let blockers_mask = slider_attacks_from_square(square, directions, Bitboard::empty(), true); + let shift = blockers_mask.count_ones() as u8; + + let occ_move_map = bitsets(blockers_mask) + .iter() + .map(|b| { + ( + *b, + slider_attacks_from_square(square, directions, *b, false), + ) + }) + .collect::>(); + + let mut magic_tentative = SquareMagic { + shift, + mask: blockers_mask.raw(), + magic: 0, + attacks: vec![Bitboard::empty(); 1 << shift], + }; + + for seed in 0.. { + magic_tentative.magic = sparse_random(seed); + let mut used = [false; 4096]; + let mut found_collision = false; + + for (occupancy, moves) in &occ_move_map { + let index = magic_index(*occupancy, &magic_tentative); + if used[index] && magic_tentative.attacks[index] != *moves { + found_collision = true; + break; + } + magic_tentative.attacks[index] = *moves; + used[index] = true; + } + + if !found_collision { + let largest_used_index = used.iter().rposition(|&used| used).unwrap(); + magic_tentative + .attacks + .resize(largest_used_index + 1, Bitboard::empty()); + return magic_tentative; + } + } + + panic!("Could not find any valid magic using u64 seeds."); +} + +fn find_magics(piece: Piece) -> [SquareMagic; 64] { + (0..64) + .map(|square| thread::spawn(move || find_magic(Square::from(square).unwrap(), piece))) + .collect::>() + .into_iter() + .map(|h| h.join().unwrap()) + .collect::>() + .try_into() + .unwrap() +} + +#[cfg(test)] +mod tests { + use crate::{ + moves::gen::{ + magics::magic_index, + sliders::{slider_attacks_from_square, BISHOP_MOVE_DIRECTIONS, ROOK_MOVE_DIRECTIONS}, + }, + position::{bitboard::Bitboard, piece::Piece, square::Square}, + }; + + use super::{bitsets, find_magic}; + + fn test_magics(piece: Piece) { + let directions = match piece { + Piece::Rook => &ROOK_MOVE_DIRECTIONS, + Piece::Bishop => &BISHOP_MOVE_DIRECTIONS, + _ => panic!("Invalid piece"), + }; + + for square in Square::list() { + let magic = find_magic(*square, piece); + + let blockers_mask = + slider_attacks_from_square(*square, directions, Bitboard::empty(), true); + let bitsets = bitsets(blockers_mask); + + for bitset in bitsets { + let index = magic_index(bitset, &magic); + assert_eq!( + magic.attacks[index], + slider_attacks_from_square(*square, directions, bitset, false) + ); + } + } + } + + #[test] + fn bitsets_simple() { + let bitboard = Bitboard::new(0b11001); + let bitsets = bitsets(bitboard); + + let expected_bitsets = [ + Bitboard::new(0b0), + Bitboard::new(0b1), + Bitboard::new(0b1000), + Bitboard::new(0b1001), + Bitboard::new(0b10000), + Bitboard::new(0b10001), + Bitboard::new(0b11000), + Bitboard::new(0b11001), + ]; + + for bitset in bitsets { + assert!(expected_bitsets.contains(&bitset)); + } + } + + #[test] + fn rook_magics() { + test_magics(Piece::Rook); + } + + #[test] + fn bishop_magics() { + test_magics(Piece::Bishop); + } +} diff --git a/src/moves/gen/mod.rs b/src/moves/gen/mod.rs index b06d91f3..cb21ea15 100644 --- a/src/moves/gen/mod.rs +++ b/src/moves/gen/mod.rs @@ -4,6 +4,7 @@ use leapers::{king_regular_moves, knight_moves}; use pawns::{pawn_attackers, pawn_moves}; mod leapers; +mod magics; mod pawns; mod sliders; diff --git a/src/position/bitboard.rs b/src/position/bitboard.rs index b56ac3c4..a3b5561d 100644 --- a/src/position/bitboard.rs +++ b/src/position/bitboard.rs @@ -50,6 +50,10 @@ impl Bitboard { Bitboard(data) } + pub const fn raw(&self) -> u64 { + self.0 + } + pub const fn from_square(square: Square) -> Self { Bitboard(FROM_SQUARE[square as usize]) }