//! 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 { 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. #[must_use] 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(), } } }