//! **Move generation.** use crate::bitboard::*; use crate::board::*; use crate::lookup; use crate::moves::*; 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. #[must_use] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Position(Setup); pub(crate) const MAX_LEGAL_MOVES: usize = 218; const PAWN: u8 = Role::Pawn as u8; const KNIGHT: u8 = Role::Knight as u8; const BISHOP: u8 = Role::Bishop as u8; const ROOK: u8 = Role::Rook as u8; const QUEEN: u8 = Role::Queen as u8; const KING: u8 = Role::King as u8; macro_rules! map_role { ($f:ident, $role:ident, $($x:expr),+) => ( match $role { Role::Pawn => $f::($($x),+), Role::Knight => $f::($($x),+), Role::Bishop => $f::($($x),+), Role::Rook => $f::($($x),+), Role::Queen => $f::($($x),+), Role::King => $f::($($x),+), } ) } 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 { 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, }) } /// Returns the number of legal moves on the position. #[inline] pub fn legal_moves<'l>(&'l self) -> Moves<'l> { Moves::compute(self) } /// Counts the legal moves on the position. #[must_use] #[inline] pub fn count_legal_moves(&self) -> usize { struct MoveGenImpl { len: usize, } impl MoveGenImpl { fn new() -> Self { Self { len: 0 } } } impl MoveGen for MoveGenImpl { #[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, { self.len += iter.len(); } } let mut moves = MoveGenImpl::new(); self.generate_moves(&mut moves); moves.len } /// Returns `true` if the king is in check. #[must_use] pub fn is_check(&self) -> bool { struct MoveGenImpl { is_check: bool, } impl MoveGen for MoveGenImpl { #[inline] fn roles(&self, _role: Role) -> bool { false } #[inline] fn from(&self) -> Bitboard { Bitboard::new() } #[inline] fn to(&self) -> Bitboard { Bitboard::new() } #[inline] fn is_check(&mut self) { self.is_check = true; } #[inline] fn en_passant_is_legal(&mut self) {} #[inline] fn extend(&mut self, _iter: I) where I: Iterator + ExactSizeIterator, { } } let mut moves = MoveGenImpl { is_check: false }; self.generate_moves(&mut moves); moves.is_check } /// Returns `true` if there is no legal move on the position. #[must_use] pub fn is_mate(&self) -> bool { self.count_legal_moves() == 0 } /// Returns `true` if taking en passant is legal on the position. #[must_use] pub fn en_passant_is_legal(&self) -> bool { if self.as_setup().en_passant == OptionSquare::None { return false; } struct MoveGenImpl { to: Bitboard, en_passant_is_legal: bool, } impl MoveGen for MoveGenImpl { #[inline] fn roles(&self, role: Role) -> bool { match role { Role::Pawn => true, _ => false, } } #[inline] fn from(&self) -> Bitboard { !Bitboard::new() } #[inline] fn to(&self) -> Bitboard { self.to } #[inline] fn is_check(&mut self) {} #[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, { } } let mut moves = MoveGenImpl { to: self.as_setup().en_passant.bitboard(), en_passant_is_legal: false, }; self.generate_moves(&mut moves); moves.en_passant_is_legal } /// Discards the 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.0.en_passant = OptionSquare::None; } /// Discards the castling rights for the given color and side. #[inline] pub fn remove_castling_rights(&mut self, color: Color, side: CastlingSide) { self.0.set_castling_rights(color, side, false); } /// Borrows the position as a [`Setup`]. #[inline] pub fn as_setup(&self) -> &Setup { &self.0 } /// Converts the position into the [`Setup`] type, allowing to edit it without enforcing its legality. #[inline] pub fn into_setup(self) -> Setup { self.0 } /// Returns the position after after passing the turn to the other color, /// and `None` if the king in check. On success, this inverts the color to /// play and discards the en passant square. pub fn pass(&self) -> Option { let setup = &self.0; 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 & (lookup::pawn_attack(setup.turn, king_square) & p | lookup::knight(king_square) & n | lookup::bishop(king_square, blockers) & (q | b) | lookup::rook(king_square, blockers) & (q | r)); checkers.is_empty().then(|| { Self(Setup { turn: !setup.turn, en_passant: OptionSquare::None, ..setup.clone() }) }) } /// Returns the mirror image of the position (see [`Setup::mirror`]). #[inline] pub fn mirror(&self) -> Self { Self(self.0.mirror()) } /// 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 MoveGenImpl { role: Role, from: Bitboard, to: Bitboard, found: Option, } impl MoveGenImpl { #[inline] fn new(role: Role, from: Bitboard, to: Bitboard) -> Self { Self { role, from, to, found: None, } } } impl MoveGen for MoveGenImpl { #[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 extend(&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.0.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 moves = MoveGenImpl::::new(role, from.bitboard(), to.bitboard()); position.generate_moves(&mut moves); let raw = moves.found.ok_or(InvalidUciMove::Illegal)?; Ok(unsafe { Move::new_unchecked(position, raw) }) } let promotion = if role == Role::Pawn { promotion.unwrap_or(Role::Pawn) } else if promotion.is_some() { return Err(InvalidUciMove::Illegal); } else { role }; map_role!(aux, role, self, promotion, from, to) } pub(crate) fn move_from_san<'l>(&'l self, san: &San) -> Result, InvalidSan> { struct MoveGenImpl { role: Role, from: Bitboard, to: Bitboard, found: Option, found_other: bool, } impl MoveGenImpl { #[inline] fn new(role: Role, from: Bitboard, to: Bitboard) -> Self { Self { role, from, to, found: None, found_other: false, } } } impl MoveGen for MoveGenImpl { #[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 extend(&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::from_coords(File::E, self.0.turn().home_rank()).bitboard(), Square::from_coords(File::G, self.0.turn().home_rank()).bitboard(), ), SanInner::Castle(CastlingSide::Long) => ( Role::King, Role::King, Square::from_coords(File::E, self.0.turn().home_rank()).bitboard(), Square::from_coords(File::C, self.0.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 moves = MoveGenImpl::::new(role, from, to); position.generate_moves(&mut moves); match moves.found { None => Err(InvalidSan::Illegal), Some(raw) => match moves.found_other { true => Err(InvalidSan::Ambiguous), false => Ok(unsafe { Move::new_unchecked(position, raw) }), }, } } map_role!(aux, role, self, promotion, from, to) } } impl std::fmt::Debug for Position { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.debug_tuple("Position") .field(&TextRecord(self.as_setup())) .finish() } } #[derive(Clone, Copy)] pub(crate) struct RawMove { pub kind: MoveType, pub role: Role, pub from: Square, pub to: Square, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub(crate) enum MoveType { CastleShort, CastleLong, KingMove, PieceMove, PawnAdvance, PawnAttack, PawnAdvancePromotion, PawnAttackPromotion, PawnDoubleAdvance, EnPassant, } impl RawMove { #[inline] pub fn from(&self) -> Square { self.from } #[inline] pub fn to(&self) -> Square { self.to } #[inline] pub 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] pub fn promotion(&self) -> Option { match self.kind { MoveType::PawnAdvancePromotion | MoveType::PawnAttackPromotion => Some(self.role), _ => None, } } #[inline] pub fn uci(&self) -> UciMove { UciMove { from: self.from(), to: self.to(), promotion: self.promotion(), } } } pub(crate) trait MoveGen { #[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 extend(&mut self, iter: I) where I: Iterator + ExactSizeIterator; } impl Position { /// SAFETY: The position must be valid. pub(crate) unsafe fn from_setup(setup: Setup) -> Self { Self(setup) } pub(crate) fn generate_moves(&self, moves: &mut T) where T: MoveGen, { let global_mask_from = moves.from(); let global_mask_to = moves.to(); let Setup { w, p_b_q, n_b_k, r_q_k, turn, en_passant, castling_rights, } = self.0; 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 = lookup::bishop(king_square, blockers); let y = lookup::rook(king_square, blockers); let checkers = lookup::pawn_attack(turn, king_square) & theirs.pawn() | lookup::knight(king_square) & theirs.knight() | x & theirs.bishop() | y & theirs.rook(); if moves.roles(Role::King) && global_mask_from.contains(king_square) { let attacked = { let blockers = blockers ^ ours.king(); theirs .king() .map(|sq| lookup::king(sq)) .chain(theirs.bishop().map(|sq| lookup::bishop(sq, blockers))) .chain(theirs.rook().map(|sq| lookup::rook(sq, blockers))) .chain(theirs.knight().map(|sq| lookup::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 moves.extend( (global_mask_to & lookup::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) { moves.extend(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) { moves.extend(std::iter::once(RawMove { kind: MoveType::CastleLong, from, to, role: Role::King, })) } } } } if checkers.len() > 1 { moves.is_check(); return; } let blockers_x_ray = blockers & !(x | y); let pinned = ((lookup::bishop(king_square, blockers_x_ray) & theirs.bishop()) | (lookup::rook(king_square, blockers_x_ray) & theirs.rook())) .map(|sq| lookup::segment(king_square, sq)) .reduce_or(); let checker = checkers.first(); let block_check = checker .map(|checker| lookup::segment(king_square, checker)) .unwrap_or(Bitboard(!0)); let target_mask = global_mask_to & block_check; // pawns if moves.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; moves.extend((targets & !promotion).map(|to| RawMove { kind: MoveType::PawnAdvance, from: unsafe { to.trans_unchecked(!forward) }, to, role: Role::Pawn, })); moves.extend(MoveAndPromote::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 | lookup::ray(king_square, kside))) .trans(kside) & them & target_mask; moves.extend((targets & !promotion).map(|to| RawMove { kind: MoveType::PawnAttack, from: unsafe { to.trans_unchecked(!kside) }, to, role: Role::Pawn, })); moves.extend(MoveAndPromote::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 | lookup::ray(king_square, qside))) .trans(qside) & them & target_mask; moves.extend((targets & !promotion).map(|to| RawMove { kind: MoveType::PawnAttack, from: unsafe { to.trans_unchecked(!qside) }, to, role: Role::Pawn, })); moves.extend(MoveAndPromote::new((targets & promotion).map(|to| { RawMove { kind: MoveType::PawnAttackPromotion, from: unsafe { to.trans_unchecked(!qside) }, to, role: Role::Pawn, } }))); } // pawn double advances moves.extend( ((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 = lookup::pawn_attack(!turn, to) & ours.pawn(); let blockers = blockers ^ capture_square.bitboard(); let pinned = pinned | (lookup::rook( king_square, blockers & !(lookup::rook(king_square, blockers)), ) & theirs.rook()) .map(|sq| lookup::segment(king_square, sq)) .reduce_or(); (global_mask_from & candidates & (!pinned | lookup::segment(king_square, to))) .for_each(|from| { moves.en_passant_is_legal(); moves.extend(std::iter::once(RawMove { kind: MoveType::EnPassant, from, to, role: Role::Pawn, })) }) } } } } // pieces not pinned { let aux = |moves: &mut T, role| { for from in global_mask_from & *ours.get(role) & !pinned { moves.extend( (lookup::targets(role, from, blockers) & !us & target_mask).map(|to| { RawMove { kind: MoveType::PieceMove, from, to, role, } }), ) } }; if moves.roles(Role::Knight) { aux(moves, Role::Knight) } if moves.roles(Role::Bishop) { aux(moves, Role::Bishop) } if moves.roles(Role::Rook) { aux(moves, Role::Rook) } if moves.roles(Role::Queen) { aux(moves, Role::Queen) } } if checker.is_some() { moves.is_check(); return; } // pinned pieces { let aux = |moves: &mut T, role, role_mask| { for from in global_mask_from & pinned & role_mask & *ours.get(role) { moves.extend( (global_mask_to & !us & pinned & lookup::line(king_square, from)).map( |to| RawMove { kind: MoveType::PieceMove, from, to, role, }, ), ) } }; if moves.roles(Role::Bishop) { aux(moves, Role::Bishop, lookup::bishop_lines(king_square)); } if moves.roles(Role::Rook) { aux(moves, Role::Rook, lookup::rook_lines(king_square)); } if moves.roles(Role::Queen) { aux(moves, Role::Queen, !Bitboard::new()); } } } #[inline] pub(crate) 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::from_coords( 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; } } #[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::from_coords(File::H, setup.turn.promotion_rank()) { setup .castling_rights .unset(!setup.turn, CastlingSide::Short); } if target == Square::from_coords(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::from_coords(File::H, setup.turn.home_rank()).bitboard() { setup.castling_rights.unset(setup.turn, CastlingSide::Short); } if from == Square::from_coords(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::from_coords(File::E, rank).bitboard() | Square::from_coords(File::G, rank).bitboard(), Square::from_coords(File::H, rank).bitboard() | Square::from_coords(File::F, rank).bitboard(), ), CastlingSide::Long => ( Square::from_coords(File::E, rank).bitboard() | Square::from_coords(File::C, rank).bitboard(), Square::from_coords(File::A, rank).bitboard() | Square::from_coords(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); } pub(crate) struct MateMoveGenImpl { pub is_check: bool, pub is_mate: bool, } impl MateMoveGenImpl { #[inline] pub fn new() -> Self { Self { is_check: false, is_mate: true, } } } impl MoveGen for MateMoveGenImpl { #[inline] fn is_check(&mut self) { self.is_check = true; } #[inline] fn en_passant_is_legal(&mut self) {} #[inline] fn extend(&mut self, iter: I) where I: Iterator + ExactSizeIterator, { self.is_mate &= iter.len() == 0; } } struct MoveAndPromote + ExactSizeIterator + FusedIterator> { inner: I, cur: std::mem::MaybeUninit, role: Role, } impl MoveAndPromote where I: Iterator + ExactSizeIterator + FusedIterator, { #[inline] fn new(inner: I) -> Self { Self { inner, cur: std::mem::MaybeUninit::uninit(), role: Role::King, } } } impl Iterator for MoveAndPromote 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 MoveAndPromote where I: Iterator + ExactSizeIterator + FusedIterator { } impl ExactSizeIterator for MoveAndPromote where I: Iterator + ExactSizeIterator + FusedIterator, { #[inline] fn len(&self) -> usize { unsafe { self.inner.len().unchecked_mul(4) } } }