From 7c776ff874ee478fc3a7adca9f7a077ab110434a Mon Sep 17 00:00:00 2001 From: Paul-Nicolas Madelaine Date: Sun, 19 Oct 2025 17:00:09 +0200 Subject: [PATCH] wip: const lookup --- src/bitboard.rs | 24 ++- src/board.rs | 76 +++++---- src/lib.rs | 11 +- src/lookup.rs | 424 ++++++++++++++++++++++++++++++++++++++++-------- src/magics.rs | 189 +-------------------- src/main.rs | 3 + src/position.rs | 78 ++++----- src/rays.rs | 42 ----- src/setup.rs | 3 +- 9 files changed, 471 insertions(+), 379 deletions(-) create mode 100644 src/main.rs diff --git a/src/bitboard.rs b/src/bitboard.rs index 722a01b..8020564 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -7,24 +7,25 @@ use std::iter::FusedIterator; /// A set of squares. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] pub struct Bitboard(pub(crate) u64); impl Bitboard { /// Returns an empty bitboard. #[inline] - pub fn new() -> Self { + pub const fn new() -> Self { Self(0) } /// Returns `true` if the bitboard is empty. #[inline] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.0 == 0 } /// Returns the square in the bitboard with the smallest index. #[inline] - pub fn first(&self) -> Option { + pub const fn first(&self) -> Option { let mask = self.0; match mask { 0 => None, @@ -32,10 +33,21 @@ impl Bitboard { } } + #[inline] + pub(crate) const fn last(&self) -> Option { + let mask = self.0; + match mask { + 0 => None, + _ => Some(unsafe { + Square::new_unchecked(63_u8.unchecked_sub(mask.leading_zeros() as u8)) + }), + } + } + /// Removes the square in the bitboard with the smallest index and returns it, or `None` if the /// bitboard is empty. #[inline] - pub fn pop(&mut self) -> Option { + pub const fn pop(&mut self) -> Option { let Self(ref mut mask) = self; let square = match mask { 0 => None, @@ -47,13 +59,13 @@ impl Bitboard { /// Inserts a square in the bitboard. #[inline] - pub fn insert(&mut self, square: Square) { + pub const fn insert(&mut self, square: Square) { self.0 |= 1 << square as u8; } /// Returns `true` if the bitboard contains the given square. #[inline] - pub fn contains(&self, square: Square) -> bool { + pub const fn contains(&self, square: Square) -> bool { self.0 & (1 << square as u8) != 0 } diff --git a/src/board.rs b/src/board.rs index 85b4ff4..dca9dbf 100644 --- a/src/board.rs +++ b/src/board.rs @@ -5,6 +5,7 @@ use crate::bitboard::*; macro_rules! container { ($v: vis, $a:ident, $b:ident, $n:literal) => { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[repr(transparent)] $v struct $b(pub(crate) [T; $n]); #[allow(unused)] impl $b { @@ -116,7 +117,7 @@ impl File { } #[inline] - pub unsafe fn new_unchecked(index: u8) -> Self { + pub const unsafe fn new_unchecked(index: u8) -> Self { debug_assert!(index < 8); std::mem::transmute(index) } @@ -147,7 +148,7 @@ impl File { } #[inline] - pub fn bitboard(self) -> Bitboard { + pub const fn bitboard(self) -> Bitboard { Bitboard(0x0101010101010101 << (self as u8)) } } @@ -196,7 +197,7 @@ impl Rank { } #[inline] - pub unsafe fn new_unchecked(index: u8) -> Self { + pub const unsafe fn new_unchecked(index: u8) -> Self { debug_assert!(index < 8); std::mem::transmute(index) } @@ -232,7 +233,7 @@ impl Rank { } #[inline] - pub fn bitboard(self) -> Bitboard { + pub const fn bitboard(self) -> Bitboard { Bitboard(0xFF << ((self as u8) << 3)) } } @@ -278,12 +279,16 @@ impl Square { } #[inline] - pub fn new(index: u8) -> Option { - (index < 64).then(|| unsafe { Self::new_unchecked(index) }) + pub const fn new(index: u8) -> Option { + if index < 64 { + Some(unsafe { Self::new_unchecked(index) }) + } else { + None + } } #[inline] - pub unsafe fn new_unchecked(index: u8) -> Self { + pub const unsafe fn new_unchecked(index: u8) -> Self { debug_assert!(index < 64); std::mem::transmute(index) } @@ -294,12 +299,12 @@ impl Square { } #[inline] - pub fn file(self) -> File { + pub const fn file(self) -> File { unsafe { File::new_unchecked((self as u8) & 7) } } #[inline] - pub fn rank(self) -> Rank { + pub const fn rank(self) -> Rank { unsafe { Rank::new_unchecked((self as u8) >> 3) } } @@ -310,7 +315,7 @@ impl Square { } #[inline] - pub fn bitboard(self) -> Bitboard { + pub const fn bitboard(self) -> Bitboard { Bitboard(1 << self as u8) } @@ -347,16 +352,17 @@ impl Square { } #[inline] - pub(crate) fn trans(self, direction: Direction) -> Option { - self.check_trans(direction).then(|| unsafe { - // SAFETY: condition is checked before doing the translation - self.trans_unchecked(direction) - }) + pub(crate) const fn trans(self, direction: Direction) -> Option { + if self.check_trans(direction) { + Some(unsafe { self.trans_unchecked(direction) }) + } else { + None + } } /// SAFETY: the translation must not move the square outside the board #[inline] - pub(crate) unsafe fn trans_unchecked(self, direction: Direction) -> Self { + pub(crate) const unsafe fn trans_unchecked(self, direction: Direction) -> Self { debug_assert!(self.check_trans(direction)); let i = self as u8; unsafe { @@ -375,16 +381,18 @@ impl Square { /// Returns `false` if the translation would move the square outside the board #[inline] - fn check_trans(self, direction: Direction) -> bool { + const fn check_trans(self, direction: Direction) -> bool { + let file = self.file() as u8; + let rank = self.rank() as u8; match direction { - Direction::East => self.file() < File::H, - Direction::NorthEast => self.file() < File::H && self.rank() < Rank::Eighth, - Direction::North => self.rank() < Rank::Eighth, - Direction::NorthWest => self.file() > File::A && self.rank() < Rank::Eighth, - Direction::SouthEast => self.file() < File::H && self.rank() > Rank::First, - Direction::South => self.rank() > Rank::First, - Direction::SouthWest => self.file() > File::A && self.rank() > Rank::First, - Direction::West => self.file() > File::A, + Direction::East => file < 7, + Direction::NorthEast => file < 7 && rank < 7, + Direction::North => rank < 7, + Direction::NorthWest => file > 0 && rank < 7, + Direction::SouthEast => file < 7 && rank > 0, + Direction::South => rank > 0, + Direction::SouthWest => file > 0 && rank > 0, + Direction::West => file > 0, } } } @@ -525,6 +533,7 @@ impl Role { } #[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] pub struct ByRole(pub(crate) [T; 6]); #[allow(unused)] impl ByRole { @@ -615,9 +624,18 @@ impl Direction { } #[inline] - unsafe fn transmute(value: u8) -> Self { - debug_assert!(value < 8); - std::mem::transmute(value) + pub const fn new(index: u8) -> Option { + if index < 8 { + Some(unsafe { Self::new_unchecked(index) }) + } else { + None + } + } + + #[inline] + pub const unsafe fn new_unchecked(index: u8) -> Self { + debug_assert!(index < 8); + std::mem::transmute(index) } } @@ -625,7 +643,7 @@ impl std::ops::Not for Direction { type Output = Self; #[inline] fn not(self) -> Self::Output { - unsafe { Self::transmute(self as u8 ^ 0b111) } + unsafe { Self::new_unchecked(self as u8 ^ 0b111) } } } diff --git a/src/lib.rs b/src/lib.rs index de60b7e..fc8e1d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,12 +73,11 @@ //! - etc. pub(crate) mod array_vec; +pub(crate) mod lookup; pub(crate) mod magics; -pub(crate) mod rays; pub mod bitboard; pub mod board; -pub mod lookup; pub mod position; pub mod san; pub mod setup; @@ -88,3 +87,11 @@ pub mod uci; pub mod prelude { pub use crate::{position::Position, san::San, setup::Setup, uci::UciMove}; } + +pub fn test() { + use crate::bitboard::*; + use crate::board::*; + let d = &crate::lookup::LOOKUP; + let Bitboard(x) = d.pawn_attack(Color::White, Square::F7); + println!("{x:016x}"); +} diff --git a/src/lookup.rs b/src/lookup.rs index d1378a6..f8a8ec9 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -1,20 +1,146 @@ -//! Lookup tables initialisation. -//! -//! Move generation in eschac requires about 1MB of precomputed lookup tables. - use crate::bitboard::*; use crate::board::*; use crate::magics::*; -use crate::rays::*; -pub(crate) use init::InitialisedLookup; +#[allow(long_running_const_eval)] +pub(crate) static LOOKUP: Lookup = Lookup::compute(); -/// Forces the initialisation of the lookup tables. -/// -/// It is not necessary to call this function, as lookup tables are initialised lazily, but it can -/// be used to ensure that they are initialised before a given time. -pub fn init() { - InitialisedLookup::init(); +macro_rules! loop_bishop_directions { + ($d: ident, $body: expr) => {{ + { + let $d = Direction::NorthEast; + $body + } + { + let $d = Direction::NorthWest; + $body + } + { + let $d = Direction::SouthWest; + $body + } + { + let $d = Direction::SouthEast; + $body + } + }}; +} + +macro_rules! loop_rook_directions { + ($d: ident, $body: expr) => {{ + { + let $d = Direction::East; + $body + } + { + let $d = Direction::North; + $body + } + { + let $d = Direction::West; + $body + } + { + let $d = Direction::South; + $body + } + }}; +} + +macro_rules! loop_all_directions { + ($d: ident, $body: expr) => {{ + loop_bishop_directions!($d, $body); + loop_rook_directions!($d, $body); + }}; +} + +macro_rules! by_color { + ($c: ident, $body: expr) => {{ + ByColor([ + { + let $c = Color::White; + $body + }, + { + let $c = Color::Black; + $body + }, + ]) + }}; +} + +macro_rules! by_square { + ($sq: ident, $init: expr, $body: expr) => {{ + let mut res = [$init; 64]; + let mut $sq: u8 = 0; + while $sq < 64 { + res[$sq as usize] = { + let $sq = Square::new($sq).unwrap(); + $body + }; + $sq += 1; + } + BySquare(res) + }}; +} + +macro_rules! by_direction { + ($d: ident, $body: expr) => {{ + let mut res = [Bitboard(0); 8]; + let mut $d: u8 = 0; + while $d < 8 { + res[$d as usize] = { + let $d = Direction::new($d).unwrap(); + $body + }; + $d += 1; + } + ByDirection(res) + }}; +} + +pub(crate) struct Rays(BySquare>); + +impl Rays { + pub(crate) const fn new() -> Self { + Self(by_square!( + square, + ByDirection([Bitboard(0); 8]), + by_direction!(direction, { + let mut square = square; + let mut res = 0; + while let Some(x) = square.trans(direction) { + square = x; + res |= square.bitboard().0; + } + Bitboard(res) + }) + )) + } + + #[inline] + pub(crate) const fn ray(&self, square: Square, direction: Direction) -> Bitboard { + (self.0).0[square as u8 as usize].0[direction as u8 as usize] + } + + pub(crate) const fn blocked( + &self, + square: Square, + direction: Direction, + blockers: Bitboard, + ) -> Bitboard { + let ray = self.ray(square, direction); + let blockers = Bitboard(blockers.0 & ray.0); + let square = if (direction as u8) < 4 { + Bitboard::first(&blockers) + } else { + Bitboard::last(&blockers) + }; + match square { + None => ray, + Some(square) => Bitboard(ray.0 & !self.ray(square, direction).0), + } + } } pub(crate) struct Lookup { @@ -81,75 +207,97 @@ impl Lookup { } } - pub(crate) fn compute() -> Self { + const fn compute() -> Self { let rays = Rays::new(); - let lines = BySquare::new(|a| { - BySquare::new(|b| { - for d in Direction::all() { + let lines = by_square!(a, BySquare([Bitboard(0); 64]), { + by_square!(b, Bitboard(0), { + let mut res = Bitboard(0); + loop_all_directions!(d, { let r = rays.ray(a, d); if r.contains(b) { - return r; + res = r; } - } - Bitboard::new() + }); + res }) }); - let segments = BySquare::new(|a| { - BySquare::new(|b| { - for d in Direction::all() { + let segments: BySquare> = by_square!(a, BySquare([Bitboard(0); 64]), { + by_square!(b, Bitboard(0), { + let mut res = 0; + loop_all_directions!(d, { let r = rays.ray(a, d); if r.contains(b) { - return r & !rays.ray(b, d); + res = r.0 & !rays.ray(b, d).0; } - } - b.bitboard() + }); + Bitboard(res) }) }); - let pawn_attacks = ByColor::new(|color| { + let pawn_attacks = by_color!(color, { let direction = match color { Color::White => Direction::North, Color::Black => Direction::South, }; - BySquare::new(|square| { - let mut res = Bitboard::new(); + by_square!(square, Bitboard(0), { + let mut res = Bitboard(0); if let Some(square) = square.trans(direction) { - square.trans(Direction::East).map(|s| res.insert(s)); - square.trans(Direction::West).map(|s| res.insert(s)); + if let Some(s) = square.trans(Direction::East) { + res.insert(s) + }; + if let Some(s) = square.trans(Direction::West) { + res.insert(s) + }; } res }) }); - let king_moves = BySquare::new(|square| { - let mut res = Bitboard::new(); - for direction in Direction::all() { - if let Some(x) = square.trans(direction) { - res |= x.bitboard(); + let king_moves = by_square!(sq, Bitboard(0), { + let mut res = 0; + loop_all_directions!(d, { + if let Some(x) = sq.trans(d) { + res |= x.bitboard().0; } - } - res + }); + Bitboard(res) }); - let knight_moves = BySquare::new(|s| { + let knight_moves = by_square!(s, Bitboard(0), { let mut res = Bitboard::new(); if let Some(s) = s.trans(Direction::North) { - s.trans(Direction::NorthEast).map(|s| res.insert(s)); - s.trans(Direction::NorthWest).map(|s| res.insert(s)); + if let Some(s) = s.trans(Direction::NorthEast) { + res.insert(s); + } + if let Some(s) = s.trans(Direction::NorthWest) { + res.insert(s) + }; } if let Some(s) = s.trans(Direction::West) { - s.trans(Direction::NorthWest).map(|s| res.insert(s)); - s.trans(Direction::SouthWest).map(|s| res.insert(s)); + if let Some(s) = s.trans(Direction::NorthWest) { + res.insert(s) + }; + if let Some(s) = s.trans(Direction::SouthWest) { + res.insert(s) + }; } if let Some(s) = s.trans(Direction::South) { - s.trans(Direction::SouthWest).map(|s| res.insert(s)); - s.trans(Direction::SouthEast).map(|s| res.insert(s)); + if let Some(s) = s.trans(Direction::SouthWest) { + res.insert(s) + }; + if let Some(s) = s.trans(Direction::SouthEast) { + res.insert(s) + }; } if let Some(s) = s.trans(Direction::East) { - s.trans(Direction::SouthEast).map(|s| res.insert(s)); - s.trans(Direction::NorthEast).map(|s| res.insert(s)); + if let Some(s) = s.trans(Direction::SouthEast) { + res.insert(s) + }; + if let Some(s) = s.trans(Direction::NorthEast) { + res.insert(s) + }; } res }); @@ -168,35 +316,175 @@ impl Lookup { } } -mod init { - use std::{mem::MaybeUninit, sync::LazyLock}; +// -------- Magics -------- - use super::Lookup; +const BISHOP_SHR: u8 = 55; +const ROOK_SHR: u8 = 52; - static mut LOOKUP: MaybeUninit = MaybeUninit::uninit(); +const TABLE_SIZE: usize = 267_293; - #[allow(static_mut_refs)] - static INIT: LazyLock<()> = LazyLock::new(|| unsafe { - LOOKUP.write(Lookup::compute()); - }); +pub(crate) struct Magics { + bishop: BySquare, + rook: BySquare, + table: [Bitboard; TABLE_SIZE], +} - #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub(crate) struct InitialisedLookup(()); +#[derive(Clone, Copy)] +struct Magic { + premask: Bitboard, + factor: u64, + offset: isize, +} - impl InitialisedLookup { - #[inline] - pub(crate) fn init() -> Self { - LazyLock::force(&INIT); - Self(()) +macro_rules! loop_subsets { + ($premask: ident, $subset: ident, $body: expr) => {{ + let mut $subset: u64 = 0; + loop { + $subset = $subset.wrapping_sub($premask) & $premask; + $body + if $subset == 0 { + break; + } + } + }}; +} + +impl Magics { + pub(crate) const fn compute(rays: &Rays) -> Self { + let null = Magic { + premask: Bitboard(0), + factor: 0, + offset: 0, + }; + + let mut table = [Bitboard(0); TABLE_SIZE]; + let mut len: usize = 0; + + let bishop = by_square!(square, null, { + let mut premask = 0; + loop_bishop_directions!(direction, { + premask |= rays.ray(square, direction).0; + }); + premask &= !Rank::First.bitboard().0; + premask &= !Rank::Eighth.bitboard().0; + premask &= !File::A.bitboard().0; + premask &= !File::H.bitboard().0; + + let factor = bishop_factors(square); + + let mut a = usize::MAX; + loop_subsets!(premask, blockers, { + let cur = hash(BISHOP_SHR, factor, Bitboard(blockers | !premask)); + if cur < a { + a = cur; + } + }); + let offset = len as isize - a as isize; + + loop_subsets!(premask, blockers, { + let index = (offset + + hash(BISHOP_SHR, factor, Bitboard(blockers | !premask)) as isize) + as usize; + let mut res = 0; + loop_bishop_directions!(direction, { + res |= rays.blocked(square, direction, Bitboard(blockers)).0; + }); + if table[index].0 != 0 && table[index].0 != res { + panic!() + } + if index >= len { + len = index + 1; + } + table[index] = Bitboard(res); + }); + + Magic { + premask: Bitboard(!premask), + factor, + offset, + } + }); + + let rook = by_square!(square, null, { + let mut premask = 0; + premask |= rays.ray(square, Direction::East).0 & !File::H.bitboard().0; + premask |= rays.ray(square, Direction::North).0 & !Rank::Eighth.bitboard().0; + premask |= rays.ray(square, Direction::West).0 & !File::A.bitboard().0; + premask |= rays.ray(square, Direction::South).0 & !Rank::First.bitboard().0; + + let factor = rook_factors(square); + + let mut a = usize::MAX; + loop_subsets!(premask, blockers, { + let cur = hash(ROOK_SHR, factor, Bitboard(blockers | !premask)); + if cur < a { + a = cur; + } + }); + let offset = len as isize - a as isize; + + loop_subsets!(premask, blockers, { + let index = (offset + + hash(ROOK_SHR, factor, Bitboard(blockers | !premask)) as isize) + as usize; + let mut res = 0; + loop_rook_directions!(direction, { + res |= rays.blocked(square, direction, Bitboard(blockers)).0; + }); + if table[index].0 != 0 && table[index].0 != res { + panic!() + } + if index >= len { + len = index + 1; + } + table[index] = Bitboard(res); + }); + + Magic { + premask: Bitboard(!premask), + factor, + offset, + } + }); + + // if len != TABLE_SIZE { + // panic!() + // } + + Self { + bishop, + rook, + table, } } - impl std::ops::Deref for InitialisedLookup { - type Target = Lookup; - #[allow(static_mut_refs)] - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { LOOKUP.assume_init_ref() } - } + #[inline] + pub(crate) fn bishop(&self, square: Square, blockers: Bitboard) -> Bitboard { + unsafe { self.get_unchecked(BISHOP_SHR, *self.bishop.get(square), blockers) } + } + + #[inline] + pub(crate) fn rook(&self, square: Square, blockers: Bitboard) -> Bitboard { + unsafe { self.get_unchecked(ROOK_SHR, *self.rook.get(square), blockers) } + } + + #[inline] + unsafe fn get_unchecked(&self, shr: u8, magic: Magic, blockers: Bitboard) -> Bitboard { + let Magic { + premask, + factor, + offset, + } = magic; + debug_assert!(self + .table + .get( + (hash(shr, factor, blockers | premask) as isize) + .checked_add(offset) + .unwrap() as usize + ) + .is_some()); + *self.table.get_unchecked( + ((hash(shr, factor, blockers | premask) as isize).unchecked_add(offset)) as usize, + ) } } diff --git a/src/magics.rs b/src/magics.rs index d82e531..e37a0f6 100644 --- a/src/magics.rs +++ b/src/magics.rs @@ -1,193 +1,16 @@ use crate::bitboard::*; use crate::board::*; -use crate::rays::Rays; -const BISHOP_SHR: u8 = 55; -const ROOK_SHR: u8 = 52; +pub(crate) const BISHOP_SHR: u8 = 55; +pub(crate) const ROOK_SHR: u8 = 52; -pub(crate) struct Magics { - bishop: BySquare, - rook: BySquare, - table: Box<[Bitboard]>, -} +pub(crate) const TABLE_SIZE: usize = 0; -#[derive(Clone, Copy)] -struct Magic { - premask: Bitboard, - factor: u64, - offset: isize, -} - -impl Magics { - pub(crate) fn compute(rays: &Rays) -> Self { - let mut data = Vec::new(); - - let mut aux = - |shr, - factors: fn(Square) -> u64, - make_table: fn(&Rays, Square) -> (Bitboard, Vec<(Bitboard, Bitboard)>)| { - BySquare::new(|square| { - let (premask, table) = make_table(rays, square); - let factor = factors(square); - let offset = fill_table(&mut data, shr, factor, table); - Magic { - premask, - factor, - offset, - } - }) - }; - - let bishop = aux(BISHOP_SHR, bishop_factors, make_bishop_table); - let rook = aux(ROOK_SHR, rook_factors, make_rook_table); - - let mut table = Box::new_uninit_slice(data.len()); - for (i, entry) in data.into_iter().enumerate() { - table[i].write(entry); - } - - Self { - bishop, - rook, - table: unsafe { table.assume_init() }, - } - } - - #[inline] - pub(crate) fn bishop(&self, square: Square, blockers: Bitboard) -> Bitboard { - unsafe { self.get_unchecked(BISHOP_SHR, *self.bishop.get(square), blockers) } - } - - #[inline] - pub(crate) fn rook(&self, square: Square, blockers: Bitboard) -> Bitboard { - unsafe { self.get_unchecked(ROOK_SHR, *self.rook.get(square), blockers) } - } - - #[inline] - unsafe fn get_unchecked(&self, shr: u8, magic: Magic, blockers: Bitboard) -> Bitboard { - let Magic { - premask, - factor, - offset, - } = magic; - *self.table.get_unchecked( - ((hash(shr, factor, blockers | premask) as isize).unchecked_add(offset)) as usize, - ) - } -} - -fn fill_table( - data: &mut Vec, - shr: u8, - factor: u64, - table: Vec<(Bitboard, Bitboard)>, -) -> isize { - let offset = data.len() as isize - - table - .iter() - .map(|(x, _)| hash(shr, factor, *x) as isize) - .min() - .unwrap(); - for (x, y) in &table { - let i = (hash(shr, factor, *x) as isize + offset) as usize; - while data.len() <= i { - data.push(Bitboard::new()); - } - if data[i] != Bitboard::new() && data[i] != *y { - panic!(); - } - data[i] = *y; - } - offset -} - -fn make_bishop_table(rays: &Rays, square: Square) -> (Bitboard, Vec<(Bitboard, Bitboard)>) { - let mut premask = Bitboard::new(); - for direction in [ - Direction::NorthWest, - Direction::SouthWest, - Direction::SouthEast, - Direction::NorthEast, - ] { - premask |= rays.ray(square, direction); - } - premask &= !Rank::First.bitboard(); - premask &= !Rank::Eighth.bitboard(); - premask &= !File::A.bitboard(); - premask &= !File::H.bitboard(); - - let mut table = make_table(premask, |blockers| { - let mut res = Bitboard::new(); - for direction in [ - Direction::NorthWest, - Direction::SouthWest, - Direction::SouthEast, - Direction::NorthEast, - ] { - res |= rays.blocked(square, direction, blockers); - } - res - }); - - premask = !premask; - for (x, _) in &mut table { - *x |= premask; - } - - (premask, table) -} - -fn make_rook_table(rays: &Rays, square: Square) -> (Bitboard, Vec<(Bitboard, Bitboard)>) { - let mut premask = Bitboard::new(); - premask |= rays.ray(square, Direction::North) & !Rank::Eighth.bitboard(); - premask |= rays.ray(square, Direction::West) & !File::A.bitboard(); - premask |= rays.ray(square, Direction::South) & !Rank::First.bitboard(); - premask |= rays.ray(square, Direction::East) & !File::H.bitboard(); - - let mut table = make_table(premask, |blockers| { - let mut res = Bitboard::new(); - for direction in [ - Direction::North, - Direction::West, - Direction::South, - Direction::East, - ] { - res |= rays.blocked(square, direction, blockers); - } - res - }); - - premask = !premask; - for (x, _) in &mut table { - *x |= premask; - } - - (premask, table) -} - -fn make_table(premask: Bitboard, f: F) -> Vec<(Bitboard, T)> -where - F: Fn(Bitboard) -> T, -{ - let mut res = Vec::new(); - let mut subset: u64 = 0; - loop { - subset = subset.wrapping_sub(premask.0) & premask.0; - let x = Bitboard(subset); - let y = f(x); - res.push((x, y)); - if subset == 0 { - break; - } - } - res -} - -fn hash(shr: u8, factor: u64, x: Bitboard) -> usize { +pub(crate) const fn hash(shr: u8, factor: u64, x: Bitboard) -> usize { (x.0.wrapping_mul(factor) >> shr) as usize } -fn bishop_factors(square: Square) -> u64 { +pub(crate) const fn bishop_factors(square: Square) -> u64 { match square { Square::A1 => 0x0000404040404040, Square::B1 => 0x0040C100081000E8, @@ -256,7 +79,7 @@ fn bishop_factors(square: Square) -> u64 { } } -fn rook_factors(square: Square) -> u64 { +pub(crate) const fn rook_factors(square: Square) -> u64 { match square { Square::A1 => 0x002000A28110000C, Square::B1 => 0x0018000C01060001, diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f6e92c7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + eschac::test(); +} diff --git a/src/position.rs b/src/position.rs index b187631..388622e 100644 --- a/src/position.rs +++ b/src/position.rs @@ -3,7 +3,6 @@ use crate::array_vec::*; use crate::bitboard::*; use crate::board::*; -use crate::lookup::*; use crate::san::*; use crate::setup::*; use crate::uci::*; @@ -55,10 +54,7 @@ use std::iter::FusedIterator; /// requirement is to be efficient while respecting the [`Ord`] trait protocol. It should not be /// considered stable. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Position { - setup: Setup, - lookup: InitialisedLookup, -} +pub struct Position(Setup); const MAX_LEGAL_MOVES: usize = 218; @@ -76,18 +72,15 @@ impl Position { /// i.e. `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -` #[inline] pub fn new() -> Self { - Self { - setup: Setup { - w: Bitboard(0x000000000000FFFF), - p_b_q: Bitboard(0x2CFF00000000FF2C), - n_b_k: Bitboard(0x7600000000000076), - r_q_k: Bitboard(0x9900000000000099), - turn: Color::White, - castling_rights: CastlingRights::full(), - en_passant: OptionSquare::None, - }, - lookup: InitialisedLookup::init(), - } + Self(Setup { + w: Bitboard(0x000000000000FFFF), + p_b_q: Bitboard(0x2CFF00000000FF2C), + n_b_k: Bitboard(0x7600000000000076), + r_q_k: Bitboard(0x9900000000000099), + turn: Color::White, + castling_rights: CastlingRights::full(), + en_passant: OptionSquare::None, + }) } /// Tries to read a valid position from a text record. @@ -182,25 +175,25 @@ impl Position { /// responsibility to rule out the legality of en passant before calling this function. #[inline] pub fn remove_en_passant_target_square(&mut self) { - self.setup.en_passant = OptionSquare::None; + self.0.en_passant = OptionSquare::None; } /// Returns the occupancy of a square. #[inline] pub fn get(&self, square: Square) -> Option { - self.setup.get(square) + self.0.get(square) } /// Returns the color whose turn it is to play. #[inline] pub fn turn(&self) -> Color { - self.setup.turn() + self.0.turn() } /// Returns `true` if castling is available for the given color and side. #[inline] pub fn castling_rights(&self, color: Color, side: CastlingSide) -> bool { - self.setup.castling_rights(color, side) + self.0.castling_rights(color, side) } /// Returns the en passant target square if it exists. @@ -209,26 +202,26 @@ impl Position { /// legal or even pseudo-legal. #[inline] pub fn en_passant_target_square(&self) -> Option { - self.setup.en_passant_target_square() + self.0.en_passant_target_square() } /// Discards the castling rights for the given color and side. #[inline] pub fn remove_castling_rights(&mut self, color: Color, side: CastlingSide) { - self.setup.set_castling_rights(color, side, false); + self.0.set_castling_rights(color, side, false); } /// Borrows the position as a [`Setup`]. #[inline] pub fn as_setup(&self) -> &Setup { - &self.setup + &self.0 } /// Converts a position to a [`Setup`], allowing to edit the position without enforcing its /// legality. #[inline] pub fn into_setup(self) -> Setup { - self.setup + self.0 } /// Tries to pass the turn to the other color, failing if it would leave the king in check. @@ -236,8 +229,8 @@ impl Position { /// When possible, this inverts the color to play and removes the en passant square if it /// exists. pub fn pass(&self) -> Option { - let d = self.lookup; - let setup = &self.setup; + let d = &crate::lookup::LOOKUP; + let setup = &self.0; let blockers = setup.p_b_q | setup.n_b_k | setup.r_q_k; let k = setup.n_b_k & setup.r_q_k; let q = setup.p_b_q & setup.r_q_k; @@ -255,23 +248,19 @@ impl Position { | d.knight(king_square) & n | d.bishop(king_square, blockers) & (q | b) | d.rook(king_square, blockers) & (q | r)); - checkers.is_empty().then(|| Self { - setup: Setup { + checkers.is_empty().then(|| { + Self(Setup { turn: !setup.turn, en_passant: OptionSquare::None, ..setup.clone() - }, - lookup: d, + }) }) } /// Returns the mirror image of the position (see [`Setup::mirror`]). #[inline] pub fn mirror(&self) -> Self { - Self { - setup: self.setup.mirror(), - lookup: self.lookup, - } + Self(self.0.mirror()) } /// Returns the number of possible chess games for a given number of moves. @@ -351,7 +340,7 @@ impl Position { to, promotion, } = uci; - let role = self.setup.get_role(from).ok_or(InvalidUciMove::Illegal)?; + let role = self.0.get_role(from).ok_or(InvalidUciMove::Illegal)?; #[inline] fn aux<'l, const ROLE: u8>( position: &'l Position, @@ -550,9 +539,7 @@ impl<'l> Move<'l> { #[inline] pub fn is_capture(self) -> bool { self.raw.kind == MoveType::EnPassant - || !((self.position.setup.p_b_q - | self.position.setup.n_b_k - | self.position.setup.r_q_k) + || !((self.position.0.p_b_q | self.position.0.n_b_k | self.position.0.r_q_k) & self.to().bitboard()) .is_empty() } @@ -562,7 +549,7 @@ impl<'l> Move<'l> { pub fn captured(self) -> Option { match self.raw.kind { MoveType::EnPassant => Some(Role::Pawn), - _ => self.position.setup.get_role(self.raw.to), + _ => self.position.0.get_role(self.raw.to), } } @@ -932,10 +919,7 @@ impl<'l> Visitor for Moves<'l> { impl Position { /// SAFETY: The position must be valid. pub(crate) unsafe fn from_setup(setup: Setup) -> Self { - Self { - setup, - lookup: InitialisedLookup::init(), - } + Self(setup) } fn generate_moves(&self, visitor: &mut T) @@ -953,8 +937,8 @@ impl Position { turn, en_passant, castling_rights, - } = self.setup; - let d = self.lookup; + } = self.0; + let d = &crate::lookup::LOOKUP; let blockers = p_b_q | n_b_k | r_q_k; let (us, them) = match turn { @@ -1267,7 +1251,7 @@ impl Position { #[inline] unsafe fn play_unchecked(&mut self, m: RawMove) { - let Self { setup, .. } = self; + let Self(setup) = self; setup.en_passant = OptionSquare::None; diff --git a/src/rays.rs b/src/rays.rs index d758ffc..cfa17e9 100644 --- a/src/rays.rs +++ b/src/rays.rs @@ -1,44 +1,2 @@ use crate::bitboard::*; use crate::board::*; - -pub(crate) struct Rays(BySquare>); - -impl Rays { - pub(crate) fn new() -> Self { - Self(BySquare::new(|square| { - ByDirection::new(|direction| { - let mut square = square; - let mut res = Bitboard::new(); - while let Some(x) = square.trans(direction) { - square = x; - res |= square.bitboard(); - } - res - }) - })) - } - - #[inline] - pub(crate) fn ray(&self, square: Square, direction: Direction) -> Bitboard { - *self.0.get(square).get(direction) - } - - pub(crate) fn blocked( - &self, - square: Square, - direction: Direction, - blockers: Bitboard, - ) -> Bitboard { - let blockers = blockers & *self.0.get(square).get(direction); - let square2 = if (direction as u8) < 4 { - blockers.first() - } else { - blockers.last() - }; - *self.0.get(square).get(direction) - & !match square2 { - Some(square2) => *self.0.get(square2).get(direction), - None => Bitboard::new(), - } - } -} diff --git a/src/setup.rs b/src/setup.rs index 50d161b..a1b9c1a 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -4,7 +4,6 @@ use crate::bitboard::*; use crate::board::*; -use crate::lookup::*; use crate::position::*; /// **A builder type for chess positions.** @@ -347,7 +346,7 @@ impl Setup { debug_assert!((self.p_b_q & self.n_b_k & self.r_q_k).is_empty()); let mut reasons = IllegalPositionReasons::new(); - let d = InitialisedLookup::init(); + let d = &crate::lookup::LOOKUP; let blockers = self.p_b_q | self.n_b_k | self.r_q_k; let pieces = self.pieces();