From 55b6dc72bb737628e0ab9fa83abc54741a36fb7a Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Jul 2022 13:40:01 +0200 Subject: [PATCH 1/5] Generate magic tables with build script --- Cargo.toml | 11 +++++ src/build.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 src/build.rs diff --git a/Cargo.toml b/Cargo.toml index 52a2217..756e025 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,19 @@ name = "seer" version = "0.1.0" edition = "2021" +build = "src/build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] random = "0.12.2" + +[build-dependencies] +random = "0.12.2" + +# Optimize build scripts to shorten compile times. +[profile.dev.build-override] +opt-level = 3 + +[profile.release.build-override] +opt-level = 3 diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..29721d1 --- /dev/null +++ b/src/build.rs @@ -0,0 +1,133 @@ +use std::io::{Result, Write}; + +pub mod board; +pub mod movegen; +pub mod utils; + +use crate::{ + board::{Bitboard, Color, File, Square}, + movegen::{ + king::king_moves, + knight::knight_moves, + pawn::{pawn_captures, pawn_moves}, + wizardry::generation::{generate_bishop_magics, generate_rook_magics}, + Magic, + }, +}; + +fn print_magics(out: &mut dyn Write, var_name: &str, magics: &[Magic]) -> Result<()> { + writeln!(out, "static {}: [Magic; {}] = [", var_name, magics.len())?; + for magic in magics.iter() { + writeln!( + out, + " Magic{{magic: {}, offset: {}, mask: Bitboard({}), shift: {},}},", + magic.magic, magic.offset, magic.mask.0, magic.shift + )?; + } + writeln!(out, "];")?; + Ok(()) +} + +fn print_boards(out: &mut dyn Write, var_name: &str, boards: &[Bitboard]) -> Result<()> { + writeln!(out, "static {}: [Bitboard; {}] = [", var_name, boards.len())?; + for board in boards.iter().cloned() { + writeln!(out, " Bitboard({}),", board.0)?; + } + writeln!(out, "];")?; + Ok(()) +} + +fn print_double_sided_boards( + out: &mut dyn Write, + var_name: &str, + white_boards: &[Bitboard], + black_boards: &[Bitboard], +) -> Result<()> { + assert_eq!(white_boards.len(), black_boards.len()); + writeln!( + out, + "static {}: [[Bitboard; {}]; 2] = [", + var_name, + white_boards.len() + )?; + for color in Color::iter() { + let boards = if color == Color::White { + white_boards + } else { + black_boards + }; + writeln!(out, " [")?; + for square in Square::iter() { + writeln!(out, " Bitboard({}),", boards[square.index()].0)?; + } + writeln!(out, " ],")?; + } + writeln!(out, "];")?; + Ok(()) +} + +#[allow(clippy::redundant_clone)] +fn main() -> Result<()> { + // FIXME: rerun-if-changed directives + + let out_dir = std::env::var_os("OUT_DIR").unwrap(); + let magic_path = std::path::Path::new(&out_dir).join("magic_tables.rs"); + let mut out = std::fs::File::create(&magic_path).unwrap(); + + let rng = random::default().seed([12, 27]); + + { + let (magics, moves) = generate_bishop_magics(&mut rng.clone()); + print_magics(&mut out, "BISHOP_MAGICS", &magics)?; + print_boards(&mut out, "BISHOP_MOVES", &moves)?; + } + + { + let (magics, moves) = generate_rook_magics(&mut rng.clone()); + print_magics(&mut out, "ROOK_MAGICS", &magics)?; + print_boards(&mut out, "ROOK_MOVES", &moves)?; + } + + { + let moves: Vec<_> = Square::iter().map(knight_moves).collect(); + print_boards(&mut out, "KNIGHT_MOVES", &moves)?; + } + + { + let white_moves: Vec<_> = Square::iter() + .map(|square| pawn_moves(Color::White, square, Bitboard::EMPTY)) + .collect(); + let black_moves: Vec<_> = Square::iter() + .map(|square| pawn_moves(Color::Black, square, Bitboard::EMPTY)) + .collect(); + print_double_sided_boards(&mut out, "PAWN_MOVES", &white_moves, &black_moves)?; + let white_attacks: Vec<_> = Square::iter() + .map(|square| pawn_captures(Color::White, square)) + .collect(); + let black_attacks: Vec<_> = Square::iter() + .map(|square| pawn_captures(Color::Black, square)) + .collect(); + print_double_sided_boards(&mut out, "PAWN_ATTACKS", &white_attacks, &black_attacks)?; + } + + { + let moves: Vec<_> = Square::iter().map(king_moves).collect(); + print_boards(&mut out, "KING_MOVES", &moves)?; + let king_blockers: Vec<_> = Color::iter() + .map(|color| { + Square::new(File::F, color.first_rank()) | Square::new(File::G, color.first_rank()) + }) + .collect(); + let queen_blockers: Vec<_> = Color::iter() + .map(|color| { + Square::new(File::B, color.first_rank()) + | Square::new(File::C, color.first_rank()) + | Square::new(File::D, color.first_rank()) + }) + .collect(); + print_boards(&mut out, "KING_SIDE_CASTLE_BLOCKERS", &king_blockers)?; + print_boards(&mut out, "QUEEN_SIDE_CASTLE_BLOCKERS", &queen_blockers)?; + } + + Ok(()) +} From bf4c34554c579d54a3acfc1eb733411b99a366b5 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Jul 2022 16:24:35 +0200 Subject: [PATCH 2/5] Move naive move generation into sub-module --- src/build.rs | 8 +++++--- src/movegen/mod.rs | 8 ++------ src/movegen/{ => naive}/bishop.rs | 0 src/movegen/{ => naive}/king.rs | 0 src/movegen/{ => naive}/knight.rs | 0 src/movegen/naive/mod.rs | 5 +++++ src/movegen/{ => naive}/pawn.rs | 0 src/movegen/{ => naive}/rook.rs | 0 src/movegen/wizardry/generation.rs | 3 +-- src/movegen/wizardry/mask.rs | 3 +-- 10 files changed, 14 insertions(+), 13 deletions(-) rename src/movegen/{ => naive}/bishop.rs (100%) rename src/movegen/{ => naive}/king.rs (100%) rename src/movegen/{ => naive}/knight.rs (100%) create mode 100644 src/movegen/naive/mod.rs rename src/movegen/{ => naive}/pawn.rs (100%) rename src/movegen/{ => naive}/rook.rs (100%) diff --git a/src/build.rs b/src/build.rs index 29721d1..e05410d 100644 --- a/src/build.rs +++ b/src/build.rs @@ -7,9 +7,11 @@ pub mod utils; use crate::{ board::{Bitboard, Color, File, Square}, movegen::{ - king::king_moves, - knight::knight_moves, - pawn::{pawn_captures, pawn_moves}, + naive::{ + king::king_moves, + knight::knight_moves, + pawn::{pawn_captures, pawn_moves}, + }, wizardry::generation::{generate_bishop_magics, generate_rook_magics}, Magic, }, diff --git a/src/movegen/mod.rs b/src/movegen/mod.rs index 26b60a3..9ddbf36 100644 --- a/src/movegen/mod.rs +++ b/src/movegen/mod.rs @@ -2,12 +2,8 @@ pub mod magic; pub use magic::*; -// Move generation implementation details -pub(crate) mod bishop; -pub(crate) mod king; -pub(crate) mod knight; -pub(crate) mod pawn; -pub(crate) mod rook; +// Naive move generation +pub mod naive; // Magic bitboard generation pub(crate) mod wizardry; diff --git a/src/movegen/bishop.rs b/src/movegen/naive/bishop.rs similarity index 100% rename from src/movegen/bishop.rs rename to src/movegen/naive/bishop.rs diff --git a/src/movegen/king.rs b/src/movegen/naive/king.rs similarity index 100% rename from src/movegen/king.rs rename to src/movegen/naive/king.rs diff --git a/src/movegen/knight.rs b/src/movegen/naive/knight.rs similarity index 100% rename from src/movegen/knight.rs rename to src/movegen/naive/knight.rs diff --git a/src/movegen/naive/mod.rs b/src/movegen/naive/mod.rs new file mode 100644 index 0000000..f1bbe3e --- /dev/null +++ b/src/movegen/naive/mod.rs @@ -0,0 +1,5 @@ +pub mod bishop; +pub mod king; +pub mod knight; +pub mod pawn; +pub mod rook; diff --git a/src/movegen/pawn.rs b/src/movegen/naive/pawn.rs similarity index 100% rename from src/movegen/pawn.rs rename to src/movegen/naive/pawn.rs diff --git a/src/movegen/rook.rs b/src/movegen/naive/rook.rs similarity index 100% rename from src/movegen/rook.rs rename to src/movegen/naive/rook.rs diff --git a/src/movegen/wizardry/generation.rs b/src/movegen/wizardry/generation.rs index ee75d57..173eef0 100644 --- a/src/movegen/wizardry/generation.rs +++ b/src/movegen/wizardry/generation.rs @@ -1,6 +1,5 @@ use crate::board::{Bitboard, Square}; -use crate::movegen::bishop::bishop_moves; -use crate::movegen::rook::rook_moves; +use crate::movegen::naive::{bishop::bishop_moves, rook::rook_moves}; use crate::movegen::Magic; use super::mask::{generate_bishop_mask, generate_rook_mask}; diff --git a/src/movegen/wizardry/mask.rs b/src/movegen/wizardry/mask.rs index 0c0bbdf..eed93a0 100644 --- a/src/movegen/wizardry/mask.rs +++ b/src/movegen/wizardry/mask.rs @@ -1,6 +1,5 @@ use crate::board::{Bitboard, File, Rank, Square}; -use crate::movegen::bishop::bishop_moves; -use crate::movegen::rook::rook_moves; +use crate::movegen::naive::{bishop::bishop_moves, rook::rook_moves}; pub fn generate_bishop_mask(square: Square) -> Bitboard { let rays = bishop_moves(square, Bitboard::EMPTY); From b348c89eeefc97bdda52834d7948b96465112e75 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Jul 2022 16:31:26 +0200 Subject: [PATCH 3/5] Make use of generated move tables --- src/build.rs | 3 ++ src/movegen/magic/mod.rs | 5 +++ src/movegen/magic/moves.rs | 71 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/movegen/magic/moves.rs diff --git a/src/build.rs b/src/build.rs index e05410d..29265f5 100644 --- a/src/build.rs +++ b/src/build.rs @@ -131,5 +131,8 @@ fn main() -> Result<()> { print_boards(&mut out, "QUEEN_SIDE_CASTLE_BLOCKERS", &queen_blockers)?; } + // Include the generated files now that the build script has run. + println!("cargo:rustc-cfg=generated_boards"); + Ok(()) } diff --git a/src/movegen/magic/mod.rs b/src/movegen/magic/mod.rs index f59bde2..b31abeb 100644 --- a/src/movegen/magic/mod.rs +++ b/src/movegen/magic/mod.rs @@ -19,3 +19,8 @@ impl Magic { base_index + self.offset } } + +#[cfg(generated_boards)] +mod moves; +#[cfg(generated_boards)] +pub use moves::*; diff --git a/src/movegen/magic/moves.rs b/src/movegen/magic/moves.rs new file mode 100644 index 0000000..2901b28 --- /dev/null +++ b/src/movegen/magic/moves.rs @@ -0,0 +1,71 @@ +use super::Magic; +use crate::board::{Bitboard, Color, Square}; + +include!(concat!(env!("OUT_DIR"), "/magic_tables.rs")); + +pub fn quiet_pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard { + // If there is a piece in front of the pawn, it can't advance + if !(color.backward_direction().move_board(blockers) & square).is_empty() { + return Bitboard::EMPTY; + } + // SAFETY: we know the values are in-bounds + unsafe { + *PAWN_MOVES + .get_unchecked(color.index()) + .get_unchecked(square.index()) + } +} + +pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard { + // SAFETY: we know the values are in-bounds + let attacks = unsafe { + *PAWN_ATTACKS + .get_unchecked(color.index()) + .get_unchecked(square.index()) + }; + quiet_pawn_moves(color, square, blockers) | attacks +} + +pub fn knight_moves(square: Square) -> Bitboard { + // SAFETY: we know the values are in-bounds + unsafe { *KNIGHT_MOVES.get_unchecked(square.index()) } +} + +pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard { + // SAFETY: we know the values are in-bounds + unsafe { + let index = BISHOP_MAGICS + .get_unchecked(square.index()) + .get_index(blockers); + *BISHOP_MOVES.get_unchecked(index) + } +} + +pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard { + // SAFETY: we know the values are in-bounds + unsafe { + let index = ROOK_MAGICS + .get_unchecked(square.index()) + .get_index(blockers); + *ROOK_MOVES.get_unchecked(index) + } +} + +pub fn queen_moves(square: Square, blockers: Bitboard) -> Bitboard { + bishop_moves(square, blockers) | rook_moves(square, blockers) +} + +pub fn king_moves(square: Square) -> Bitboard { + // SAFETY: we know the values are in-bounds + unsafe { *KING_MOVES.get_unchecked(square.index()) } +} + +pub fn king_side_castle_blockers(color: Color) -> Bitboard { + // SAFETY: we know the values are in-bounds + unsafe { *KING_SIDE_CASTLE_BLOCKERS.get_unchecked(color.index()) } +} + +pub fn queen_side_castle_blockers(color: Color) -> Bitboard { + // SAFETY: we know the values are in-bounds + unsafe { *QUEEN_SIDE_CASTLE_BLOCKERS.get_unchecked(color.index()) } +} From 36729a11316c6937129cb89ba238fa0b9d70494f Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Jul 2022 16:33:06 +0200 Subject: [PATCH 4/5] Add 'rerun-if-changed' directives to build script --- src/build.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/build.rs b/src/build.rs index 29265f5..10d7a94 100644 --- a/src/build.rs +++ b/src/build.rs @@ -134,5 +134,10 @@ fn main() -> Result<()> { // Include the generated files now that the build script has run. println!("cargo:rustc-cfg=generated_boards"); + // Run the build script only if something in move generation might have changed. + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=movegen/naive/"); + println!("cargo:rerun-if-changed=movegen/wizardry/"); + Ok(()) } From 7db4ec990e2f6aad7447952725e714175333ab07 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Jul 2022 16:43:18 +0200 Subject: [PATCH 5/5] Remove all useless 'allow(unused)' --- src/movegen/naive/bishop.rs | 1 - src/movegen/naive/king.rs | 2 -- src/movegen/naive/knight.rs | 1 - src/movegen/naive/pawn.rs | 3 --- src/movegen/naive/rook.rs | 1 - 5 files changed, 8 deletions(-) diff --git a/src/movegen/naive/bishop.rs b/src/movegen/naive/bishop.rs index 0fe0247..0806077 100644 --- a/src/movegen/naive/bishop.rs +++ b/src/movegen/naive/bishop.rs @@ -1,6 +1,5 @@ use crate::board::{Bitboard, Direction, Square}; -#[allow(unused)] pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard { Direction::iter_bishop() .map(|dir| dir.slide_board_with_blockers(square.into_bitboard(), blockers)) diff --git a/src/movegen/naive/king.rs b/src/movegen/naive/king.rs index 932b4f4..6e98df7 100644 --- a/src/movegen/naive/king.rs +++ b/src/movegen/naive/king.rs @@ -1,7 +1,6 @@ use crate::board::{Bitboard, CastleRights, Color, Direction, File, Square}; // No castling moves included -#[allow(unused)] pub fn king_moves(square: Square) -> Bitboard { let board = square.into_bitboard(); @@ -10,7 +9,6 @@ pub fn king_moves(square: Square) -> Bitboard { .fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs) } -#[allow(unused)] pub fn king_castling_moves(color: Color, castle_rights: CastleRights) -> Bitboard { let rank = color.first_rank(); diff --git a/src/movegen/naive/knight.rs b/src/movegen/naive/knight.rs index 4783bde..f850d71 100644 --- a/src/movegen/naive/knight.rs +++ b/src/movegen/naive/knight.rs @@ -1,6 +1,5 @@ use crate::board::{Bitboard, Direction, Square}; -#[allow(unused)] pub fn knight_moves(square: Square) -> Bitboard { let board = square.into_bitboard(); diff --git a/src/movegen/naive/pawn.rs b/src/movegen/naive/pawn.rs index 5c929fa..55b5bf6 100644 --- a/src/movegen/naive/pawn.rs +++ b/src/movegen/naive/pawn.rs @@ -1,6 +1,5 @@ use crate::board::{Bitboard, Color, Direction, Rank, Square}; -#[allow(unused)] pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard { if (square.rank() == Rank::First) || (square.rank() == Rank::Eighth) { return Bitboard::EMPTY; @@ -22,7 +21,6 @@ pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard } } -#[allow(unused)] pub fn pawn_captures(color: Color, square: Square) -> Bitboard { if (square.rank() == Rank::First) || (square.rank() == Rank::Eighth) { return Bitboard::EMPTY; @@ -38,7 +36,6 @@ pub fn pawn_captures(color: Color, square: Square) -> Bitboard { attack_west | attack_east } -#[allow(unused)] pub fn en_passant_origins(square: Square) -> Bitboard { let board = square.into_bitboard(); diff --git a/src/movegen/naive/rook.rs b/src/movegen/naive/rook.rs index 31fd7d8..0b06cef 100644 --- a/src/movegen/naive/rook.rs +++ b/src/movegen/naive/rook.rs @@ -1,6 +1,5 @@ use crate::board::{Bitboard, Direction, Square}; -#[allow(unused)] pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard { Direction::iter_rook() .map(|dir| dir.slide_board_with_blockers(square.into_bitboard(), blockers))