//! **Move generation.** use crate::array_vec::*; use crate::bitboard::*; use crate::board::*; use crate::lookup::*; 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. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Position { setup: Setup, lookup: InitialisedLookup, } const MAX_LEGAL_MOVES: usize = 218; impl std::fmt::Debug for Position { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.debug_tuple("Position") .field(&self.as_setup().to_string()) .finish() } } 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 { Self { setup: 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, }, lookup: InitialisedLookup::init(), } } /// Tries to read a valid position from a text record. /// /// This is a shortcut for parsing and validating a [`Setup`]: /// ``` /// # use eschac::setup::Setup; /// # |s: &str| -> Option { /// s.parse::().ok().and_then(|pos| pos.validate().ok()) /// # }; /// ``` #[inline] pub fn from_text_record(s: &str) -> Option { s.parse::().ok().and_then(|pos| pos.validate().ok()) } /// Returns all the legal moves on the position. #[inline] pub fn legal_moves<'l>(&'l self) -> Moves<'l> { fn aux(position: &Position, visitor: &mut Moves) { position.generate_moves(visitor); } let mut visitor = Moves { position: self, is_check: false, en_passant_is_legal: false, array: ArrayVec::new(), }; aux(self, &mut visitor); visitor } /// Counts the legal moves on the position. /// /// This is equivalent but faster than: /// ``` /// # use eschac::position::Position; /// # |position: &Position| -> usize { /// position.legal_moves().len() /// # }; /// ``` #[inline] pub fn count_legal_moves(&self) -> usize { struct VisitorImpl { len: usize, } impl VisitorImpl { fn new() -> Self { Self { len: 0 } } } impl Visitor for VisitorImpl { #[inline] fn is_check(&mut self) {} #[inline] fn en_passant_is_legal(&mut self) {} #[inline] fn moves(&mut self, iter: I) where I: Iterator + ExactSizeIterator, { self.len += iter.len(); } } fn aux(position: &Position, visitor: &mut VisitorImpl) { position.generate_moves(visitor); } let mut visitor = VisitorImpl::new(); aux(self, &mut visitor); visitor.len } /// Discards the optional en passant target square. /// /// 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) { self.setup.en_passant = OptionSquare::None; } /// Returns the occupancy of a square. #[inline] pub fn get(&self, square: Square) -> Option { self.setup.get(square) } /// Returns the color whose turn it is to play. #[inline] pub fn turn(&self) -> Color { self.setup.turn() } /// Returns `true` if castling is available for the given color and side. #[inline] pub fn castling_rights(&self, color: Color, side: CastlingSide) -> bool { self.setup.castling_rights(color, side) } /// Returns the en passant target square if it exists. /// /// Note that if an en passant target square exists, it does not mean that taking en passant is /// legal or even pseudo-legal. #[inline] pub fn en_passant_target_square(&self) -> Option { self.setup.en_passant_target_square() } /// Discards the castling rights for the given color and side. #[inline] pub fn remove_castling_rights(&mut self, color: Color, side: CastlingSide) { self.setup.set_castling_rights(color, side, false); } /// Borrows the position as a [`Setup`]. #[inline] pub fn as_setup(&self) -> &Setup { &self.setup } /// Converts a position to a [`Setup`], allowing to edit the position without enforcing its /// legality. #[inline] pub fn into_setup(self) -> Setup { self.setup } /// Tries to pass the turn to the other color, failing if it would leave the king in check. /// /// When possible, this inverts the color to play and removes the en passant square if it /// exists. pub fn pass(&self) -> Option { let d = self.lookup; let setup = &self.setup; 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 & (d.pawn_attack(setup.turn, king_square) & p | d.knight(king_square) & n | d.bishop(king_square, blockers) & (q | b) | d.rook(king_square, blockers) & (q | r)); checkers.is_empty().then(|| Self { setup: Setup { turn: !setup.turn, en_passant: OptionSquare::None, ..setup.clone() }, lookup: d, }) } /// Returns the mirror image of the position (see [`Setup::mirror`]). #[inline] pub fn mirror(&self) -> Self { Self { setup: self.setup.mirror(), lookup: self.lookup, } } /// 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, InvalidUciMove> { struct VisitorImpl { role: Role, from: Bitboard, to: Bitboard, found: Option, } impl VisitorImpl { #[inline] fn new(role: Role, from: Bitboard, to: Bitboard) -> Self { Self { role, from, to, found: None, } } } impl Visitor for VisitorImpl { #[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] fn moves(&mut self, iter: I) where I: Iterator + 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; let role = self.setup.get_role(from).ok_or(InvalidUciMove::Illegal)?; #[inline] fn aux<'l, const ROLE: u8>( position: &'l Position, role: Role, from: Square, to: Square, ) -> Result, InvalidUciMove> { let mut visitor = VisitorImpl::::new(role, from.bitboard(), to.bitboard()); position.generate_moves(&mut visitor); let raw = visitor.found.ok_or(InvalidUciMove::Illegal)?; Ok(Move { position, raw }) } 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, InvalidSan> { struct VisitorImpl { role: Role, from: Bitboard, to: Bitboard, found: Option, found_other: bool, } impl VisitorImpl { #[inline] fn new(role: Role, from: Bitboard, to: Bitboard) -> Self { Self { role, from, to, found: None, found_other: false, } } } impl Visitor for VisitorImpl { #[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] fn moves(&mut self, iter: I) where I: Iterator + 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, Square::new(File::E, self.turn().home_rank()).bitboard(), Square::new(File::G, self.turn().home_rank()).bitboard(), ), SanInner::Castle(CastlingSide::Long) => ( Role::King, Role::King, Square::new(File::E, self.turn().home_rank()).bitboard(), Square::new(File::C, self.turn().home_rank()).bitboard(), ), 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, InvalidSan> { let mut visitor = VisitorImpl::::new(role, from, to); position.generate_moves(&mut visitor); match visitor.found { None => Err(InvalidSan::Illegal), Some(raw) => match visitor.found_other { true => Err(InvalidSan::Ambiguous), false => Ok(Move { position, raw }), }, } } 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), } } } /// 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.setup.p_b_q | self.position.setup.n_b_k | self.position.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.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 VisitorImpl { to: Bitboard, candidates: Bitboard, } impl VisitorImpl { #[inline] fn new(to: Square) -> Self { Self { to: to.bitboard(), candidates: Bitboard::new(), } } } impl Visitor for VisitorImpl { #[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 moves(&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 visitor = VisitorImpl::::new(m.to()); m.position().generate_moves(&mut visitor); let candidates = visitor.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 visitor = MateCollector::new(); pos.generate_moves(&mut visitor); visitor.is_check.then(|| match visitor.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, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u8)] enum MoveType { CastleShort, CastleLong, KingMove, PieceMove, PawnAdvance, PawnAttack, PawnAdvancePromotion, PawnAttackPromotion, PawnDoubleAdvance, EnPassant, } impl RawMove { #[inline] fn from(&self) -> Square { self.from } #[inline] fn to(&self) -> Square { self.to } #[inline] fn role(&self) -> Role { 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] fn promotion(&self) -> Option { match self.kind { MoveType::PawnAdvancePromotion | MoveType::PawnAttackPromotion => Some(self.role), _ => None, } } #[inline] fn uci(&self) -> UciMove { UciMove { from: self.from(), to: self.to(), promotion: self.promotion(), } } } trait Visitor { #[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); fn moves(&mut self, iter: I) where I: Iterator + ExactSizeIterator; } impl<'l> Visitor 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 moves(&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, lookup: InitialisedLookup::init(), } } fn generate_moves(&self, visitor: &mut T) where T: Visitor, { let global_mask_from = visitor.from(); let global_mask_to = visitor.to(); let Setup { w, p_b_q, n_b_k, r_q_k, turn, en_passant, castling_rights, } = self.setup; let d = self.lookup; 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(); let x = d.bishop(king_square, blockers); let y = d.rook(king_square, blockers); let checkers = d.pawn_attack(turn, king_square) & theirs.pawn() | d.knight(king_square) & theirs.knight() | x & theirs.bishop() | y & theirs.rook(); let blockers_x_ray = blockers & !(x | y); let pinned = (d.bishop(king_square, blockers_x_ray) & theirs.bishop() | d.rook(king_square, blockers_x_ray) & theirs.rook()) .map(|sq| d.segment(king_square, sq)) .reduce_or(); if visitor.roles(Role::King) && global_mask_from.contains(king_square) { let attacked = { let blockers = blockers ^ ours.king(); theirs .king() .map(|sq| d.king(sq)) .chain(theirs.bishop().map(|sq| d.bishop(sq, blockers))) .chain(theirs.rook().map(|sq| d.rook(sq, blockers))) .chain(theirs.knight().map(|sq| d.knight(sq))) .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 visitor.moves( (global_mask_to & d.king(king_square) & !us & !attacked).map(|to| RawMove { 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) { visitor.moves(std::iter::once(RawMove { 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) { visitor.moves(std::iter::once(RawMove { kind: MoveType::CastleLong, from, to, role: Role::King, })) } } } } if checkers.len() > 1 { visitor.is_check(); return; } let checker = checkers.first(); let block_check = checker .map(|checker| d.segment(king_square, checker)) .unwrap_or(Bitboard(!0)); let target_mask = global_mask_to & block_check; // pawns if visitor.roles(Role::Pawn) { 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; visitor.moves((targets & !promotion).map(|to| RawMove { kind: MoveType::PawnAdvance, from: unsafe { to.trans_unchecked(!forward) }, to, role: Role::Pawn, })); visitor.moves(WithPromotion::new((targets & promotion).map(|to| { RawMove { kind: MoveType::PawnAdvancePromotion, from: unsafe { to.trans_unchecked(!forward) }, to, role: Role::Pawn, } }))); } // pawn attacks kingside { let targets = (global_mask_from & ours.pawn() & (!pinned | d.ray(king_square, kside))) .trans(kside) & them & target_mask; visitor.moves((targets & !promotion).map(|to| RawMove { kind: MoveType::PawnAttack, from: unsafe { to.trans_unchecked(!kside) }, to, role: Role::Pawn, })); visitor.moves(WithPromotion::new((targets & promotion).map(|to| { RawMove { kind: MoveType::PawnAttackPromotion, from: unsafe { to.trans_unchecked(!kside) }, to, role: Role::Pawn, } }))); } // pawn attacks queenside { let targets = (global_mask_from & ours.pawn() & (!pinned | d.ray(king_square, qside))) .trans(qside) & them & target_mask; visitor.moves((targets & !promotion).map(|to| RawMove { kind: MoveType::PawnAttack, from: unsafe { to.trans_unchecked(!qside) }, to, role: Role::Pawn, })); visitor.moves(WithPromotion::new((targets & promotion).map(|to| { RawMove { kind: MoveType::PawnAttackPromotion, from: unsafe { to.trans_unchecked(!qside) }, to, role: Role::Pawn, } }))); } // pawn double advances visitor.moves( ((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) { let candidates = d.pawn_attack(!turn, to) & ours.pawn(); let blockers = blockers ^ capture_square.bitboard(); let pinned = pinned | (d.rook(king_square, blockers & !(d.rook(king_square, blockers))) & theirs.rook()) .map(|sq| d.segment(king_square, sq)) .reduce_or(); (global_mask_from & candidates & (!pinned | d.segment(king_square, to))) .for_each(|from| { visitor.en_passant_is_legal(); visitor.moves(std::iter::once(RawMove { kind: MoveType::EnPassant, from, to, role: Role::Pawn, })) }) } } } } // pieces not pinned { let aux = |visitor: &mut T, role| { for from in global_mask_from & *ours.get(role) & !pinned { visitor.moves( (d.targets(role, from, blockers) & !us & target_mask).map(|to| RawMove { kind: MoveType::PieceMove, from, to, role, }), ) } }; if visitor.roles(Role::Knight) { aux(visitor, Role::Knight) } if visitor.roles(Role::Bishop) { aux(visitor, Role::Bishop) } if visitor.roles(Role::Rook) { aux(visitor, Role::Rook) } if visitor.roles(Role::Queen) { aux(visitor, Role::Queen) } } if checker.is_some() { visitor.is_check(); return; } // pinned pieces { let aux = |visitor: &mut T, role| { for from in global_mask_from & *ours.get(role) & pinned { visitor.moves( (global_mask_to & d.targets(role, from, blockers) & !us & d.line(king_square, from)) .map(|to| RawMove { kind: MoveType::PieceMove, from, to, role, }), ) } }; if visitor.roles(Role::Bishop) { aux(visitor, Role::Bishop) } if visitor.roles(Role::Rook) { aux(visitor, Role::Rook) } if visitor.roles(Role::Queen) { aux(visitor, Role::Queen) } } } #[inline] unsafe fn play_unchecked(&mut self, m: RawMove) { let Self { setup, .. } = self; 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); setup.en_passant = OptionSquare::new(Some(Square::new( 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; } } struct WithPromotion + ExactSizeIterator + FusedIterator> { inner: I, cur: std::mem::MaybeUninit, role: Role, } impl WithPromotion where I: Iterator + ExactSizeIterator + FusedIterator, { #[inline] fn new(inner: I) -> Self { Self { inner, cur: std::mem::MaybeUninit::uninit(), role: Role::King, } } } impl Iterator for WithPromotion where I: Iterator + ExactSizeIterator + FusedIterator, { type Item = RawMove; #[inline] fn next(&mut self) -> Option { 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) { let len = self.len(); (len, Some(len)) } } impl FusedIterator for WithPromotion where I: Iterator + ExactSizeIterator + FusedIterator { } impl ExactSizeIterator for WithPromotion where I: Iterator + ExactSizeIterator + FusedIterator, { #[inline] fn len(&self) -> usize { unsafe { self.inner.len().unchecked_mul(4) } } } #[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; if target == Square::new(File::H, setup.turn.promotion_rank()) { setup .castling_rights .unset(!setup.turn, CastlingSide::Short); } if target == Square::new(File::A, setup.turn.promotion_rank()) { 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; if from == Square::new(File::H, setup.turn.home_rank()).bitboard() { setup.castling_rights.unset(setup.turn, CastlingSide::Short); } if from == Square::new(File::A, setup.turn.home_rank()).bitboard() { 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 => ( Square::new(File::E, rank).bitboard() | Square::new(File::G, rank).bitboard(), Square::new(File::H, rank).bitboard() | Square::new(File::F, rank).bitboard(), ), CastlingSide::Long => ( Square::new(File::E, rank).bitboard() | Square::new(File::C, rank).bitboard(), Square::new(File::A, rank).bitboard() | Square::new(File::D, rank).bitboard(), ), }; 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); } struct MateCollector { is_check: bool, is_mate: bool, } impl MateCollector { #[inline] fn new() -> Self { Self { is_check: false, is_mate: true, } } } impl Visitor for MateCollector { #[inline] fn is_check(&mut self) { self.is_check = true; } #[inline] fn en_passant_is_legal(&mut self) {} #[inline] fn moves(&mut self, iter: I) where I: Iterator + ExactSizeIterator, { self.is_mate &= iter.len() == 0; } }