From 01253b66deb096ddd2cd31155275b5a07017cdd9 Mon Sep 17 00:00:00 2001 From: Paul-Nicolas Madelaine Date: Thu, 13 Nov 2025 23:10:22 +0100 Subject: [PATCH] split code --- src/lib.rs | 5 +- src/moves.rs | 375 +++++++++++++++++++++++++++++++++++++++++++++ src/position.rs | 400 +++--------------------------------------------- src/san.rs | 1 + src/setup.rs | 2 +- src/uci.rs | 1 + 6 files changed, 403 insertions(+), 381 deletions(-) create mode 100644 src/moves.rs diff --git a/src/lib.rs b/src/lib.rs index 7df0d27..51c7a8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,9 @@ //! [`IllegalPositionReason`](setup::IllegalPositionReason) to know more). Legal moves are then //! generated using the [`Position::legal_moves`](position::Position::legal_moves) method or //! obtained from chess notation like [`UciMove`](uci::UciMove) or [`San`](san::San). Moves are -//! represented with the [`Move<'l>`](position::Move) type, which holds a reference to the origin +//! represented with the [`Move<'l>`](moves::Move) type, which holds a reference to the origin //! position (hence the lifetime), this ensures the move is played on the correct position. -//! Finally, moves are played using the [`Move::make`](position::Move) method which returns a new +//! Finally, moves are played using the [`Move::make`](moves::Move::make) method which returns a new //! [`Position`](position::Position), and on it goes. //! //! ## Example @@ -78,6 +78,7 @@ pub(crate) mod magics; pub mod bitboard; pub mod board; +pub mod moves; pub mod position; pub mod san; pub mod setup; diff --git a/src/moves.rs b/src/moves.rs new file mode 100644 index 0000000..1492c28 --- /dev/null +++ b/src/moves.rs @@ -0,0 +1,375 @@ +//! 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. +#[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 { + 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 { + 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 { + to: Bitboard, + candidates: Bitboard, + } + impl MoveGenImpl { + #[inline] + fn new(to: Square) -> Self { + Self { + to: to.bitboard(), + candidates: Bitboard::new(), + } + } + } + impl MoveGen for MoveGenImpl { + #[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(&mut self, iter: I) + where + I: Iterator + 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(m: &Move) -> SanInner { + let mut moves = MoveGenImpl::::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. +pub struct Moves<'l> { + position: &'l Position, + is_check: bool, + en_passant_is_legal: bool, + array: ArrayVec, +} + +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(&mut self, iter: I) + where + I: Iterator + 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> { + 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(&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.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, +} +impl<'l> Iterator for MovesIntoIter<'l> { + type Item = Move<'l>; + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|raw| Move { + position: self.position, + raw, + }) + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + 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(), + } + } +} diff --git a/src/position.rs b/src/position.rs index d86ed63..c69787f 100644 --- a/src/position.rs +++ b/src/position.rs @@ -1,9 +1,9 @@ //! **Move generation.** -use crate::array_vec::*; use crate::bitboard::*; use crate::board::*; use crate::lookup; +use crate::moves::*; use crate::san::*; use crate::setup::*; use crate::uci::*; @@ -57,7 +57,7 @@ use std::iter::FusedIterator; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Position(Setup); -const MAX_LEGAL_MOVES: usize = 218; +pub(crate) const MAX_LEGAL_MOVES: usize = 218; impl Position { /// Returns the initial position of a chess game. @@ -79,17 +79,7 @@ impl Position { /// Returns all the legal moves on the position. #[inline] pub fn legal_moves<'l>(&'l self) -> Moves<'l> { - fn aux(position: &Position, moves: &mut Moves) { - position.generate_moves(moves); - } - let mut moves = Moves { - position: self, - is_check: false, - en_passant_is_legal: false, - array: ArrayVec::new(), - }; - aux(self, &mut moves); - moves + Moves::compute(self) } /// Counts the legal moves on the position. @@ -287,7 +277,7 @@ impl Position { let mut moves = MoveGenImpl::::new(role, from.bitboard(), to.bitboard()); position.generate_moves(&mut moves); let raw = moves.found.ok_or(InvalidUciMove::Illegal)?; - Ok(Move { position, raw }) + Ok(unsafe { Move::new_unchecked(position, raw) }) } let promotion = if role == Role::Pawn { promotion.unwrap_or(Role::Pawn) @@ -408,7 +398,7 @@ impl Position { None => Err(InvalidSan::Illegal), Some(raw) => match moves.found_other { true => Err(InvalidSan::Ambiguous), - false => Ok(Move { position, raw }), + false => Ok(unsafe { Move::new_unchecked(position, raw) }), }, } } @@ -431,345 +421,17 @@ impl std::fmt::Debug for Position { } } -/// A legal move. -#[derive(Clone, Copy)] -pub struct Move<'l> { - position: &'l Position, - raw: RawMove, -} - -impl<'l> Move<'l> { - /// 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 { - self.raw.promotion() - } - - /// Returns `true` if the move is a capture. - #[inline] - pub fn is_capture(self) -> bool { - self.raw.kind == MoveType::EnPassant - || !((self.position.0.p_b_q | self.position.0.n_b_k | self.position.0.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 { - match self.raw.kind { - MoveType::EnPassant => Some(Role::Pawn), - _ => self.position.0.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 { - to: Bitboard, - candidates: Bitboard, - } - impl MoveGenImpl { - #[inline] - fn new(to: Square) -> Self { - Self { - to: to.bitboard(), - candidates: Bitboard::new(), - } - } - } - impl MoveGen for MoveGenImpl { - #[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(&mut self, iter: I) - where - I: Iterator + 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(m: &Move) -> SanInner { - let mut moves = MoveGenImpl::::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. -pub struct Moves<'l> { - position: &'l Position, - is_check: bool, - en_passant_is_legal: bool, - array: ArrayVec, -} - -impl<'l> Moves<'l> { - /// 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> { - 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(&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.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, -} -impl<'l> Iterator for MovesIntoIter<'l> { - type Item = Move<'l>; - #[inline] - fn next(&mut self) -> Option { - self.iter.next().map(|raw| Move { - position: self.position, - raw, - }) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - 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(), - } - } -} - #[derive(Clone, Copy)] pub(crate) struct RawMove { - kind: MoveType, - role: Role, - from: Square, - to: Square, + pub kind: MoveType, + pub role: Role, + pub from: Square, + pub to: Square, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u8)] -enum MoveType { +pub(crate) enum MoveType { CastleShort, CastleLong, KingMove, @@ -784,15 +446,15 @@ enum MoveType { impl RawMove { #[inline] - fn from(&self) -> Square { + pub fn from(&self) -> Square { self.from } #[inline] - fn to(&self) -> Square { + pub fn to(&self) -> Square { self.to } #[inline] - fn role(&self) -> Role { + pub fn role(&self) -> Role { match self.kind { MoveType::CastleShort | MoveType::CastleLong | MoveType::KingMove => Role::King, MoveType::PieceMove => self.role, @@ -805,14 +467,14 @@ impl RawMove { } } #[inline] - fn promotion(&self) -> Option { + pub fn promotion(&self) -> Option { match self.kind { MoveType::PawnAdvancePromotion | MoveType::PawnAttackPromotion => Some(self.role), _ => None, } } #[inline] - fn uci(&self) -> UciMove { + pub fn uci(&self) -> UciMove { UciMove { from: self.from(), to: self.to(), @@ -821,7 +483,7 @@ impl RawMove { } } -trait MoveGen { +pub(crate) trait MoveGen { #[inline] fn roles(&self, _role: Role) -> bool { true @@ -842,31 +504,13 @@ trait MoveGen { I: Iterator + ExactSizeIterator; } -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(&mut self, iter: I) - where - I: Iterator + ExactSizeIterator, - { - iter.for_each(|raw| unsafe { self.array.push_unchecked(raw) }); - } -} - impl Position { /// SAFETY: The position must be valid. pub(crate) unsafe fn from_setup(setup: Setup) -> Self { Self(setup) } - fn generate_moves(&self, moves: &mut T) + pub(crate) fn generate_moves(&self, moves: &mut T) where T: MoveGen, { @@ -1197,7 +841,7 @@ impl Position { } #[inline] - unsafe fn play_unchecked(&mut self, m: RawMove) { + pub(crate) unsafe fn play_unchecked(&mut self, m: RawMove) { let Self(setup) = self; setup.en_passant = OptionSquare::None; @@ -1351,13 +995,13 @@ fn aux_play_castle(setup: &mut Setup, side: CastlingSide) { setup.castling_rights.unset(setup.turn, CastlingSide::Long); } -struct MateMoveGenImpl { - is_check: bool, - is_mate: bool, +pub(crate) struct MateMoveGenImpl { + pub is_check: bool, + pub is_mate: bool, } impl MateMoveGenImpl { #[inline] - fn new() -> Self { + pub fn new() -> Self { Self { is_check: false, is_mate: true, diff --git a/src/san.rs b/src/san.rs index 1f4459d..473e6c7 100644 --- a/src/san.rs +++ b/src/san.rs @@ -10,6 +10,7 @@ //! Examples: *`e4`*, *`Qxd8#`*, *`O-O`*, *`h7h8=Q`* use crate::board::*; +use crate::moves::*; use crate::position::*; /// **The standard algebraic notation of a move.** diff --git a/src/setup.rs b/src/setup.rs index 68f1f78..6c21f29 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -634,7 +634,7 @@ pub enum IllegalPositionReason { /// There is an impossible number of pieces. /// /// Enforcing this enables to put an upper limit on the number of legal moves on any position, - /// allowing to reduce the size of [`Moves`]. + /// allowing to reduce the size of [`Moves`](crate::moves::Moves). TooMuchMaterial = 64, /// The pawn that can be taken en passant is pinned diagonally to the playing king. /// diff --git a/src/uci.rs b/src/uci.rs index c6e9e8f..5a2bceb 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -9,6 +9,7 @@ //! Examples: *`e2e4`*, *`d1d8`*, *`e1g1`* (short castling), *`h7h8q`* (promotion) use crate::board::*; +use crate::moves::*; use crate::position::*; /// **The UCI notation of a move.**