1
0
Fork 0

const lookup table

This commit is contained in:
Paul-Nicolas Madelaine 2025-10-22 22:59:53 +02:00
parent 752150107e
commit 0d22c59cc3
8 changed files with 573 additions and 542 deletions

View file

@ -3,7 +3,7 @@
use crate::array_vec::*;
use crate::bitboard::*;
use crate::board::*;
use crate::lookup::*;
use crate::lookup;
use crate::san::*;
use crate::setup::*;
use crate::uci::*;
@ -55,10 +55,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;
@ -68,18 +65,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.
@ -160,25 +154,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<Piece> {
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.
@ -187,26 +181,26 @@ impl Position {
/// legal or even pseudo-legal.
#[inline]
pub fn en_passant_target_square(&self) -> Option<Square> {
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.
@ -214,8 +208,7 @@ impl Position {
/// When possible, this inverts the color to play and removes the en passant square if it
/// exists.
pub fn pass(&self) -> Option<Self> {
let d = self.lookup;
let setup = &self.setup;
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;
@ -229,27 +222,23 @@ impl Position {
};
let king_square = (us & k).next().unwrap();
let checkers = them
& (d.pawn_attack(setup.turn, king_square) & p
| 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 {
& (lookup::pawn_attack(setup.turn, king_square) & p
| lookup::knight(king_square) & n
| lookup::bishop(king_square, blockers) & (q | b)
| lookup::rook(king_square, blockers) & (q | r));
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.
@ -329,7 +318,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,
@ -536,9 +525,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()
}
@ -548,7 +535,7 @@ impl<'l> Move<'l> {
pub fn captured(self) -> Option<Role> {
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),
}
}
@ -918,10 +905,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<T>(&self, visitor: &mut T)
@ -939,8 +923,7 @@ impl Position {
turn,
en_passant,
castling_rights,
} = self.setup;
let d = self.lookup;
} = self.0;
let blockers = p_b_q | n_b_k | r_q_k;
let (us, them) = match turn {
@ -970,10 +953,10 @@ impl Position {
let forward = turn.forward();
let x = d.bishop(king_square, blockers);
let y = d.rook(king_square, blockers);
let checkers = d.pawn_attack(turn, king_square) & theirs.pawn()
| d.knight(king_square) & theirs.knight()
let x = lookup::bishop(king_square, blockers);
let y = lookup::rook(king_square, blockers);
let checkers = lookup::pawn_attack(turn, king_square) & theirs.pawn()
| lookup::knight(king_square) & theirs.knight()
| x & theirs.bishop()
| y & theirs.rook();
@ -982,10 +965,10 @@ impl Position {
let blockers = blockers ^ ours.king();
theirs
.king()
.map(|sq| d.king(sq))
.chain(theirs.bishop().map(|sq| d.bishop(sq, blockers)))
.chain(theirs.rook().map(|sq| d.rook(sq, blockers)))
.chain(theirs.knight().map(|sq| d.knight(sq)))
.map(|sq| lookup::king(sq))
.chain(theirs.bishop().map(|sq| lookup::bishop(sq, blockers)))
.chain(theirs.rook().map(|sq| lookup::rook(sq, blockers)))
.chain(theirs.knight().map(|sq| lookup::knight(sq)))
.chain(std::iter::once(
theirs.pawn().trans(!forward).trans(Direction::East),
))
@ -996,7 +979,7 @@ impl Position {
};
// king moves
visitor.moves(
(global_mask_to & d.king(king_square) & !us & !attacked).map(|to| RawMove {
(global_mask_to & lookup::king(king_square) & !us & !attacked).map(|to| RawMove {
kind: MoveType::KingMove,
from: king_square,
to,
@ -1054,17 +1037,17 @@ impl Position {
}
let blockers_x_ray = blockers & !(x | y);
let pinned_diagonally = (d.bishop(king_square, blockers_x_ray) & theirs.bishop())
.map(|sq| d.segment(king_square, sq))
let pinned_diagonally = (lookup::bishop(king_square, blockers_x_ray) & theirs.bishop())
.map(|sq| lookup::segment(king_square, sq))
.reduce_or();
let pinned_horizontally = (d.rook(king_square, blockers_x_ray) & theirs.rook())
.map(|sq| d.segment(king_square, sq))
let pinned_horizontally = (lookup::rook(king_square, blockers_x_ray) & theirs.rook())
.map(|sq| lookup::segment(king_square, sq))
.reduce_or();
let pinned = pinned_diagonally | pinned_horizontally;
let checker = checkers.first();
let block_check = checker
.map(|checker| d.segment(king_square, checker))
.map(|checker| lookup::segment(king_square, checker))
.unwrap_or(Bitboard(!0));
let target_mask = global_mask_to & block_check;
@ -1107,7 +1090,7 @@ impl Position {
// pawn attacks kingside
{
let targets =
(global_mask_from & ours.pawn() & (!pinned | d.ray(king_square, kside)))
(global_mask_from & ours.pawn() & (!pinned | lookup::ray(king_square, kside)))
.trans(kside)
& them
& target_mask;
@ -1129,7 +1112,7 @@ impl Position {
// pawn attacks queenside
{
let targets =
(global_mask_from & ours.pawn() & (!pinned | d.ray(king_square, qside)))
(global_mask_from & ours.pawn() & (!pinned | lookup::ray(king_square, qside)))
.trans(qside)
& them
& target_mask;
@ -1169,23 +1152,27 @@ impl Position {
if block_check.contains(to)
|| checker.is_none_or(|checker| checker == capture_square)
{
let candidates = d.pawn_attack(!turn, to) & ours.pawn();
let candidates = lookup::pawn_attack(!turn, to) & ours.pawn();
let blockers = blockers ^ capture_square.bitboard();
let pinned = pinned
| (d.rook(king_square, blockers & !(d.rook(king_square, blockers)))
& theirs.rook())
.map(|sq| d.segment(king_square, sq))
| (lookup::rook(
king_square,
blockers & !(lookup::rook(king_square, blockers)),
) & theirs.rook())
.map(|sq| lookup::segment(king_square, sq))
.reduce_or();
(global_mask_from & candidates & (!pinned | d.segment(king_square, to)))
.for_each(|from| {
visitor.en_passant_is_legal();
visitor.moves(std::iter::once(RawMove {
kind: MoveType::EnPassant,
from,
to,
role: Role::Pawn,
}))
})
(global_mask_from
& candidates
& (!pinned | lookup::segment(king_square, to)))
.for_each(|from| {
visitor.en_passant_is_legal();
visitor.moves(std::iter::once(RawMove {
kind: MoveType::EnPassant,
from,
to,
role: Role::Pawn,
}))
})
}
}
}
@ -1196,11 +1183,13 @@ impl Position {
let aux = |visitor: &mut T, role| {
for from in global_mask_from & *ours.get(role) & !pinned {
visitor.moves(
(d.targets(role, from, blockers) & !us & target_mask).map(|to| RawMove {
kind: MoveType::PieceMove,
from,
to,
role,
(lookup::targets(role, from, blockers) & !us & target_mask).map(|to| {
RawMove {
kind: MoveType::PieceMove,
from,
to,
role,
}
}),
)
}
@ -1229,14 +1218,14 @@ impl Position {
let aux = |visitor: &mut T, role, mask| {
for from in global_mask_from & *ours.get(role) & mask {
visitor.moves(
(global_mask_to & !us & pinned & d.line(king_square, from)).map(|to| {
RawMove {
(global_mask_to & !us & pinned & lookup::line(king_square, from)).map(
|to| RawMove {
kind: MoveType::PieceMove,
from,
to,
role,
}
}),
},
),
)
}
};
@ -1254,7 +1243,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;