1
0
Fork 0
eschac/src/position.rs

1084 lines
36 KiB
Rust
Raw Normal View History

2024-04-29 02:00:26 +02:00
//! **Move generation.**
use crate::bitboard::*;
use crate::board::*;
2025-10-22 22:59:53 +02:00
use crate::lookup;
2025-11-13 23:10:22 +01:00
use crate::moves::*;
2024-04-29 02:00:26 +02:00
use crate::san::*;
use crate::setup::*;
use crate::uci::*;
use std::iter::ExactSizeIterator;
use std::iter::FusedIterator;
/// **A chess position.**
///
/// ## Game's state
///
/// This type records the following information:
/// - the position of pieces on the board
/// - the color to play
/// - the available castling rights
/// - the optional en passant target square (even if taking is not possible)
///
/// ## Validity & Legality
///
/// This type can only represent "valid" chess positions. Valid positions include but are not
/// limited to legal chess positions (i.e. positions that can be reached from a sequence of legal
/// moves starting on the initial position). It is neither computably feasible nor desirable to
/// reject all illegal positions. eschac will only reject illegal positions when chess rules can't
/// be applied unambiguously or when doing so enables some code optimisation. See
/// [`IllegalPositionReason`] for details about rejected positions.
///
/// ## Move generation & play
///
/// The [`legal_moves`](Position::legal_moves) method generates the list of all legal moves on the
/// position. [`UciMove::to_move`] and [`San::to_move`] can also be used to convert chess notation
/// to playable moves.
///
/// Playing a move is done through a copy-make interface. [`legal_moves`](Position::legal_moves)
/// returns a sequence of [`Move`] objects. Moves hold a reference to the position from where they
/// were computed. They can be played without further checks and without potential panics using
/// [`Move::make`].
///
/// ## En passant & Equality
///
/// The en passant target square is set when the [`Position`] is obtained after playing a double
/// pawn advance, even when there is no pawn to take or when taking is not legal. In that case,
/// [`remove_en_passant_target_square`](Position::remove_en_passant_target_square) can be used to
/// remove the target square from the record. As a consequence, [`Eq`] is not defined in accordance
/// with the FIDE laws of chess.
///
/// ## Ordering
//
/// The order on [`Position`] is defined only for use in data structures. Hence its only
/// requirement is to be efficient while respecting the [`Ord`] trait protocol. It should not be
/// considered stable.
2025-11-13 23:10:22 +01:00
#[must_use]
2024-04-29 02:00:26 +02:00
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
2025-10-22 22:59:53 +02:00
pub struct Position(Setup);
2024-04-29 02:00:26 +02:00
2025-11-13 23:10:22 +01:00
pub(crate) const MAX_LEGAL_MOVES: usize = 218;
2024-04-29 02:00:26 +02:00
impl Position {
/// Returns the initial position of a chess game.
///
/// i.e. `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -`
#[inline]
pub fn new() -> Self {
2025-10-22 22:59:53 +02:00
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,
})
2024-04-29 02:00:26 +02:00
}
/// Returns all the legal moves on the position.
#[inline]
pub fn legal_moves<'l>(&'l self) -> Moves<'l> {
2025-11-13 23:10:22 +01:00
Moves::compute(self)
2024-04-29 02:00:26 +02:00
}
/// Counts the legal moves on the position.
///
/// This is equivalent but faster than:
/// ```
/// # use eschac::position::Position;
/// # |position: &Position| -> usize {
/// position.legal_moves().len()
/// # };
/// ```
2025-11-13 23:10:22 +01:00
#[must_use]
2024-04-29 02:00:26 +02:00
#[inline]
pub fn count_legal_moves(&self) -> usize {
2025-11-05 23:47:00 +01:00
struct MoveGenImpl {
2024-04-29 02:00:26 +02:00
len: usize,
}
2025-11-05 23:47:00 +01:00
impl MoveGenImpl {
2024-04-29 02:00:26 +02:00
fn new() -> Self {
Self { len: 0 }
}
}
2025-11-05 23:47:00 +01:00
impl MoveGen for MoveGenImpl {
2024-04-29 02:00:26 +02:00
#[inline]
fn is_check(&mut self) {}
#[inline]
fn en_passant_is_legal(&mut self) {}
#[inline]
2025-11-05 23:47:00 +01:00
fn extend<I>(&mut self, iter: I)
2024-04-29 02:00:26 +02:00
where
I: Iterator<Item = RawMove> + ExactSizeIterator,
{
self.len += iter.len();
}
}
2025-11-05 23:47:00 +01:00
fn aux(position: &Position, moves: &mut MoveGenImpl) {
position.generate_moves(moves);
2024-04-29 02:00:26 +02:00
}
2025-11-05 23:47:00 +01:00
let mut moves = MoveGenImpl::new();
aux(self, &mut moves);
moves.len
2024-04-29 02:00:26 +02:00
}
2025-11-13 23:10:22 +01:00
/// Discards the en passant target square.
2024-04-29 02:00:26 +02:00
///
/// This function is useful to check for position equality, notably when implementing FIDE's
/// draw by repetition rules. Note that this function will remove the en passant target square
/// even if taking en passant is legal. If this is not desirable, it is the caller's
/// responsibility to rule out the legality of en passant before calling this function.
#[inline]
pub fn remove_en_passant_target_square(&mut self) {
2025-10-22 22:59:53 +02:00
self.0.en_passant = OptionSquare::None;
2024-04-29 02:00:26 +02:00
}
/// Discards the castling rights for the given color and side.
#[inline]
pub fn remove_castling_rights(&mut self, color: Color, side: CastlingSide) {
2025-10-22 22:59:53 +02:00
self.0.set_castling_rights(color, side, false);
2024-04-29 02:00:26 +02:00
}
/// Borrows the position as a [`Setup`].
#[inline]
pub fn as_setup(&self) -> &Setup {
2025-10-22 22:59:53 +02:00
&self.0
2024-04-29 02:00:26 +02:00
}
/// Converts the position into the [`Setup`] type, allowing to edit it without enforcing its legality.
2024-04-29 02:00:26 +02:00
#[inline]
pub fn into_setup(self) -> Setup {
2025-10-22 22:59:53 +02:00
self.0
2024-04-29 02:00:26 +02:00
}
2025-11-13 23:10:22 +01:00
/// Returns the position after after passing the turn to the other color,
/// and `None` if the king in check. On success, this inverts the color to
/// play and discards the en passant square.
2024-04-29 02:00:26 +02:00
pub fn pass(&self) -> Option<Self> {
2025-10-22 22:59:53 +02:00
let setup = &self.0;
2024-04-29 02:00:26 +02:00
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;
let b = setup.p_b_q & setup.n_b_k;
let n = setup.n_b_k ^ b ^ k;
let r = setup.r_q_k ^ q ^ k;
let p = setup.p_b_q ^ b ^ q;
let (us, them) = match setup.turn {
Color::White => (setup.w, blockers ^ setup.w),
Color::Black => (blockers ^ setup.w, setup.w),
};
let king_square = (us & k).next().unwrap();
let checkers = them
2025-10-22 22:59:53 +02:00
& (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 {
2024-04-29 02:00:26 +02:00
turn: !setup.turn,
en_passant: OptionSquare::None,
..setup.clone()
2025-10-22 22:59:53 +02:00
})
2024-04-29 02:00:26 +02:00
})
}
/// Returns the mirror image of the position (see [`Setup::mirror`]).
#[inline]
pub fn mirror(&self) -> Self {
2025-10-22 22:59:53 +02:00
Self(self.0.mirror())
2024-04-29 02:00:26 +02:00
}
/// Returns the number of possible chess games for a given number of moves.
///
/// This function is intended for benchmarking and is written as a simple recursion without any
/// caching.
pub fn perft(&self, depth: usize) -> u128 {
fn aux(position: &Position, depth: usize) -> u128 {
match depth.checked_sub(1) {
None => position.count_legal_moves() as u128,
Some(depth) => position
.legal_moves()
.into_iter()
.map(|m| aux(&m.make(), depth))
.sum(),
}
}
match depth.checked_sub(1) {
None => 1,
Some(depth) => aux(self, depth),
}
}
pub(crate) fn move_from_uci<'l>(&'l self, uci: UciMove) -> Result<Move<'l>, InvalidUciMove> {
2025-11-05 23:47:00 +01:00
struct MoveGenImpl<const ROLE: u8> {
2024-04-29 02:00:26 +02:00
role: Role,
from: Bitboard,
to: Bitboard,
found: Option<RawMove>,
}
2025-11-05 23:47:00 +01:00
impl<const ROLE: u8> MoveGenImpl<ROLE> {
2024-04-29 02:00:26 +02:00
#[inline]
fn new(role: Role, from: Bitboard, to: Bitboard) -> Self {
Self {
role,
from,
to,
found: None,
}
}
}
2025-11-05 23:47:00 +01:00
impl<const ROLE: u8> MoveGen for MoveGenImpl<ROLE> {
2024-04-29 02:00:26 +02:00
#[inline]
fn roles(&self, role: Role) -> bool {
role as u8 == ROLE
}
#[inline]
fn from(&self) -> Bitboard {
self.from
}
#[inline]
fn to(&self) -> Bitboard {
self.to
}
#[inline]
fn is_check(&mut self) {}
#[inline]
fn en_passant_is_legal(&mut self) {}
#[inline]
2025-11-05 23:47:00 +01:00
fn extend<I>(&mut self, iter: I)
2024-04-29 02:00:26 +02:00
where
I: Iterator<Item = RawMove> + ExactSizeIterator,
{
iter.for_each(|raw| {
debug_assert!(raw.role() as u8 == ROLE);
debug_assert!(self.from.contains(raw.from()));
debug_assert!(self.to.contains(raw.to()));
if raw.role == self.role {
debug_assert!(self.found.is_none());
self.found = Some(raw);
}
});
}
}
let UciMove {
from,
to,
promotion,
} = uci;
2025-10-22 22:59:53 +02:00
let role = self.0.get_role(from).ok_or(InvalidUciMove::Illegal)?;
2024-04-29 02:00:26 +02:00
#[inline]
fn aux<'l, const ROLE: u8>(
position: &'l Position,
role: Role,
from: Square,
to: Square,
) -> Result<Move<'l>, InvalidUciMove> {
2025-11-05 23:47:00 +01:00
let mut moves = MoveGenImpl::<ROLE>::new(role, from.bitboard(), to.bitboard());
position.generate_moves(&mut moves);
let raw = moves.found.ok_or(InvalidUciMove::Illegal)?;
2025-11-13 23:10:22 +01:00
Ok(unsafe { Move::new_unchecked(position, raw) })
2024-04-29 02:00:26 +02:00
}
let promotion = if role == Role::Pawn {
promotion.unwrap_or(Role::Pawn)
} else if promotion.is_some() {
return Err(InvalidUciMove::Illegal);
} else {
role
};
match role {
Role::Pawn => aux::<1>(self, promotion, from, to),
Role::Knight => aux::<2>(self, promotion, from, to),
Role::Bishop => aux::<3>(self, promotion, from, to),
Role::Rook => aux::<4>(self, promotion, from, to),
Role::Queen => aux::<5>(self, promotion, from, to),
Role::King => aux::<6>(self, promotion, from, to),
}
}
pub(crate) fn move_from_san<'l>(&'l self, san: &San) -> Result<Move<'l>, InvalidSan> {
2025-11-05 23:47:00 +01:00
struct MoveGenImpl<const ROLE: u8> {
2024-04-29 02:00:26 +02:00
role: Role,
from: Bitboard,
to: Bitboard,
found: Option<RawMove>,
found_other: bool,
}
2025-11-05 23:47:00 +01:00
impl<const ROLE: u8> MoveGenImpl<ROLE> {
2024-04-29 02:00:26 +02:00
#[inline]
fn new(role: Role, from: Bitboard, to: Bitboard) -> Self {
Self {
role,
from,
to,
found: None,
found_other: false,
}
}
}
2025-11-05 23:47:00 +01:00
impl<const ROLE: u8> MoveGen for MoveGenImpl<ROLE> {
2024-04-29 02:00:26 +02:00
#[inline]
fn roles(&self, role: Role) -> bool {
role as u8 == ROLE
}
#[inline]
fn from(&self) -> Bitboard {
self.from
}
#[inline]
fn to(&self) -> Bitboard {
self.to
}
#[inline]
fn is_check(&mut self) {}
#[inline]
fn en_passant_is_legal(&mut self) {}
#[inline]
2025-11-05 23:47:00 +01:00
fn extend<I>(&mut self, iter: I)
2024-04-29 02:00:26 +02:00
where
I: Iterator<Item = RawMove> + ExactSizeIterator,
{
iter.for_each(|raw| {
debug_assert!(raw.role() as u8 == ROLE);
debug_assert!(self.from.contains(raw.from()));
debug_assert!(self.to.contains(raw.to()));
if raw.role == self.role {
match self.found {
Some(_) => self.found_other = true,
None => self.found = Some(raw),
}
}
});
}
}
let (role, promotion, from, to) = match san.inner {
SanInner::Castle(CastlingSide::Short) => (
Role::King,
Role::King,
2025-11-05 23:37:02 +01:00
Square::from_coords(File::E, self.0.turn().home_rank()).bitboard(),
Square::from_coords(File::G, self.0.turn().home_rank()).bitboard(),
2024-04-29 02:00:26 +02:00
),
SanInner::Castle(CastlingSide::Long) => (
Role::King,
Role::King,
2025-11-05 23:37:02 +01:00
Square::from_coords(File::E, self.0.turn().home_rank()).bitboard(),
Square::from_coords(File::C, self.0.turn().home_rank()).bitboard(),
2024-04-29 02:00:26 +02:00
),
SanInner::Normal {
role,
target,
file,
rank,
promotion,
..
} => (
role,
if role == Role::Pawn {
promotion.unwrap_or(Role::Pawn)
} else if promotion.is_some() {
return Err(InvalidSan::Illegal);
} else {
role
},
file.map_or(!Bitboard::new(), |file| file.bitboard())
& rank.map_or(!Bitboard::new(), |rank| rank.bitboard()),
target.bitboard(),
),
};
#[inline]
fn aux<'l, const ROLE: u8>(
position: &'l Position,
role: Role,
from: Bitboard,
to: Bitboard,
) -> Result<Move<'l>, InvalidSan> {
2025-11-05 23:47:00 +01:00
let mut moves = MoveGenImpl::<ROLE>::new(role, from, to);
position.generate_moves(&mut moves);
match moves.found {
2024-04-29 02:00:26 +02:00
None => Err(InvalidSan::Illegal),
2025-11-05 23:47:00 +01:00
Some(raw) => match moves.found_other {
2024-04-29 02:00:26 +02:00
true => Err(InvalidSan::Ambiguous),
2025-11-13 23:10:22 +01:00
false => Ok(unsafe { Move::new_unchecked(position, raw) }),
2024-04-29 02:00:26 +02:00
},
}
}
match role {
Role::Pawn => aux::<1>(self, promotion, from, to),
Role::Knight => aux::<2>(self, promotion, from, to),
Role::Bishop => aux::<3>(self, promotion, from, to),
Role::Rook => aux::<4>(self, promotion, from, to),
Role::Queen => aux::<5>(self, promotion, from, to),
Role::King => aux::<6>(self, promotion, from, to),
}
}
}
2025-10-22 22:59:53 +02:00
impl std::fmt::Debug for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_tuple("Position")
2025-10-23 23:34:31 +02:00
.field(&TextRecord(self.as_setup()))
2025-10-22 22:59:53 +02:00
.finish()
}
}
2024-04-29 02:00:26 +02:00
#[derive(Clone, Copy)]
pub(crate) struct RawMove {
2025-11-13 23:10:22 +01:00
pub kind: MoveType,
pub role: Role,
pub from: Square,
pub to: Square,
2024-04-29 02:00:26 +02:00
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
2025-11-13 23:10:22 +01:00
pub(crate) enum MoveType {
2024-04-29 02:00:26 +02:00
CastleShort,
CastleLong,
KingMove,
PieceMove,
PawnAdvance,
PawnAttack,
PawnAdvancePromotion,
PawnAttackPromotion,
PawnDoubleAdvance,
EnPassant,
}
impl RawMove {
#[inline]
2025-11-13 23:10:22 +01:00
pub fn from(&self) -> Square {
2024-04-29 02:00:26 +02:00
self.from
}
#[inline]
2025-11-13 23:10:22 +01:00
pub fn to(&self) -> Square {
2024-04-29 02:00:26 +02:00
self.to
}
#[inline]
2025-11-13 23:10:22 +01:00
pub fn role(&self) -> Role {
2024-04-29 02:00:26 +02:00
match self.kind {
MoveType::CastleShort | MoveType::CastleLong | MoveType::KingMove => Role::King,
MoveType::PieceMove => self.role,
MoveType::PawnAdvance
| MoveType::PawnAttack
| MoveType::PawnAdvancePromotion
| MoveType::PawnAttackPromotion
| MoveType::PawnDoubleAdvance
| MoveType::EnPassant => Role::Pawn,
}
}
#[inline]
2025-11-13 23:10:22 +01:00
pub fn promotion(&self) -> Option<Role> {
2024-04-29 02:00:26 +02:00
match self.kind {
MoveType::PawnAdvancePromotion | MoveType::PawnAttackPromotion => Some(self.role),
_ => None,
}
}
#[inline]
2025-11-13 23:10:22 +01:00
pub fn uci(&self) -> UciMove {
2024-04-29 02:00:26 +02:00
UciMove {
from: self.from(),
to: self.to(),
promotion: self.promotion(),
}
}
}
2025-11-13 23:10:22 +01:00
pub(crate) trait MoveGen {
2024-04-29 02:00:26 +02:00
#[inline]
fn roles(&self, _role: Role) -> bool {
true
}
#[inline]
fn from(&self) -> Bitboard {
!Bitboard::new()
}
#[inline]
fn to(&self) -> Bitboard {
!Bitboard::new()
}
fn is_check(&mut self);
fn en_passant_is_legal(&mut self);
2025-11-05 23:47:00 +01:00
fn extend<I>(&mut self, iter: I)
2024-04-29 02:00:26 +02:00
where
I: Iterator<Item = RawMove> + ExactSizeIterator;
}
impl Position {
/// SAFETY: The position must be valid.
pub(crate) unsafe fn from_setup(setup: Setup) -> Self {
2025-10-22 22:59:53 +02:00
Self(setup)
2024-04-29 02:00:26 +02:00
}
2025-11-13 23:10:22 +01:00
pub(crate) fn generate_moves<T>(&self, moves: &mut T)
2024-04-29 02:00:26 +02:00
where
2025-11-05 23:47:00 +01:00
T: MoveGen,
2024-04-29 02:00:26 +02:00
{
2025-11-05 23:47:00 +01:00
let global_mask_from = moves.from();
let global_mask_to = moves.to();
2024-04-29 02:00:26 +02:00
let Setup {
w,
p_b_q,
n_b_k,
r_q_k,
turn,
en_passant,
castling_rights,
2025-10-22 22:59:53 +02:00
} = self.0;
2024-04-29 02:00:26 +02:00
let blockers = p_b_q | n_b_k | r_q_k;
let (us, them) = match turn {
Color::White => (w, blockers ^ w),
Color::Black => (blockers ^ w, w),
};
let k = n_b_k & r_q_k;
let q = p_b_q & r_q_k;
let b = p_b_q & n_b_k;
let n = n_b_k ^ b ^ k;
let r = r_q_k ^ q ^ k;
let p = p_b_q ^ b ^ q;
let ours = ByRole([p & us, n & us, b & us, r & us, q & us, k & us]);
let theirs = ByRole([
p & them,
n & them,
(q | b) & them,
(q | r) & them,
q & them,
k & them,
]);
let king_square = unsafe {
// SAFETY: the position is legal
ours.king().first().unwrap_unchecked()
};
let forward = turn.forward();
2025-10-22 22:59:53 +02:00
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()
2024-04-29 02:00:26 +02:00
| x & theirs.bishop()
| y & theirs.rook();
2025-11-05 23:47:00 +01:00
if moves.roles(Role::King) && global_mask_from.contains(king_square) {
2024-04-29 02:00:26 +02:00
let attacked = {
let blockers = blockers ^ ours.king();
theirs
.king()
2025-10-22 22:59:53 +02:00
.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)))
2024-04-29 02:00:26 +02:00
.chain(std::iter::once(
theirs.pawn().trans(!forward).trans(Direction::East),
))
.chain(std::iter::once(
theirs.pawn().trans(!forward).trans(Direction::West),
))
.reduce_or()
};
// king moves
2025-11-05 23:47:00 +01:00
moves.extend(
2025-10-22 22:59:53 +02:00
(global_mask_to & lookup::king(king_square) & !us & !attacked).map(|to| RawMove {
2024-04-29 02:00:26 +02:00
kind: MoveType::KingMove,
from: king_square,
to,
role: Role::King,
}),
);
// castling
if castling_rights.get(turn, CastlingSide::Short) {
let (x, y) = match turn {
Color::White => (Bitboard(0x0000000000000070), Bitboard(0x0000000000000060)),
Color::Black => (Bitboard(0x7000000000000000), Bitboard(0x6000000000000000)),
};
if (attacked & x | blockers & y).is_empty() {
let from = king_square;
let to = unsafe {
from.trans_unchecked(Direction::East)
.trans_unchecked(Direction::East)
};
if global_mask_to.contains(to) {
2025-11-05 23:47:00 +01:00
moves.extend(std::iter::once(RawMove {
2024-04-29 02:00:26 +02:00
kind: MoveType::CastleShort,
from,
to,
role: Role::King,
}))
}
}
}
if castling_rights.get(turn, CastlingSide::Long) {
let (x, y) = match turn {
Color::White => (Bitboard(0x000000000000001C), Bitboard(0x000000000000000E)),
Color::Black => (Bitboard(0x1C00000000000000), Bitboard(0x0E00000000000000)),
};
if (attacked & x | blockers & y).is_empty() {
let from = king_square;
let to = unsafe {
from.trans_unchecked(Direction::West)
.trans_unchecked(Direction::West)
};
if global_mask_to.contains(to) {
2025-11-05 23:47:00 +01:00
moves.extend(std::iter::once(RawMove {
2024-04-29 02:00:26 +02:00
kind: MoveType::CastleLong,
from,
to,
role: Role::King,
}))
}
}
}
}
if checkers.len() > 1 {
2025-11-05 23:47:00 +01:00
moves.is_check();
2024-04-29 02:00:26 +02:00
return;
}
2025-10-22 22:59:53 +02:00
let blockers_x_ray = blockers & !(x | y);
let pinned = ((lookup::bishop(king_square, blockers_x_ray) & theirs.bishop())
| (lookup::rook(king_square, blockers_x_ray) & theirs.rook()))
.map(|sq| lookup::segment(king_square, sq))
.reduce_or();
2025-10-22 22:59:53 +02:00
2024-04-29 02:00:26 +02:00
let checker = checkers.first();
let block_check = checker
2025-10-22 22:59:53 +02:00
.map(|checker| lookup::segment(king_square, checker))
2024-04-29 02:00:26 +02:00
.unwrap_or(Bitboard(!0));
let target_mask = global_mask_to & block_check;
// pawns
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Pawn) {
2024-04-29 02:00:26 +02:00
let kside = match turn {
Color::White => Direction::NorthEast,
Color::Black => Direction::SouthEast,
};
let qside = match turn {
Color::White => Direction::NorthWest,
Color::Black => Direction::SouthWest,
};
let third_rank = match turn {
Color::White => Rank::Third,
Color::Black => Rank::Sixth,
};
let adv = (global_mask_from & ours.pawn() & (!pinned | king_square.file().bitboard()))
.trans(forward)
& !blockers;
let promotion = turn.promotion_rank().bitboard();
// pawn advances
{
let targets = adv & target_mask;
2025-11-05 23:47:00 +01:00
moves.extend((targets & !promotion).map(|to| RawMove {
2024-04-29 02:00:26 +02:00
kind: MoveType::PawnAdvance,
from: unsafe { to.trans_unchecked(!forward) },
to,
role: Role::Pawn,
}));
2025-11-05 23:47:00 +01:00
moves.extend(MoveAndPromote::new((targets & promotion).map(|to| {
2024-04-29 02:00:26 +02:00
RawMove {
kind: MoveType::PawnAdvancePromotion,
from: unsafe { to.trans_unchecked(!forward) },
to,
role: Role::Pawn,
}
})));
}
// pawn attacks kingside
{
let targets =
2025-10-22 22:59:53 +02:00
(global_mask_from & ours.pawn() & (!pinned | lookup::ray(king_square, kside)))
2024-04-29 02:00:26 +02:00
.trans(kside)
& them
& target_mask;
2025-11-05 23:47:00 +01:00
moves.extend((targets & !promotion).map(|to| RawMove {
2024-04-29 02:00:26 +02:00
kind: MoveType::PawnAttack,
from: unsafe { to.trans_unchecked(!kside) },
to,
role: Role::Pawn,
}));
2025-11-05 23:47:00 +01:00
moves.extend(MoveAndPromote::new((targets & promotion).map(|to| {
2024-04-29 02:00:26 +02:00
RawMove {
kind: MoveType::PawnAttackPromotion,
from: unsafe { to.trans_unchecked(!kside) },
to,
role: Role::Pawn,
}
})));
}
// pawn attacks queenside
{
let targets =
2025-10-22 22:59:53 +02:00
(global_mask_from & ours.pawn() & (!pinned | lookup::ray(king_square, qside)))
2024-04-29 02:00:26 +02:00
.trans(qside)
& them
& target_mask;
2025-11-05 23:47:00 +01:00
moves.extend((targets & !promotion).map(|to| RawMove {
2024-04-29 02:00:26 +02:00
kind: MoveType::PawnAttack,
from: unsafe { to.trans_unchecked(!qside) },
to,
role: Role::Pawn,
}));
2025-11-05 23:47:00 +01:00
moves.extend(MoveAndPromote::new((targets & promotion).map(|to| {
2024-04-29 02:00:26 +02:00
RawMove {
kind: MoveType::PawnAttackPromotion,
from: unsafe { to.trans_unchecked(!qside) },
to,
role: Role::Pawn,
}
})));
}
// pawn double advances
2025-11-05 23:47:00 +01:00
moves.extend(
2024-04-29 02:00:26 +02:00
((adv & third_rank.bitboard()).trans(forward) & !blockers & target_mask).map(
|to| RawMove {
kind: MoveType::PawnDoubleAdvance,
from: unsafe { to.trans_unchecked(!forward).trans_unchecked(!forward) },
to,
role: Role::Pawn,
},
),
);
// en passant
if let Some(to) = en_passant.try_into_square() {
if global_mask_to.contains(to) {
let capture_square = unsafe {
// SAFETY: the position is legal
to.trans_unchecked(!forward)
};
if block_check.contains(to)
|| checker.is_none_or(|checker| checker == capture_square)
{
2025-10-22 22:59:53 +02:00
let candidates = lookup::pawn_attack(!turn, to) & ours.pawn();
2024-04-29 02:00:26 +02:00
let blockers = blockers ^ capture_square.bitboard();
let pinned = pinned
2025-10-22 22:59:53 +02:00
| (lookup::rook(
king_square,
blockers & !(lookup::rook(king_square, blockers)),
) & theirs.rook())
.map(|sq| lookup::segment(king_square, sq))
2024-04-29 02:00:26 +02:00
.reduce_or();
2025-10-22 22:59:53 +02:00
(global_mask_from
& candidates
& (!pinned | lookup::segment(king_square, to)))
.for_each(|from| {
2025-11-05 23:47:00 +01:00
moves.en_passant_is_legal();
moves.extend(std::iter::once(RawMove {
2025-10-22 22:59:53 +02:00
kind: MoveType::EnPassant,
from,
to,
role: Role::Pawn,
}))
})
2024-04-29 02:00:26 +02:00
}
}
}
}
// pieces not pinned
{
2025-11-05 23:47:00 +01:00
let aux = |moves: &mut T, role| {
2024-04-29 02:00:26 +02:00
for from in global_mask_from & *ours.get(role) & !pinned {
2025-11-05 23:47:00 +01:00
moves.extend(
2025-10-22 22:59:53 +02:00
(lookup::targets(role, from, blockers) & !us & target_mask).map(|to| {
RawMove {
kind: MoveType::PieceMove,
from,
to,
role,
}
2024-04-29 02:00:26 +02:00
}),
)
}
};
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Knight) {
aux(moves, Role::Knight)
2024-04-29 02:00:26 +02:00
}
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Bishop) {
aux(moves, Role::Bishop)
2024-04-29 02:00:26 +02:00
}
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Rook) {
aux(moves, Role::Rook)
2024-04-29 02:00:26 +02:00
}
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Queen) {
aux(moves, Role::Queen)
2024-04-29 02:00:26 +02:00
}
}
if checker.is_some() {
2025-11-05 23:47:00 +01:00
moves.is_check();
2024-04-29 02:00:26 +02:00
return;
}
// pinned pieces
{
let aux = |moves: &mut T, role, role_mask| {
for from in global_mask_from & pinned & role_mask & *ours.get(role) {
2025-11-05 23:47:00 +01:00
moves.extend(
2025-10-22 22:59:53 +02:00
(global_mask_to & !us & pinned & lookup::line(king_square, from)).map(
|to| RawMove {
kind: MoveType::PieceMove,
from,
to,
role,
2025-10-22 22:59:53 +02:00
},
),
2024-04-29 02:00:26 +02:00
)
}
};
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Bishop) {
aux(moves, Role::Bishop, lookup::bishop_lines(king_square));
2024-04-29 02:00:26 +02:00
}
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Rook) {
aux(moves, Role::Rook, lookup::rook_lines(king_square));
2024-04-29 02:00:26 +02:00
}
2025-11-05 23:47:00 +01:00
if moves.roles(Role::Queen) {
aux(moves, Role::Queen, !Bitboard::new());
2024-04-29 02:00:26 +02:00
}
}
}
#[inline]
2025-11-13 23:10:22 +01:00
pub(crate) unsafe fn play_unchecked(&mut self, m: RawMove) {
2025-10-22 22:59:53 +02:00
let Self(setup) = self;
2024-04-29 02:00:26 +02:00
setup.en_passant = OptionSquare::None;
let RawMove {
kind,
from,
to,
role,
} = m;
match kind {
MoveType::CastleShort => aux_play_castle(setup, CastlingSide::Short),
MoveType::CastleLong => aux_play_castle(setup, CastlingSide::Long),
MoveType::KingMove => aux_play_normal(setup, Role::King, from, to),
MoveType::PieceMove => aux_play_normal(setup, role, from, to),
MoveType::PawnAdvance => aux_play_pawn_advance(setup, Role::Pawn, from, to),
MoveType::PawnAttack => aux_play_normal(setup, Role::Pawn, from, to),
MoveType::PawnAdvancePromotion => aux_play_pawn_advance(setup, role, from, to),
MoveType::PawnAttackPromotion => aux_play_normal(setup, role, from, to),
MoveType::PawnDoubleAdvance => {
aux_play_pawn_advance(setup, Role::Pawn, from, to);
2025-11-05 23:37:02 +01:00
setup.en_passant = OptionSquare::new(Some(Square::from_coords(
2024-04-29 02:00:26 +02:00
from.file(),
match setup.turn {
Color::White => Rank::Third,
Color::Black => Rank::Sixth,
},
)));
}
MoveType::EnPassant => {
let direction = !setup.turn.forward();
let x = (unsafe { to.trans_unchecked(direction) }).bitboard();
setup.p_b_q ^= x;
setup.w &= !x;
aux_play_pawn_advance(setup, Role::Pawn, from, to);
}
}
setup.turn = !setup.turn;
}
}
#[inline]
fn aux_play_normal(setup: &mut Setup, role: Role, from: Square, target: Square) {
let from = from.bitboard();
let to = target.bitboard();
let mask = !(from | to);
setup.w &= mask;
setup.p_b_q &= mask;
setup.n_b_k &= mask;
setup.r_q_k &= mask;
2025-11-05 23:37:02 +01:00
if target == Square::from_coords(File::H, setup.turn.promotion_rank()) {
2024-04-29 02:00:26 +02:00
setup
.castling_rights
.unset(!setup.turn, CastlingSide::Short);
}
2025-11-05 23:37:02 +01:00
if target == Square::from_coords(File::A, setup.turn.promotion_rank()) {
2024-04-29 02:00:26 +02:00
setup.castling_rights.unset(!setup.turn, CastlingSide::Long);
}
match role {
Role::King => {
setup.n_b_k |= to;
setup.r_q_k |= to;
setup.castling_rights.unset(setup.turn, CastlingSide::Short);
setup.castling_rights.unset(setup.turn, CastlingSide::Long);
}
Role::Queen => {
setup.p_b_q |= to;
setup.r_q_k |= to;
}
Role::Bishop => {
setup.p_b_q |= to;
setup.n_b_k |= to;
}
Role::Knight => {
setup.n_b_k |= to;
}
Role::Rook => {
setup.r_q_k |= to;
2025-11-05 23:37:02 +01:00
if from == Square::from_coords(File::H, setup.turn.home_rank()).bitboard() {
2024-04-29 02:00:26 +02:00
setup.castling_rights.unset(setup.turn, CastlingSide::Short);
}
2025-11-05 23:37:02 +01:00
if from == Square::from_coords(File::A, setup.turn.home_rank()).bitboard() {
2024-04-29 02:00:26 +02:00
setup.castling_rights.unset(setup.turn, CastlingSide::Long);
}
}
Role::Pawn => {
setup.p_b_q |= to;
}
}
if setup.turn == Color::White {
setup.w |= to;
}
}
#[inline]
fn aux_play_pawn_advance(setup: &mut Setup, role: Role, from: Square, to: Square) {
let from = from.bitboard();
let to = to.bitboard();
match role {
Role::King => unreachable!(),
Role::Queen => {
setup.p_b_q ^= from | to;
setup.r_q_k |= to;
}
Role::Bishop => {
setup.p_b_q ^= from | to;
setup.n_b_k |= to;
}
Role::Knight => {
setup.p_b_q ^= from;
setup.n_b_k |= to;
}
Role::Rook => {
setup.p_b_q ^= from;
setup.r_q_k |= to;
}
Role::Pawn => setup.p_b_q ^= from | to,
}
if setup.turn == Color::White {
setup.w ^= from | to;
}
}
#[inline]
fn aux_play_castle(setup: &mut Setup, side: CastlingSide) {
let rank = setup.turn.home_rank();
let (king_flip, rook_flip) = match side {
CastlingSide::Short => (
2025-11-05 23:37:02 +01:00
Square::from_coords(File::E, rank).bitboard()
| Square::from_coords(File::G, rank).bitboard(),
Square::from_coords(File::H, rank).bitboard()
| Square::from_coords(File::F, rank).bitboard(),
2024-04-29 02:00:26 +02:00
),
CastlingSide::Long => (
2025-11-05 23:37:02 +01:00
Square::from_coords(File::E, rank).bitboard()
| Square::from_coords(File::C, rank).bitboard(),
Square::from_coords(File::A, rank).bitboard()
| Square::from_coords(File::D, rank).bitboard(),
2024-04-29 02:00:26 +02:00
),
};
if setup.turn == Color::White {
setup.w ^= king_flip | rook_flip;
}
setup.n_b_k ^= king_flip;
setup.r_q_k ^= king_flip | rook_flip;
setup.castling_rights.unset(setup.turn, CastlingSide::Short);
setup.castling_rights.unset(setup.turn, CastlingSide::Long);
}
2025-11-13 23:10:22 +01:00
pub(crate) struct MateMoveGenImpl {
pub is_check: bool,
pub is_mate: bool,
2024-04-29 02:00:26 +02:00
}
2025-11-05 23:47:00 +01:00
impl MateMoveGenImpl {
2024-04-29 02:00:26 +02:00
#[inline]
2025-11-13 23:10:22 +01:00
pub fn new() -> Self {
2024-04-29 02:00:26 +02:00
Self {
is_check: false,
is_mate: true,
}
}
}
2025-11-05 23:47:00 +01:00
impl MoveGen for MateMoveGenImpl {
2024-04-29 02:00:26 +02:00
#[inline]
fn is_check(&mut self) {
self.is_check = true;
}
#[inline]
fn en_passant_is_legal(&mut self) {}
#[inline]
2025-11-05 23:47:00 +01:00
fn extend<I>(&mut self, iter: I)
2024-04-29 02:00:26 +02:00
where
I: Iterator<Item = RawMove> + ExactSizeIterator,
{
self.is_mate &= iter.len() == 0;
}
}
2025-10-22 22:59:53 +02:00
struct MoveAndPromote<I: Iterator<Item = RawMove> + ExactSizeIterator + FusedIterator> {
inner: I,
cur: std::mem::MaybeUninit<RawMove>,
role: Role,
}
impl<I> MoveAndPromote<I>
where
I: Iterator<Item = RawMove> + ExactSizeIterator + FusedIterator,
{
#[inline]
fn new(inner: I) -> Self {
Self {
inner,
cur: std::mem::MaybeUninit::uninit(),
role: Role::King,
}
}
}
impl<I> Iterator for MoveAndPromote<I>
where
I: Iterator<Item = RawMove> + ExactSizeIterator + FusedIterator,
{
type Item = RawMove;
#[inline]
fn next(&mut self) -> Option<RawMove> {
if self.role == Role::King {
self.cur.write(self.inner.next()?);
self.role = Role::Knight;
}
let raw = unsafe { self.cur.assume_init() };
let res = RawMove {
role: self.role,
..raw
};
self.role = unsafe { Role::transmute((self.role as u8).unchecked_add(1)) };
Some(res)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<I> FusedIterator for MoveAndPromote<I> where
I: Iterator<Item = RawMove> + ExactSizeIterator + FusedIterator
{
}
impl<I> ExactSizeIterator for MoveAndPromote<I>
where
I: Iterator<Item = RawMove> + ExactSizeIterator + FusedIterator,
{
#[inline]
fn len(&self) -> usize {
unsafe { self.inner.len().unchecked_mul(4) }
}
}