1
0
Fork 0
eschac/src/moves.rs
2025-11-13 23:10:22 +01:00

377 lines
11 KiB
Rust

//! Move representation.
use crate::array_vec::*;
use crate::bitboard::*;
use crate::board::*;
use crate::position::*;
use crate::san::*;
use crate::uci::*;
use std::iter::ExactSizeIterator;
use std::iter::FusedIterator;
/// A legal move.
#[must_use]
#[derive(Clone, Copy)]
pub struct Move<'l> {
position: &'l Position,
raw: RawMove,
}
impl<'l> Move<'l> {
pub(crate) unsafe fn new_unchecked(position: &'l Position, raw: RawMove) -> Self {
Self { position, raw }
}
/// Returns the position after playing the move.
///
/// This sets the en passant square after a double pawn advance, even when there is no pawn to
/// capture or when capturing is not legal.
pub fn make(self) -> Position {
let mut position = self.position.clone();
unsafe { position.play_unchecked(self.raw) };
position
}
/// Returns the position the move is played on.
#[inline]
pub fn position(self) -> &'l Position {
self.position
}
/// Returns the type of piece that moves.
#[inline]
pub fn role(self) -> Role {
self.raw.role()
}
/// Returns the origin square of the move.
#[inline]
pub fn from(self) -> Square {
self.raw.from()
}
/// Returns the target square of the move.
#[inline]
pub fn to(self) -> Square {
self.raw.to()
}
/// Returns the type of piece that the pawn is promoted to, if the move is a promotion.
#[inline]
pub fn promotion(self) -> Option<Role> {
self.raw.promotion()
}
/// Returns `true` if the move is a capture.
#[inline]
pub fn is_capture(self) -> bool {
let setup = self.position.as_setup();
self.raw.kind == MoveType::EnPassant
|| !((setup.p_b_q | setup.n_b_k | setup.r_q_k) & self.to().bitboard()).is_empty()
}
/// Returns the type of piece that is captured, if the move is a capture.
#[inline]
pub fn captured(self) -> Option<Role> {
match self.raw.kind {
MoveType::EnPassant => Some(Role::Pawn),
_ => self.position.as_setup().get_role(self.raw.to),
}
}
/// Returns the UCI notation of the move.
#[inline]
pub fn to_uci(self) -> UciMove {
self.raw.uci()
}
/// Returns the standard algebraic notation of the move.
pub fn to_san(self) -> San {
struct MoveGenImpl<const ROLE: u8> {
to: Bitboard,
candidates: Bitboard,
}
impl<const ROLE: u8> MoveGenImpl<ROLE> {
#[inline]
fn new(to: Square) -> Self {
Self {
to: to.bitboard(),
candidates: Bitboard::new(),
}
}
}
impl<const ROLE: u8> MoveGen for MoveGenImpl<ROLE> {
#[inline]
fn roles(&self, role: Role) -> bool {
role as u8 == ROLE
}
#[inline]
fn to(&self) -> Bitboard {
self.to
}
#[inline]
fn is_check(&mut self) {}
#[inline]
fn en_passant_is_legal(&mut self) {}
#[inline]
fn extend<I>(&mut self, iter: I)
where
I: Iterator<Item = RawMove> + ExactSizeIterator,
{
iter.for_each(|raw| {
debug_assert!(raw.role() as u8 == ROLE);
debug_assert!(self.to.contains(raw.to()));
self.candidates.insert(raw.from);
});
}
}
San {
inner: match self.raw.kind {
MoveType::CastleShort => SanInner::Castle(CastlingSide::Short),
MoveType::CastleLong => SanInner::Castle(CastlingSide::Long),
MoveType::PawnAdvance
| MoveType::PawnAdvancePromotion
| MoveType::PawnDoubleAdvance => SanInner::Normal {
role: Role::Pawn,
file: None,
rank: None,
capture: false,
target: self.to(),
promotion: self.promotion(),
},
MoveType::PawnAttack | MoveType::PawnAttackPromotion | MoveType::EnPassant => {
SanInner::Normal {
role: Role::Pawn,
file: Some(self.from().file()),
rank: None,
capture: true,
target: self.to(),
promotion: self.promotion(),
}
}
_ => {
fn aux<const ROLE: u8>(m: &Move) -> SanInner {
let mut moves = MoveGenImpl::<ROLE>::new(m.to());
m.position().generate_moves(&mut moves);
let candidates = moves.candidates;
let (file, rank) = if candidates == m.from().bitboard() {
(None, None)
} else if candidates & m.from().file().bitboard() == m.from().bitboard() {
(Some(m.from().file()), None)
} else if candidates & m.from().rank().bitboard() == m.from().bitboard() {
(None, Some(m.from().rank()))
} else {
(Some(m.from().file()), Some(m.from().rank()))
};
SanInner::Normal {
role: m.role(),
file,
rank,
capture: m.is_capture(),
target: m.to(),
promotion: None,
}
}
match self.role() {
Role::Pawn => aux::<1>(&self),
Role::Knight => aux::<2>(&self),
Role::Bishop => aux::<3>(&self),
Role::Rook => aux::<4>(&self),
Role::Queen => aux::<5>(&self),
Role::King => aux::<6>(&self),
}
}
},
suffix: {
let pos = self.make();
let mut moves = MateMoveGenImpl::new();
pos.generate_moves(&mut moves);
moves.is_check.then(|| match moves.is_mate {
true => SanSuffix::Checkmate,
false => SanSuffix::Check,
})
},
}
}
}
/// A list of legal moves on the same position.
///
/// It can be obtained using the [`Position::legal_moves`] method. This type is an iterator over
/// [`Move`] objects.
#[must_use]
pub struct Moves<'l> {
position: &'l Position,
is_check: bool,
en_passant_is_legal: bool,
array: ArrayVec<RawMove, MAX_LEGAL_MOVES>,
}
impl<'l> MoveGen for Moves<'l> {
#[inline]
fn is_check(&mut self) {
self.is_check = true;
}
#[inline]
fn en_passant_is_legal(&mut self) {
self.en_passant_is_legal = true;
}
#[inline]
fn extend<I>(&mut self, iter: I)
where
I: Iterator<Item = RawMove> + ExactSizeIterator,
{
iter.for_each(|raw| unsafe { self.array.push_unchecked(raw) });
}
}
impl<'l> Moves<'l> {
#[inline]
pub(crate) fn compute(position: &'l Position) -> Moves<'l> {
fn aux(position: &Position, moves: &mut Moves) {
position.generate_moves(moves);
}
let mut moves = Moves {
position,
is_check: false,
en_passant_is_legal: false,
array: ArrayVec::new(),
};
aux(position, &mut moves);
moves
}
/// Returns the position on which the moves are played.
#[inline]
pub fn position(&self) -> &Position {
self.position
}
/// Iterates over the moves.
#[inline]
pub fn iter(&'l self) -> MovesIter<'l> {
MovesIter {
position: self.position,
iter: (&self.array).into_iter(),
}
}
/// Returns the number of moves in the list.
#[inline]
pub fn len(&self) -> usize {
self.array.len()
}
/// Returns `true` if en passant is legal.
#[inline]
pub fn en_passant_is_legal(&self) -> bool {
self.en_passant_is_legal
}
/// Returns `true` if the king is in check.
#[inline]
pub fn is_check(&self) -> bool {
self.is_check
}
/// Returns the move at the given index, if it exists.
#[inline]
pub fn get(&self, index: usize) -> Option<Move<'l>> {
self.array.get(index).copied().map(|raw| Move {
position: self.position,
raw,
})
}
/// Sorts the moves in the list.
///
/// See [`slice::sort_unstable_by`] for potential panics.
#[inline]
pub fn sort_by<F>(&mut self, mut compare: F)
where
F: FnMut(Move, Move) -> std::cmp::Ordering,
{
self.array.as_slice_mut().sort_unstable_by(|a, b| {
compare(
Move {
position: self.position,
raw: *a,
},
Move {
position: self.position,
raw: *b,
},
)
});
}
}
/// An iterator over legal moves.
pub struct MovesIter<'l> {
position: &'l Position,
iter: ArrayVecIter<'l, RawMove, MAX_LEGAL_MOVES>,
}
impl<'l> Iterator for MovesIter<'l> {
type Item = Move<'l>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|raw| Move {
position: self.position,
raw,
})
}
}
impl<'l> FusedIterator for MovesIter<'l> {}
impl<'l> ExactSizeIterator for MovesIter<'l> {
#[inline]
fn len(&self) -> usize {
self.iter.len()
}
}
impl<'l> IntoIterator for &'l Moves<'l> {
type Item = Move<'l>;
type IntoIter = MovesIter<'l>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
/// An iterator over legal moves.
pub struct MovesIntoIter<'l> {
position: &'l Position,
iter: ArrayVecIntoIter<RawMove, MAX_LEGAL_MOVES>,
}
impl<'l> Iterator for MovesIntoIter<'l> {
type Item = Move<'l>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|raw| Move {
position: self.position,
raw,
})
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'l> FusedIterator for MovesIntoIter<'l> {}
impl<'l> ExactSizeIterator for MovesIntoIter<'l> {
#[inline]
fn len(&self) -> usize {
self.iter.len()
}
}
impl<'l> IntoIterator for Moves<'l> {
type Item = Move<'l>;
type IntoIter = MovesIntoIter<'l>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
MovesIntoIter {
position: self.position,
iter: self.array.into_iter(),
}
}
}