diff --git a/src/movegen/moves.rs b/src/movegen/moves.rs index 7e40a18..9840083 100644 --- a/src/movegen/moves.rs +++ b/src/movegen/moves.rs @@ -4,25 +4,35 @@ use crate::{ board::{Bitboard, Color, File, Square}, movegen::{ naive, - wizardry::{generate_bishop_magics, generate_rook_magics, MagicMoves, RandGen}, + wizardry::{ + generate_bishop_magics, generate_rook_magics, MagicMoves, RandGen, BISHOP_SEED, + ROOK_SEED, + }, }, }; -// A simple XOR-shift RNG implementation. -struct SimpleRng(u64); +// A pre-rolled RNG for magic bitboard generation, using pre-determined values. +struct PreRolledRng { + numbers: [u64; 64], + current_index: usize, +} -impl SimpleRng { - pub fn new() -> Self { - Self(4) // https://xkcd.com/221/ +impl PreRolledRng { + pub fn new(numbers: [u64; 64]) -> Self { + Self { + numbers, + current_index: 0, + } } } -impl RandGen for SimpleRng { +impl RandGen for PreRolledRng { fn gen(&mut self) -> u64 { - self.0 ^= self.0 >> 12; - self.0 ^= self.0 << 25; - self.0 ^= self.0 >> 27; - self.0 + // We roll 3 numbers per square to bitwise-and them together. + // Just return the same one 3 times as a work-around. + let res = self.numbers[self.current_index / 3]; + self.current_index += 1; + res } } @@ -86,7 +96,7 @@ pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard { static BISHOP_MAGICS: OnceLock = OnceLock::new(); BISHOP_MAGICS .get_or_init(|| { - let (magics, moves) = generate_bishop_magics(&mut SimpleRng::new()); + let (magics, moves) = generate_bishop_magics(&mut PreRolledRng::new(BISHOP_SEED)); // SAFETY: we used the generator function to compute these values unsafe { MagicMoves::new(magics, moves) } }) @@ -98,7 +108,7 @@ pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard { static ROOK_MAGICS: OnceLock = OnceLock::new(); ROOK_MAGICS .get_or_init(|| { - let (magics, moves) = generate_rook_magics(&mut SimpleRng::new()); + let (magics, moves) = generate_rook_magics(&mut PreRolledRng::new(ROOK_SEED)); // SAFETY: we used the generator function to compute these values unsafe { MagicMoves::new(magics, moves) } }) diff --git a/src/movegen/wizardry/mod.rs b/src/movegen/wizardry/mod.rs index 83f4d69..00645d8 100644 --- a/src/movegen/wizardry/mod.rs +++ b/src/movegen/wizardry/mod.rs @@ -56,3 +56,228 @@ impl MagicMoves { } } } + +// region:sourcegen +/// A set of magic numbers for bishop move generation. +pub(crate) const BISHOP_SEED: [u64; 64] = [ + 4908958787341189172, + 1157496606860279808, + 289395876198088778, + 649648646467355137, + 19162426089930848, + 564067194896448, + 18586170375029026, + 9185354800693760, + 72172012436987968, + 317226351607872, + 2597178509285688384, + 1162205282238464, + 144154788211329152, + 172197832046936160, + 4625762105940000802, + 1477217245166903296, + 2251937789583872, + 289373902621379585, + 4616200855845409024, + 2251909637357568, + 3532510975437640064, + 563517968228352, + 562953309660434, + 1196005458310201856, + 2350914225914520576, + 2287018679861376, + 13836188353273790593, + 11267795163676832, + 297519119119499264, + 18588344158519552, + 10453428171813953792, + 72128237668534272, + 1298164929055953920, + 865575144395900952, + 9293076573325312, + 108104018148197376, + 578503662094123152, + 4665870505495102224, + 6066493872259301520, + 285877477613857, + 2328941618281318466, + 721165292771739652, + 4899973577790523400, + 75050392749184, + 2305878200632215680, + 11530099074925593616, + 290561512873919880, + 18652187227888000, + 3379933716168704, + 9223409493537718272, + 22273835729926, + 1152921524003672064, + 4647812741240848385, + 1244225087719112712, + 7367907171013001728, + 9263922034316951570, + 300758214358598160, + 4611686331973636096, + 2377900605806479360, + 6958097192913601024, + 864691130877743617, + 703824948904066, + 612700674899317536, + 180742128018784384, +]; + +/// A set of magic numbers for rook move generation. +pub(crate) const ROOK_SEED: [u64; 64] = [ + 2341871943948451840, + 18015635528220736, + 72066665545773824, + 1188959097794342912, + 12141713393631625314, + 720649693658353672, + 36029896538981888, + 36033359356363520, + 140746619355268, + 1158339898446446661, + 36591886560003650, + 578853633228023808, + 2392554490300416, + 140814806160384, + 180706952366596608, + 10696087878779396, + 1153260703948210820, + 310748649170673678, + 36311372044308544, + 9223444604757615104, + 1267187285230592, + 282574622818306, + 18722484274726152, + 2271591090110593, + 1153063519847989248, + 10168327557107712, + 4507998211276833, + 1153203035420233728, + 4631961017139660032, + 2454499182462107776, + 289367288355753288, + 18015815850820609, + 9268726066908758912, + 11547264697673728000, + 2314929519368081536, + 140943655192577, + 20266215511427202, + 180706969441535248, + 1302683805944911874, + 11534000122299940994, + 22676602724843520, + 4639271120198041668, + 1302104069046927376, + 9184220895313928, + 4612249105954373649, + 562984581726212, + 2312678200579457040, + 4647736876550193157, + 3170604524138139776, + 4684447574787096704, + 20283792725901696, + 1152992019380963840, + 117383863558471808, + 1153488854922068096, + 17596884583424, + 90074759127192064, + 4900502436426416706, + 4573968656793901, + 1161084564408385, + 1657887889314811910, + 4614501455660058690, + 4612530729109422081, + 642458506527236, + 1116704154754, +]; +// endregion:sourcegen + +#[cfg(test)] +mod test { + use super::*; + + // A simple XOR-shift RNG implementation. + struct SimpleRng(u64); + + impl SimpleRng { + pub fn new() -> Self { + Self(4) // https://xkcd.com/221/ + } + } + + impl RandGen for SimpleRng { + fn gen(&mut self) -> u64 { + self.0 ^= self.0 >> 12; + self.0 ^= self.0 << 25; + self.0 ^= self.0 >> 27; + self.0 + } + } + + #[test] + fn rng() { + let mut rng = SimpleRng::new(); + + assert_eq!(rng.gen(), 134217733); + assert_eq!(rng.gen(), 4504699139039237); + assert_eq!(rng.gen(), 13512173405898766); + assert_eq!(rng.gen(), 9225626310854853124); + assert_eq!(rng.gen(), 29836777971867270); + } + + fn split_twice<'a>( + text: &'a str, + start_marker: &str, + end_marker: &str, + ) -> Option<(&'a str, &'a str, &'a str)> { + let (prefix, rest) = text.split_once(start_marker)?; + let (mid, suffix) = rest.split_once(end_marker)?; + Some((prefix, mid, suffix)) + } + + fn array_string(piece_type: &str, values: &[Magic]) -> String { + let mut res = format!( + "/// A set of magic numbers for {} move generation.\n", + piece_type + ); + res.push_str(&format!( + "pub(crate) const {}_SEED: [u64; 64] = [\n", + piece_type.to_uppercase() + )); + for magic in values { + res.push_str(&format!(" {},\n", magic.magic)); + } + res.push_str("];\n"); + res + } + + #[test] + #[ignore = "slow"] + // Regenerates the magic bitboard numbers. + fn regen_magic_seeds() { + // We only care about the magics, the moves can be recomputed at runtime ~cheaply. + let (bishop_magics, _) = generate_bishop_magics(&mut SimpleRng::new()); + let (rook_magics, _) = generate_rook_magics(&mut SimpleRng::new()); + + let original_text = std::fs::read_to_string(file!()).unwrap(); + + let bishop_array = array_string("bishop", &bishop_magics[..]); + let rook_array = array_string("rook", &rook_magics[..]); + + let new_text = { + let start_marker = "// region:sourcegen\n"; + let end_marker = "// endregion:sourcegen\n"; + let (prefix, _, suffix) = + split_twice(&original_text, start_marker, end_marker).unwrap(); + format!("{prefix}{start_marker}{bishop_array}\n{rook_array}{end_marker}{suffix}") + }; + + if new_text != original_text { + std::fs::write(file!(), new_text).unwrap(); + panic!("source was not up-to-date") + } + } +}