use crate::array_vec::*;
use crate::bitboard::*;
use crate::board::*;
use crate::lookup;
use crate::san::*;
use crate::setup::*;
use crate::uci::*;
use core::convert::Infallible;
use core::iter::{ExactSizeIterator, FusedIterator};
use core::marker::PhantomData;
use core::ops::ControlFlow;
#[must_use]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Position(Setup);
impl core::ops::Deref for Position {
type Target = Setup;
#[inline]
fn deref(&self) -> &Setup {
&self.0
}
}
impl Position {
#[inline]
pub fn new() -> Self {
let setup = Setup::from_bitboards_inner([
Bitboard(0x2CFF00000000FF2C),
Bitboard(0x7600000000000076),
Bitboard(0x9900000000000099),
Bitboard(0xFFFF000000000000),
Bitboard(0x8100000000000081),
]);
Self::from_setup_inner(setup)
}
#[inline]
pub fn legal_moves<'l>(&'l self) -> Moves<'l> {
fn aux(position: &Position, list: &mut RawMoves) {
let ControlFlow::Continue(()) = position.generate_moves(list);
}
let mut list = RawMoves {
is_check: false,
en_passant_is_legal: false,
array: ArrayVec::new(),
};
aux(self, &mut list);
Moves {
position: self,
list,
}
}
#[inline]
pub fn for_each_move<'l, F>(&'l self, mut f: F)
where
F: FnMut(Move<'l>),
{
let ControlFlow::Continue(()) =
self.try_for_each_move(|m: Move<'l>| -> ControlFlow<Infallible> {
f(m);
ControlFlow::Continue(())
});
}
#[inline]
pub fn try_for_each_move<'l, F, T>(&'l self, f: F) -> ControlFlow<T>
where
F: FnMut(Move<'l>) -> ControlFlow<T>,
{
struct MoveGenImpl<'l, F, T> {
f: F,
pos: &'l Position,
_ph: PhantomData<T>,
}
impl<'l, F, T> MoveGen<T> for MoveGenImpl<'l, F, T>
where
F: FnMut(Move<'l>) -> ControlFlow<T>,
{
fn extend<S: MoveSet>(&mut self, s: S) -> ControlFlow<T> {
s.try_for_each(|raw| (self.f)(raw.to_move_inner(self.pos)))
}
}
let mut acc = MoveGenImpl {
f,
pos: self,
_ph: PhantomData,
};
self.generate_moves(&mut acc)
}
#[must_use]
pub fn count_legal_moves(&self) -> usize {
struct CountMoves(usize);
impl MoveGen<Infallible> for CountMoves {
#[inline]
fn extend<S: MoveSet>(&mut self, s: S) -> ControlFlow<Infallible> {
self.0 += s.len();
ControlFlow::Continue(())
}
}
let mut count = CountMoves(0);
let ControlFlow::Continue(()) = self.generate_moves(&mut count);
count.0
}
#[must_use]
pub fn is_check(&self) -> bool {
struct MoveGenImpl(Bitboard);
impl MoveGen<core::convert::Infallible> for MoveGenImpl {
#[inline]
fn checkers(&mut self, b: Bitboard) {
self.0 = b;
}
}
let mut acc = MoveGenImpl(Bitboard::new());
let ControlFlow::Continue(()) = self.generate_moves(&mut acc);
!acc.0.is_empty()
}
#[must_use]
pub fn is_mate(&self) -> bool {
struct MoveGenImpl;
impl MoveGen<()> for MoveGenImpl {
#[inline]
fn extend<S: MoveSet>(&mut self, s: S) -> ControlFlow<()> {
if s.is_empty() {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
}
}
match self.generate_moves(&mut MoveGenImpl) {
ControlFlow::Continue(()) => true,
ControlFlow::Break(()) => false,
}
}
#[must_use]
pub fn en_passant_is_legal(&self) -> bool {
struct MoveGenImpl(bool);
impl MoveGen<core::convert::Infallible> for MoveGenImpl {
#[inline]
fn en_passant(&mut self) {
self.0 = true;
}
}
let mut acc = MoveGenImpl(false);
let ControlFlow::Continue(()) = self.generate_moves(&mut acc);
acc.0
}
#[inline]
pub fn remove_en_passant_target_square(self) -> Self {
let setup = self.into_setup().set_en_passant_target_square(None);
Self::from_setup_inner(setup)
}
#[inline]
pub fn remove_castling_rights(self, color: Color, castle: Castle) -> Self {
Self::from_setup_inner(self.into_setup().set_castling_rights(color, castle, false))
}
#[inline]
pub fn pass(&self) -> Option<Self> {
if self.is_check() {
None
} else {
Some(Self::from_setup_inner(
self.clone()
.into_setup()
.switch_turn()
.set_en_passant_target_square(None),
))
}
}
#[inline]
pub fn mirror(&self) -> Self {
Self::from_setup_inner(self.0.mirror())
}
#[inline]
pub unsafe fn play_unchecked(&self, m: RawMove) -> Self {
self.play_inner(m)
}
#[inline]
pub fn to_setup(&self) -> Setup {
self.0.clone()
}
#[inline]
pub fn into_setup(self) -> Setup {
self.0
}
pub fn perft(&self, depth: usize) -> usize {
fn aux(acc: &mut usize, position: &Position, depth: usize) {
if depth == 0 {
*acc += position.count_legal_moves();
} else {
position.for_each_move(|m| aux(acc, &m.make(), depth - 1));
}
}
match depth.checked_sub(1) {
None => 1,
Some(depth) => {
let mut acc = 0;
aux(&mut acc, self, depth);
acc
}
}
}
#[inline]
pub(crate) fn from_setup_inner(setup: Setup) -> Self {
debug_assert!(setup.illegal_reasons().is_empty());
Self(setup)
}
#[inline]
pub(crate) fn play_inner(&self, m: RawMove) -> Self {
let turn = self.turn();
let [mut a, mut b, mut c, mut d, mut e] = self.bitboards();
let RawMove {
kind,
from,
to,
promotion,
} = m;
e &= !(Rank::Third.bitboard() | Rank::Sixth.bitboard());
let castle_mask =
sq("a1").bitboard() | sq("h1").bitboard() | sq("a8").bitboard() | sq("h8").bitboard();
let home = (match turn {
Color::White => Rank::First,
Color::Black => Rank::Eighth,
})
.bitboard();
let x = from.bitboard();
let y = to.bitboard();
match kind {
MoveType::Normal | MoveType::PawnAdvance | MoveType::PawnCapture => {
a.0 = (a.0 & !y.0) | ((a.0 >> from.index()) & 1) << to.index();
b.0 = (b.0 & !y.0) | ((b.0 >> from.index()) & 1) << to.index();
c.0 = (c.0 & !y.0) | ((c.0 >> from.index()) & 1) << to.index();
d.0 = (d.0 & !y.0) | ((d.0 >> from.index()) & 1) << to.index();
a &= !x;
b &= !x;
c &= !x;
d &= !x;
e &= !((x & home | y) & castle_mask);
if self.get(from).unwrap().role == Role::King {
e &= !(home & castle_mask);
}
}
MoveType::PawnAdvancePromotion | MoveType::PawnCapturePromotion => {
a &= !x;
d &= !x;
let role = promotion.unwrap();
a.0 = (a.0 & !y.0) | ((((role as u8) as u64) & 1) << to.index());
b.0 = (b.0 & !y.0) | (((((role as u8) as u64) >> 1_u8) & 1) << to.index());
c.0 = (c.0 & !y.0) | (((((role as u8) as u64) >> 2_u8) & 1) << to.index());
d.0 = (d.0 & !y.0) | ((turn as u8 as u64) << to.index());
e &= !(y & castle_mask);
}
MoveType::CastleShort => {
let x = home & Bitboard(0x5000000000000050);
let y = home & Bitboard(0xA0000000000000A0);
b ^= x;
c ^= x | y;
if turn == Color::Black {
d ^= x | y;
}
e &= !(home & castle_mask);
}
MoveType::CastleLong => {
let x = home & Bitboard(0x1400000000000014);
let y = home & Bitboard(0x0900000000000009);
b ^= x;
c ^= x | y;
if turn == Color::Black {
d ^= x | y;
}
e &= !(home & castle_mask);
}
MoveType::PawnDoubleAdvance => {
a ^= x | y;
if turn == Color::Black {
d ^= x | y;
}
e.insert(Square::from_coords(
from.file(),
match turn {
Color::White => Rank::Third,
Color::Black => Rank::Sixth,
},
));
}
MoveType::EnPassant => {
let z = y.trans(!turn.forward());
a ^= x | y | z;
d ^= match turn {
Color::White => z,
Color::Black => x | y,
};
}
}
e.0 ^= 2;
Position::from_setup_inner(Setup::from_bitboards_inner([a, b, c, d, e]))
}
}
#[derive(Debug, Clone)]
pub(crate) struct RawMoves {
pub is_check: bool,
pub en_passant_is_legal: bool,
pub array: ArrayVec<RawMove, 218>,
}
impl MoveGen<Infallible> for RawMoves {
#[inline]
fn checkers(&mut self, b: Bitboard) {
self.is_check = !b.is_empty();
}
#[inline]
fn en_passant(&mut self) {
self.en_passant_is_legal = true;
}
#[inline]
fn extend<S: MoveSet>(&mut self, s: S) -> ControlFlow<Infallible> {
s.try_for_each(|raw| -> ControlFlow<Infallible> {
self.array.push(raw);
ControlFlow::Continue(())
});
ControlFlow::Continue(())
}
}
#[must_use]
#[derive(Clone, Copy)]
pub struct Move<'l> {
position: &'l Position,
raw: RawMove,
}
impl<'l> Move<'l> {
#[inline]
pub fn position(self) -> &'l Position {
self.position
}
#[inline]
pub fn raw(self) -> RawMove {
self.raw
}
#[inline]
pub fn make(self) -> Position {
self.position().play_inner(self.raw())
}
#[inline]
pub fn role(self) -> Role {
self.position().role(self.from()).unwrap()
}
#[inline]
pub fn from(self) -> Square {
self.raw().from()
}
#[inline]
pub fn to(self) -> Square {
self.raw().to()
}
#[inline]
pub fn promotion(self) -> Option<Role> {
self.raw().promotion()
}
#[inline]
pub fn is_capture(self) -> bool {
let [a, b, c, _, _] = self.position().bitboards();
self.raw().kind == MoveType::EnPassant || (a | b | c).contains(self.to())
}
#[inline]
pub fn captured(self) -> Option<Role> {
match self.raw().kind {
MoveType::EnPassant => Some(Role::Pawn),
_ => self.position().role(self.to()),
}
}
#[inline]
pub fn castle(self) -> Option<Castle> {
match self.raw().kind {
MoveType::CastleShort => Some(Castle::Short),
MoveType::CastleLong => Some(Castle::Long),
_ => None,
}
}
#[inline]
pub fn to_uci(self) -> UciMove {
self.raw().to_uci()
}
pub fn to_san(self) -> San {
let position = self.make();
let suffix = if position.is_check() {
if position.is_mate() {
Some(SanSuffix::Checkmate)
} else {
Some(SanSuffix::Check)
}
} else {
None
};
match self.castle() {
Some(castle) => San {
data: SanData::Castle { side: castle },
capture: false,
suffix,
},
None => {
let role = self.role();
let from = self.from();
let to = self.to();
let promotion = self.promotion();
let capture = self.is_capture();
let mut candidates = Bitboard(0);
let ControlFlow::Continue(()) =
self.position
.generate_moves(&mut |m: RawMove| -> ControlFlow<Infallible> {
if self.position.role(m.from()) == Some(self.role())
&& m.to() == self.to() && m.promotion() == self.promotion()
{
candidates.insert(m.from());
}
ControlFlow::Continue(())
});
candidates &= !from.bitboard();
let from = if role == Role::Pawn {
(if capture { Some(from.file()) } else { None }, None)
} else if candidates.is_empty() {
(None, None)
} else if (candidates & from.file().bitboard()).is_empty() {
(Some(from.file()), None)
} else if (candidates & from.rank().bitboard()).is_empty() {
(None, Some(from.rank()))
} else {
(Some(from.file()), Some(from.rank()))
};
San {
data: SanData::Normal {
role,
from,
to,
promotion,
},
capture,
suffix,
}
}
}
}
}
#[must_use]
#[derive(Debug, Clone)]
pub struct Moves<'l> {
position: &'l Position,
list: RawMoves,
}
impl<'l> Moves<'l> {
#[inline]
pub fn position(&self) -> &'l Position {
self.position
}
#[inline]
pub fn iter(&'l self) -> MovesIter<'l> {
MovesIter {
inner: InnerMovesIter {
position: self.position(),
iter: self.list().array.iter(),
},
}
}
#[inline]
pub fn len(&self) -> usize {
self.list().array.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.list().array.len() == 0
}
#[inline]
pub fn is_check(&self) -> bool {
self.list().is_check
}
#[inline]
pub fn en_passant_is_legal(&self) -> bool {
self.list().en_passant_is_legal
}
#[inline]
pub fn get(&self, index: usize) -> Option<Move<'l>> {
self.list()
.array
.as_slice()
.get(index)
.copied()
.map(move |raw| raw.to_move_inner(self.position()))
}
#[inline]
pub fn sort(&mut self) {
self.list
.array
.as_mut_slice()
.sort_unstable_by(RawMove::compare)
}
#[inline]
#[cfg(feature = "alloc")]
pub fn sort_by<F>(&mut self, mut compare: F)
where
F: FnMut(Move, Move) -> core::cmp::Ordering,
{
self.list.array.as_mut_slice().sort_by(|a, b| {
let a = a.to_move_inner(self.position);
let b = b.to_move_inner(self.position);
compare(a, b)
});
}
#[inline]
pub fn sort_unstable_by<F>(&mut self, mut compare: F)
where
F: FnMut(Move, Move) -> core::cmp::Ordering,
{
self.list.array.as_mut_slice().sort_unstable_by(|a, b| {
let a = a.to_move_inner(self.position);
let b = b.to_move_inner(self.position);
compare(a, b)
});
}
#[inline]
pub fn select_nth_unstable(&mut self, index: usize) -> Move<'l> {
let (_, raw, _) = self
.list
.array
.as_mut_slice()
.select_nth_unstable_by(index, RawMove::compare);
raw.to_move_inner(self.position)
}
#[inline]
pub fn select_nth_unstable_by<F>(&mut self, index: usize, mut compare: F) -> Move<'l>
where
F: FnMut(Move, Move) -> core::cmp::Ordering,
{
let (_, raw, _) = self
.list
.array
.as_mut_slice()
.select_nth_unstable_by(index, |a, b| {
let a = a.to_move_inner(self.position);
let b = b.to_move_inner(self.position);
compare(a, b)
});
raw.to_move_inner(self.position)
}
#[inline]
pub(crate) fn list(&'l self) -> &'l RawMoves {
&self.list
}
#[inline]
pub(crate) fn into_list(self) -> RawMoves {
self.list
}
}
#[derive(Debug, Clone)]
pub struct MovesIter<'l> {
inner: InnerMovesIter<'l, ArrayVecIter<'l, RawMove, 218>>,
}
impl<'l> Iterator for MovesIter<'l> {
type Item = Move<'l>;
crate::iterator::mk!();
}
impl<'l> FusedIterator for MovesIter<'l> {}
impl<'l> ExactSizeIterator for MovesIter<'l> {
#[inline]
fn len(&self) -> usize {
self.inner.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()
}
}
#[derive(Debug, Clone)]
pub struct MovesIntoIter<'l> {
inner: InnerMovesIter<'l, ArrayVecIntoIter<RawMove, 218>>,
}
impl<'l> Iterator for MovesIntoIter<'l> {
type Item = Move<'l>;
crate::iterator::mk!();
}
impl<'l> FusedIterator for MovesIntoIter<'l> {}
impl<'l> ExactSizeIterator for MovesIntoIter<'l> {
#[inline]
fn len(&self) -> usize {
self.inner.len()
}
}
impl<'l> IntoIterator for Moves<'l> {
type Item = Move<'l>;
type IntoIter = MovesIntoIter<'l>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
let position = self.position();
let iter = self.into_list().array.into_iter();
let inner = InnerMovesIter { position, iter };
MovesIntoIter { inner }
}
}
#[derive(Debug, Clone)]
struct InnerMovesIter<'l, I: Iterator<Item = RawMove>> {
position: &'l Position,
iter: I,
}
impl<'l, I> Iterator for InnerMovesIter<'l, I>
where
I: Iterator<Item = RawMove>,
{
type Item = Move<'l>;
#[inline]
fn next(&mut self) -> Option<Move<'l>> {
self.iter.next().map(|raw| raw.to_move_inner(self.position))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
#[inline]
fn count(self) -> usize
where
Self: Sized,
{
self.iter.count()
}
#[inline]
fn last(self) -> Option<Move<'l>>
where
Self: Sized,
{
self.iter.last().map(|raw| raw.to_move_inner(self.position))
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Move<'l>> {
self.iter.nth(n).map(|raw| raw.to_move_inner(self.position))
}
#[inline]
fn for_each<F>(self, mut f: F)
where
Self: Sized,
F: FnMut(Move<'l>),
{
self.iter
.for_each(|raw| f(raw.to_move_inner(self.position)))
}
#[inline]
fn fold<B, F>(self, init: B, mut f: F) -> B
where
Self: Sized,
F: FnMut(B, Move<'l>) -> B,
{
self.iter
.fold(init, |acc, raw| f(acc, raw.to_move_inner(self.position)))
}
}
impl<'l, I> FusedIterator for InnerMovesIter<'l, I> where I: Iterator<Item = RawMove> + FusedIterator
{}
impl<'l, I> ExactSizeIterator for InnerMovesIter<'l, I>
where
I: Iterator<Item = RawMove> + ExactSizeIterator,
{
#[inline]
fn len(&self) -> usize {
self.iter.len()
}
}
impl core::fmt::Debug for Position {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
f.debug_tuple("Position")
.field(&self.to_text_record())
.finish()
}
}
impl Default for Position {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawMove {
pub(crate) from: Square,
pub(crate) to: Square,
pub(crate) promotion: Option<Role>,
pub(crate) kind: MoveType,
}
impl core::fmt::Debug for RawMove {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}({:?})", self.to_uci(), self.kind)
}
}
impl RawMove {
#[inline]
pub fn from(self) -> Square {
self.from
}
#[inline]
pub fn to(self) -> Square {
self.to
}
#[inline]
pub fn promotion(self) -> Option<Role> {
self.promotion
}
#[inline]
pub unsafe fn to_move_unchecked<'l>(self, position: &'l Position) -> Move<'l> {
self.to_move_inner(position)
}
#[inline]
pub(crate) fn to_move_inner<'l>(self, position: &'l Position) -> Move<'l> {
Move {
position,
raw: self,
}
}
#[inline]
pub fn to_uci(self) -> UciMove {
UciMove {
from: self.from(),
to: self.to(),
promotion: self.promotion(),
}
}
#[inline]
fn compare(&self, other: &Self) -> core::cmp::Ordering {
self.from
.cmp(&other.from)
.then(self.to.cmp(&other.to))
.then(self.promotion.cmp(&other.promotion))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum MoveType {
Normal,
CastleShort,
CastleLong,
PawnAdvance,
PawnCapture,
PawnAdvancePromotion,
PawnCapturePromotion,
PawnDoubleAdvance,
EnPassant,
}
struct MoveSetNormal {
kind: MoveType,
from: Square,
to: Bitboard,
}
#[rustfmt::skip]
impl MoveSet for MoveSetNormal {
#[inline]
fn len(&self) -> usize { self.to.len() }
#[inline]
fn is_empty(&self) -> bool { self.to.is_empty() }
#[inline]
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, mut f: F) -> ControlFlow<T> {
let Self { kind, from, mut to } = self;
to.try_for_each(|to| f(RawMove { kind, from, to, promotion: None }))
}
}
struct MoveSetCastleShort {
from: Square,
to: Square,
}
#[rustfmt::skip]
impl MoveSet for MoveSetCastleShort {
#[inline]
fn len(&self) -> usize { 1 }
#[inline]
fn is_empty(&self) -> bool { false }
#[inline]
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, mut f: F) -> ControlFlow<T> {
let Self { from, to } = self;
f(RawMove { kind: MoveType::CastleShort, from, to, promotion: None })
}
}
struct MoveSetCastleLong {
from: Square,
to: Square,
}
#[rustfmt::skip]
impl MoveSet for MoveSetCastleLong{
#[inline]
fn len(&self) -> usize { 1 }
#[inline]
fn is_empty(&self) -> bool { false }
#[inline]
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, mut f: F) -> ControlFlow<T> {
let Self { from, to } = self;
f(RawMove { kind: MoveType::CastleLong, from, to, promotion: None })
}
}
struct MoveSetPawnAdvance {
from: Direction,
to: Bitboard,
}
#[rustfmt::skip]
impl MoveSet for MoveSetPawnAdvance {
#[inline]
fn len(&self) -> usize {
let mask = Rank::First.bitboard() | Rank::Eighth.bitboard();
self.to.len() + (self.to & mask).len() * 3
}
#[inline]
fn is_empty(&self) -> bool { self.to.is_empty() }
#[inline]
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, mut f: F) -> ControlFlow<T> {
let Self { from, to } = self;
let mask = Rank::First.bitboard() | Rank::Eighth.bitboard();
(to & !mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnAdvance, from: to.trans_unchecked(from), to, promotion: None }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnAdvancePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Queen) }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnAdvancePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Rook) }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnAdvancePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Bishop) }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnAdvancePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Knight) }))?;
ControlFlow::Continue(())
}
}
struct MoveSetPawnCapture {
from: Direction,
to: Bitboard,
}
#[rustfmt::skip]
impl MoveSet for MoveSetPawnCapture {
#[inline]
fn len(&self) -> usize {
let mask = Rank::First.bitboard() | Rank::Eighth.bitboard();
self.to.len() + (self.to & mask).len() * 3
}
#[inline]
fn is_empty(&self) -> bool { self.to.is_empty() }
#[inline]
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, mut f: F) -> ControlFlow<T> {
let Self { from, to } = self;
let mask = Rank::First.bitboard() | Rank::Eighth.bitboard();
(to & !mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnCapture, from: to.trans_unchecked(from) , to, promotion: None }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnCapturePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Queen) }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnCapturePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Rook) }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnCapturePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Bishop) }))?;
(to & mask).try_for_each(|to| f(RawMove { kind: MoveType::PawnCapturePromotion, from: to.trans_unchecked(from), to, promotion: Some(Role::Knight) }))?;
ControlFlow::Continue(())
}
}
struct MoveSetDoubleAdvance {
from: Direction,
to: Bitboard,
}
#[rustfmt::skip]
impl MoveSet for MoveSetDoubleAdvance {
#[inline]
fn len(&self) -> usize { self.to.len() }
#[inline]
fn is_empty(&self) -> bool { self.to.is_empty() }
#[inline]
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, mut f: F) -> ControlFlow<T> {
let Self { from, mut to } = self;
to.try_for_each(|to| f(RawMove { kind: MoveType::PawnDoubleAdvance, from: to.trans_unchecked(from).trans_unchecked(from), to, promotion: None }))
}
}
struct MoveSetEnPassant {
from: Square,
to: Square,
}
#[rustfmt::skip]
impl MoveSet for MoveSetEnPassant {
#[inline]
fn len(&self) -> usize { 1 }
#[inline]
fn is_empty(&self) -> bool { false }
#[inline]
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, mut f: F) -> ControlFlow<T> {
let Self { from, to } = self;
f(RawMove { kind: MoveType::EnPassant, from, to, promotion: None })
}
}
pub(crate) trait MoveSet {
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn try_for_each<T, F: FnMut(RawMove) -> ControlFlow<T>>(self, _f: F) -> ControlFlow<T>;
}
pub(crate) trait MoveGen<T> {
#[inline]
fn checkers(&mut self, _: Bitboard) {}
#[inline]
fn pinned(&mut self, _: Bitboard) {}
#[inline]
fn en_passant(&mut self) {}
#[inline]
fn extend<S: MoveSet>(&mut self, _s: S) -> ControlFlow<T> {
ControlFlow::Continue(())
}
}
impl<F, T> MoveGen<T> for F
where
F: FnMut(RawMove) -> ControlFlow<T>,
{
fn extend<S: MoveSet>(&mut self, s: S) -> ControlFlow<T> {
s.try_for_each(self)
}
}
impl Position {
#[inline]
pub(crate) fn for_each_candidate<'a, F>(
&'a self,
role: Role,
from: Bitboard,
to: Square,
promotion: Option<Role>,
mut f: F,
) where
F: FnMut(Move<'a>),
{
let turn = self.turn();
let [u0, u1, u2, u3, _u4] = self.bitboards();
let blockers = u0 | u1 | u2;
let us = match turn {
Color::White => blockers ^ u3,
Color::Black => blockers & u3,
};
(match role {
Role::Pawn => {
let dir = !turn.forward();
let tmp = to.bitboard().trans(dir);
(tmp | tmp.trans(Direction::East) | tmp.trans(Direction::West) | tmp.trans(dir))
& u0 & !u1 & !u2
}
Role::Knight => lookup::knight(to) & !u0 & u1 & !u2,
Role::Bishop => lookup::bishop(to, blockers) & u0 & u1 & !u2,
Role::Rook => lookup::rook(to, blockers) & !u0 & !u1 & u2,
Role::Queen => {
(lookup::bishop(to, blockers) | lookup::rook(to, blockers)) & u0 & !u1 & u2
}
Role::King => !u0 & u1 & u2,
} & from & us)
.for_each(|from| {
if let Some(m) = self.check_move(from, to, promotion) {
f(m)
}
});
}
#[inline]
pub(crate) fn check_move<'a>(
&'a self,
from: Square,
to: Square,
promotion: Option<Role>,
) -> Option<Move<'a>> {
let Piece { role, color } = self.get(from)?;
let turn = self.turn();
if color != turn {
return None;
}
let promotion_mask = Rank::First.bitboard() | Rank::Eighth.bitboard();
let home_rank = turn.home_rank();
let dir = turn.forward();
let [u0, u1, u2, u3, _u4] = self.bitboards();
let blockers = u0 | u1 | u2;
let p = u0 & !u1 & !u2;
let n = !u0 & u1 & !u2;
let b = u0 & u1 & !u2;
let r = !u0 & !u1 & u2;
let q = u0 & !u1 & u2;
let k = !u0 & u1 & u2;
let (us, them) = match turn {
Color::White => (blockers ^ u3, blockers & u3),
Color::Black => (blockers & u3, blockers ^ u3),
};
let mut ksq = (us & k).first().unwrap();
match promotion {
None => {
if role == Role::Pawn && promotion_mask.contains(to) {
return None;
}
}
Some(Role::Pawn | Role::King) => return None,
Some(_) => {
if role != Role::Pawn || !promotion_mask.contains(to) {
return None;
}
}
}
let kind = match role {
Role::Pawn => {
let fw = from.bitboard().trans(dir);
if (fw & !blockers).contains(to) {
if promotion.is_some() {
MoveType::PawnAdvancePromotion
} else {
MoveType::PawnAdvance
}
} else if (fw.trans(Direction::East) | fw.trans(Direction::West)).contains(to) {
if them.contains(to) {
if promotion.is_some() {
MoveType::PawnCapturePromotion
} else {
MoveType::PawnCapture
}
} else if self.en_passant_target_square() == Some(to) {
MoveType::EnPassant
} else {
return None;
}
} else if ((fw & !blockers).trans(dir)
& !blockers & (match turn {
Color::White => Rank::Fourth,
Color::Black => Rank::Fifth,
})
.bitboard())
.contains(to)
{
MoveType::PawnDoubleAdvance
} else {
return None;
}
}
Role::Knight => {
if !(!us & lookup::knight(from)).contains(to) {
return None;
}
MoveType::Normal
}
Role::Bishop => {
if !(!us & lookup::bishop(from, blockers)).contains(to) {
return None;
}
MoveType::Normal
}
Role::Rook => {
if !(!us & lookup::rook(from, blockers)).contains(to) {
return None;
}
MoveType::Normal
}
Role::Queen => {
if !(!us & (lookup::bishop(from, blockers) | lookup::rook(from, blockers)))
.contains(to)
{
return None;
}
MoveType::Normal
}
Role::King => {
ksq = to;
if from == Square::from_coords(File::E, home_rank)
&& (to.file() == File::G || to.file() == File::C)
{
let attacked = {
let blockers = blockers ^ (us & k);
let king_square = (them & k).first().unwrap();
let mut res = lookup::king(king_square);
res = (them & (b | q))
.fold(res, |acc, sq| acc | lookup::bishop(sq, blockers));
res =
(them & (r | q)).fold(res, |acc, sq| acc | lookup::rook(sq, blockers));
res = (them & n).fold(res, |acc, sq| acc | lookup::knight(sq));
res |= (them & p).trans(!dir).trans(Direction::East);
res |= (them & p).trans(!dir).trans(Direction::West);
res
};
let kind = if to == Square::from_coords(File::G, home_rank)
&& self.castling_rights(turn, Castle::Short)
{
let x = Bitboard(0x6060606060606060) & home_rank.bitboard();
let y = Bitboard(0x7070707070707070) & home_rank.bitboard();
if !(x & blockers | y & attacked).is_empty() {
return None;
}
MoveType::CastleShort
} else if to == Square::from_coords(File::C, home_rank)
&& self.castling_rights(turn, Castle::Long)
{
let x = Bitboard(0x0E0E0E0E0E0E0E0E) & home_rank.bitboard();
let y = Bitboard(0x1C1C1C1C1C1C1C1C) & home_rank.bitboard();
if !(x & blockers | y & attacked).is_empty() {
return None;
}
MoveType::CastleLong
} else {
return None;
};
return Some(
RawMove {
kind,
from,
to,
promotion,
}
.to_move_inner(self),
);
} else if (lookup::king(from) & !us).contains(to) {
MoveType::Normal
} else {
return None;
}
}
};
let mut us = us & !from.bitboard() | to.bitboard();
let mut them = them & !to.bitboard();
match kind {
MoveType::EnPassant => {
them &= !(to.bitboard().trans(!dir));
}
MoveType::CastleShort => {
us.remove(Square::from_coords(File::H, home_rank));
us.insert(Square::from_coords(File::F, home_rank));
}
MoveType::CastleLong => {
us.remove(Square::from_coords(File::A, home_rank));
us.insert(Square::from_coords(File::D, home_rank));
}
_ => {}
}
let blockers = us | them;
let checkers = them
& (lookup::pawn_attack(turn, ksq) & p
| lookup::knight(ksq) & n
| lookup::bishop(ksq, blockers) & (b | q)
| lookup::rook(ksq, blockers) & (r | q)
| lookup::king(ksq) & k);
if !checkers.is_empty() {
return None;
}
Some(
RawMove {
kind,
from,
to,
promotion,
}
.to_move_inner(self),
)
}
#[inline]
pub(crate) fn generate_moves<S, T>(&self, moves: &mut T) -> ControlFlow<S>
where
T: MoveGen<S>,
{
let turn = self.turn();
let en_passant = self.en_passant_target_square();
let [a, b, c, d, _] = self.bitboards();
let blockers = a | b | c;
let (us, them) = match turn {
Color::White => (blockers ^ d, d),
Color::Black => (d, blockers ^ d),
};
let pieces = self.pieces();
let ours = *pieces.get(turn);
let theirs = *pieces.get(!turn);
let king_square = ours.king().first().unwrap();
let forward = turn.forward();
{
let attacked = {
let blockers = blockers ^ ours.king();
let king_square = theirs.king().first().unwrap();
let mut res = lookup::king(king_square);
res = (theirs.bishop() | theirs.queen())
.fold(res, |acc, sq| acc | lookup::bishop(sq, blockers));
res = (theirs.rook() | theirs.queen())
.fold(res, |acc, sq| acc | lookup::rook(sq, blockers));
res = theirs
.knight()
.fold(res, |acc, sq| acc | lookup::knight(sq));
res |= theirs.pawn().trans(!forward).trans(Direction::East);
res |= theirs.pawn().trans(!forward).trans(Direction::West);
res
};
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from: king_square,
to: lookup::king(king_square) & !us & !attacked,
})?;
if self.castling_rights(turn, Castle::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 = from
.trans_unchecked(Direction::East)
.trans_unchecked(Direction::East);
moves.extend(MoveSetCastleShort { from, to })?;
}
}
if self.castling_rights(turn, Castle::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 = from
.trans_unchecked(Direction::West)
.trans_unchecked(Direction::West);
moves.extend(MoveSetCastleLong { from, to })?;
}
}
}
let bishop_look = lookup::bishop(king_square, blockers);
let rook_look = lookup::rook(king_square, blockers);
let mut checkers = lookup::pawn_attack(turn, king_square) & theirs.pawn()
| lookup::knight(king_square) & theirs.knight()
| bishop_look & (theirs.bishop() | theirs.queen())
| rook_look & (theirs.rook() | theirs.queen());
moves.checkers(checkers);
let checker = checkers.next();
if !checkers.is_empty() {
return ControlFlow::Continue(());
}
let check_path = checker
.map(|checker| lookup::segment(king_square, checker))
.unwrap_or(Bitboard(!0));
let masked_blockers = blockers & !(bishop_look | rook_look);
let pinned = ((lookup::bishop(king_square, masked_blockers)
& (theirs.bishop() | theirs.queen()))
| (lookup::rook(king_square, masked_blockers) & (theirs.rook() | theirs.queen())))
.fold(Bitboard::new(), |acc, sq| {
acc | lookup::segment(king_square, sq)
});
moves.pinned(pinned);
{
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 = (ours.pawn() & (!pinned | king_square.file().bitboard())).trans(forward)
& !blockers;
moves.extend(MoveSetPawnAdvance {
from: !forward,
to: adv & check_path,
})?;
moves.extend(MoveSetPawnCapture {
from: !kside,
to: (ours.pawn() & (!pinned | lookup::ray(king_square, kside))).trans(kside)
& them & check_path,
})?;
moves.extend(MoveSetPawnCapture {
from: !qside,
to: (ours.pawn() & (!pinned | lookup::ray(king_square, qside))).trans(qside)
& them & check_path,
})?;
moves.extend(MoveSetDoubleAdvance {
from: !forward,
to: (adv & third_rank.bitboard()).trans(forward) & !blockers & check_path,
})?;
if let Some(to) = en_passant {
let capture_square = to.trans_unchecked(!forward);
if check_path.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())
.fold(Bitboard::new(), |acc, sq| {
acc | lookup::segment(king_square, sq)
});
let mut candidates = candidates & (!pinned | lookup::segment(king_square, to));
if let Some(from) = candidates.next() {
moves.en_passant();
moves.extend(MoveSetEnPassant { from, to })?;
if let Some(from) = candidates.next() {
moves.extend(MoveSetEnPassant { from, to })?;
}
}
}
}
}
{
let target_mask = check_path & !us;
(!pinned & ours.knight()).control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::knight(from),
})
})?;
(!pinned & (ours.bishop() | ours.queen())).control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::bishop(from, blockers),
})
})?;
(!pinned & (ours.rook() | ours.queen())).control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::rook(from, blockers),
})
})?;
}
if checker.is_some() {
return ControlFlow::Continue(());
}
{
let target_mask = !us & pinned;
(pinned & lookup::bishop_lines(king_square) & (ours.bishop() | ours.queen()))
.control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::line(king_square, from),
})
})?;
(pinned & lookup::rook_lines(king_square) & (ours.rook() | ours.queen()))
.control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::line(king_square, from),
})
})?;
}
ControlFlow::Continue(())
}
}