//! Move representation. use crate::array_vec::*; use crate::bitboard::*; use crate::board::*; use crate::position::*; use crate::san::*; use crate::uci::*; use core::convert::Infallible; use core::iter::{ExactSizeIterator, FusedIterator}; use core::ops::ControlFlow; /// A legal move. #[must_use] #[derive(Clone, Copy)] pub struct Move<'l> { position: &'l Position, raw: RawMove, } impl<'l> Move<'l> { #[inline] pub(crate) unsafe fn new(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. #[inline] 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 [p_b_q, n_b_k, r_q_k, _] = self.position.as_setup().bitboards; self.raw.kind == MoveType::EnPassant || !((p_b_q | n_b_k | 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().role(self.raw.to), } } /// Returns the raw data of the move. #[inline] pub fn raw(self) -> RawMove { self.raw } /// Returns the UCI notation of the move. #[inline] pub fn to_uci(self) -> UciMove { self.raw.to_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 extend(&mut self, iter: I) -> ControlFlow 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); }); ControlFlow::Continue(()) } } 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()); let ControlFlow::Continue(()) = 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(); let ControlFlow::Continue(()) = pos.generate_moves(&mut moves); let MateMoveGenImpl { is_check, is_mate } = moves; match (is_check, is_mate) { (false, _) => None, (true, false) => Some(SanSuffix::Check), (true, true) => Some(SanSuffix::Checkmate), } }, } } } /// The list of all the legal moves on a position. It is returned by [`Position::legal_moves`]. /// /// This type lies on the stack and has a fixed capacity of 218 moves, as there exists no reachable /// position with more legal moves[^1]. /// /// [^1]: In 1964, Nenad Petrović published a /// [composition](https://lichess.org/editor/R6R/3Q4/1Q4Q1/4Q3/2Q4Q/Q4Q2/pp1Q4/kBNN1KB1_w_-_-_0_1) /// with 218 legal moves. 60 years later, this was proven to be optimal by /// [Tobs40](https://lichess.org/@/Tobs40/blog/why-a-reachable-position-can-have-at-most-218-playable-moves/a5xdxeqs). /// Even though the [`Position`] type can represent some unreachable positions the proof still /// holds, as its definition of a position is even less restrictive. #[must_use] #[derive(Clone)] 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) -> ControlFlow { self.is_check = true; ControlFlow::Continue(()) } #[inline] fn en_passant_is_legal(&mut self) -> ControlFlow { self.en_passant_is_legal = true; ControlFlow::Continue(()) } #[inline] fn extend(&mut self, iter: I) -> ControlFlow where I: Iterator + ExactSizeIterator, { iter.for_each(|raw| unsafe { self.array.push_unchecked(raw) }); ControlFlow::Continue(()) } } impl<'l> Moves<'l> { #[inline] pub(crate) fn compute(position: &'l Position) -> Moves<'l> { fn aux(position: &Position, moves: &mut Moves) { let ControlFlow::Continue(()) = 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.as_slice().into_iter(), } } /// Returns the number of moves in the list. #[inline] pub fn len(&self) -> usize { self.array.len() } /// Returns `true` if the king is in check. #[inline] pub fn is_check(&self) -> bool { self.is_check } /// Returns `true` if there is no legal move on the position. #[inline] pub fn is_mate(&self) -> bool { self.array.len() == 0 } /// Returns `true` if taking en passant is legal on the position. #[inline] pub fn en_passant_is_legal(&self) -> bool { self.en_passant_is_legal } /// 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 list in ascending order with a comparison function, preserving initial order of /// equal elements. See `std::slice::sort_by` for details. #[inline] pub fn sort_by(&mut self, mut compare: F) where F: FnMut(Move, Move) -> core::cmp::Ordering, { self.array.as_mut_slice().sort_by(|a, b| { compare( Move { position: self.position, raw: *a, }, Move { position: self.position, raw: *b, }, ) }); } /// Sorts the list in ascending order with a comparison function, **without** preserving the /// initial order of equal elements. See [`sort_unstable_by`](`slice::sort_unstable_by`) for /// details. #[inline] pub fn sort_unstable_by(&mut self, mut compare: F) where F: FnMut(Move, Move) -> core::cmp::Ordering, { self.array.as_mut_slice().sort_unstable_by(|a, b| { compare( Move { position: self.position, raw: *a, }, Move { position: self.position, raw: *b, }, ) }); } } /// An iterator over legal moves. #[derive(Clone)] pub struct MovesIter<'l> { position: &'l Position, iter: core::slice::Iter<'l, RawMove>, } 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: *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(), } } }