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> {
#[inline(never)]
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(()): ControlFlow<Infallible> = self
.position
.try_for_each_candidate(role, !from.bitboard(), to, promotion, |m: Move| {
candidates.insert(m.from());
ControlFlow::Continue(())
});
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()))
};
let data = SanData::Normal {
role,
from,
to,
promotion,
};
San {
data,
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: for<'a> FnMut(Move<'a>, Move<'a>) -> 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: for<'a> FnMut(Move<'a>, Move<'a>) -> 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: for<'a> FnMut(Move<'a>, Move<'a>) -> 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,
}
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,
}
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,
}
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,
}
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,
}
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,
}
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,
}
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]
fn pawns(&self) -> Bitboard {
let [u0, u1, u2, _, _] = self.bitboards();
u0 & !u1 & !u2
}
#[inline]
fn knights(&self) -> Bitboard {
let [u0, u1, u2, _, _] = self.bitboards();
!u0 & u1 & !u2
}
#[inline]
fn bishops(&self) -> Bitboard {
let [u0, u1, u2, _, _] = self.bitboards();
u0 & u1 & !u2
}
#[inline]
fn rooks(&self) -> Bitboard {
let [u0, u1, u2, _, _] = self.bitboards();
!u0 & !u1 & u2
}
#[inline]
fn queens(&self) -> Bitboard {
let [u0, u1, u2, _, _] = self.bitboards();
u0 & !u1 & u2
}
#[inline]
fn kings(&self) -> Bitboard {
let [u0, u1, u2, _, _] = self.bitboards();
!u0 & u1 & u2
}
#[inline]
fn blockers(&self) -> Bitboard {
let [u0, u1, u2, _, _] = self.bitboards();
u0 | u1 | u2
}
#[inline]
fn us(&self) -> Bitboard {
let [_, _, _, u3, _] = self.bitboards();
match self.turn() {
Color::White => self.blockers() ^ u3,
Color::Black => self.blockers() & u3,
}
}
#[inline]
fn them(&self) -> Bitboard {
self.blockers() ^ self.us()
}
#[inline]
fn ksq(&self) -> Square {
(self.kings() & self.us()).first().unwrap()
}
#[inline]
fn attacked(&self) -> Bitboard {
let turn = self.turn();
let dir = !turn.forward();
let p = self.pawns();
let n = self.knights();
let b = self.bishops();
let r = self.rooks();
let q = self.queens();
let k = self.kings();
let us = self.us();
let them = self.them();
let blockers = self.blockers() ^ (us & k);
let mut res = lookup::king((them & k).first().unwrap());
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
}
#[inline]
fn is_check_after_move(&self, kind: MoveType, from: Square, to: Square) -> bool {
let turn = self.turn();
let us = (self.us() ^ from.bitboard()) | to.bitboard();
let dir = !turn.forward();
let them = match kind {
MoveType::EnPassant => self.them() ^ to.bitboard().trans(dir),
_ => self.them() & !to.bitboard(),
};
let blockers = us | them;
let ksq = self.ksq();
let checkers = lookup::king(ksq) & self.kings()
| lookup::pawn_attack(turn, ksq) & self.pawns()
| lookup::knight(ksq) & self.knights()
| lookup::bishop(ksq, blockers) & (self.bishops() | self.queens())
| lookup::rook(ksq, blockers) & (self.rooks() | self.queens());
!(them & checkers).is_empty()
}
#[inline]
pub(crate) fn try_for_each_candidate<'a, T, F>(
&'a self,
role: Role,
from: Bitboard,
to: Square,
promotion: Option<Role>,
mut f: F,
) -> ControlFlow<T>
where
F: FnMut(Move<'a>) -> ControlFlow<T>,
{
let mut aux = |kind: MoveType, from: Square| -> ControlFlow<T> {
debug_assert!(role != Role::King);
if !self.is_check_after_move(kind, from, to) {
f(RawMove {
kind,
from,
to,
promotion,
}
.to_move_inner(self))?;
}
ControlFlow::Continue(())
};
let turn = self.turn();
let dir = !turn.forward();
let home_rank = turn.home_rank();
let promotion_mask = Rank::First.bitboard() | Rank::Eighth.bitboard();
let blockers = self.blockers();
let p = self.pawns();
let n = self.knights();
let b = self.bishops();
let r = self.rooks();
let q = self.queens();
let us = self.us();
let them = self.them();
let ksq = self.ksq();
if us.contains(to) {
return ControlFlow::Continue(());
}
if role == Role::Pawn {
match promotion {
Some(Role::Pawn | Role::King) => {
return ControlFlow::Continue(());
}
Some(Role::Knight | Role::Bishop | Role::Rook | Role::Queen) => {
if !promotion_mask.contains(to) {
return ControlFlow::Continue(());
}
}
None => {
if promotion_mask.contains(to) {
return ControlFlow::Continue(());
}
}
}
} else if promotion.is_some() {
return ControlFlow::Continue(());
}
let (kind, mask) = match role {
Role::Pawn => {
let mask = p & us & from;
let tmp = to.bitboard().trans(dir);
if them.contains(to) {
(
if promotion.is_some() {
MoveType::PawnCapturePromotion
} else {
MoveType::PawnCapture
},
(tmp.trans(Direction::East) | tmp.trans(Direction::West)) & mask,
)
} else if !(tmp & mask).is_empty() {
aux(
if promotion.is_some() {
MoveType::PawnAdvancePromotion
} else {
MoveType::PawnAdvance
},
to.trans_unchecked(dir),
)?;
return ControlFlow::Continue(());
} else if !((Rank::Second.bitboard() | Rank::Seventh.bitboard())
& (tmp & !blockers).trans(dir)
& mask)
.is_empty()
{
aux(
MoveType::PawnDoubleAdvance,
to.trans_unchecked(dir).trans_unchecked(dir),
)?;
return ControlFlow::Continue(());
} else if self.en_passant_target_square() == Some(to) {
(
MoveType::EnPassant,
(tmp.trans(Direction::East) | tmp.trans(Direction::West)) & mask,
)
} else {
return ControlFlow::Continue(());
}
}
Role::Knight => (MoveType::Normal, lookup::knight(to) & n),
Role::Bishop => (MoveType::Normal, lookup::bishop(to, blockers) & b),
Role::Rook => (MoveType::Normal, lookup::rook(to, blockers) & r),
Role::Queen => (
MoveType::Normal,
(lookup::bishop(to, blockers) | lookup::rook(to, blockers)) & q,
),
Role::King => {
if !from.contains(ksq) {
return ControlFlow::Continue(());
}
let attacked = self.attacked();
if lookup::king(to).contains(ksq) {
if !attacked.contains(to) {
f(RawMove {
kind: MoveType::Normal,
from: ksq,
to,
promotion: None,
}
.to_move_inner(self))?;
}
} else if self.castling_rights(turn, Castle::Short)
&& to == Square::from_coords(File::G, home_rank)
{
let x = Bitboard(0x6060606060606060) & home_rank.bitboard();
let y = Bitboard(0x7070707070707070) & home_rank.bitboard();
if !(x & blockers | y & attacked).is_empty() {
return ControlFlow::Continue(());
}
f(RawMove {
kind: MoveType::CastleShort,
from: ksq,
to,
promotion: None,
}
.to_move_inner(self))?;
} else if self.castling_rights(turn, Castle::Long)
&& to == Square::from_coords(File::C, home_rank)
{
let x = Bitboard(0x0E0E0E0E0E0E0E0E) & home_rank.bitboard();
let y = Bitboard(0x1C1C1C1C1C1C1C1C) & home_rank.bitboard();
if !(x & blockers | y & attacked).is_empty() {
return ControlFlow::Continue(());
}
f(RawMove {
kind: MoveType::CastleLong,
from: ksq,
to,
promotion: None,
}
.to_move_inner(self))?;
}
return ControlFlow::Continue(());
}
};
(us & from & mask).try_for_each(|from| aux(kind, from))?;
ControlFlow::Continue(())
}
#[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 blockers = self.blockers();
let us = self.us();
let them = self.them();
if role == Role::Pawn {
match promotion {
Some(Role::Pawn | Role::King) => {
return None;
}
Some(Role::Knight | Role::Bishop | Role::Rook | Role::Queen) => {
if !promotion_mask.contains(to) {
return None;
}
}
None => {
if promotion_mask.contains(to) {
return None;
}
}
}
} else if promotion.is_some() {
return None;
}
let kind = match role {
Role::Pawn => {
let tmp = from.bitboard().trans(dir);
if (tmp & !blockers).contains(to) {
if promotion.is_some() {
MoveType::PawnAdvancePromotion
} else {
MoveType::PawnAdvance
}
} else if (tmp.trans(Direction::East) | tmp.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 ((tmp & !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 => {
let attacked = self.attacked();
if (lookup::king(from) & !us & !attacked).contains(to) {
return Some(
RawMove {
kind: MoveType::Normal,
from,
to,
promotion,
}
.to_move_inner(self),
);
} else if from == Square::from_coords(File::E, home_rank)
&& (to.file() == File::G || to.file() == File::C)
{
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 {
return None;
}
}
};
if self.is_check_after_move(kind, from, to) {
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 blockers = self.blockers();
let p = self.pawns();
let n = self.knights();
let b = self.bishops();
let r = self.rooks();
let q = self.queens();
let us = self.us();
let them = self.them();
let king_square = self.ksq();
let forward = turn.forward();
{
let attacked = self.attacked();
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 = them
& (lookup::pawn_attack(turn, king_square) & p
| lookup::knight(king_square) & n
| bishop_look & (b | q)
| rook_look & (r | q));
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) & them & (b | q))
| (lookup::rook(king_square, masked_blockers) & them & (r | q)))
.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 =
(us & p & (!pinned | king_square.file().bitboard())).trans(forward) & !blockers;
moves.extend(MoveSetPawnAdvance {
from: !forward,
to: adv & check_path,
})?;
moves.extend(MoveSetPawnCapture {
from: !kside,
to: (us & p & (!pinned | lookup::ray(king_square, kside))).trans(kside)
& them & check_path,
})?;
moves.extend(MoveSetPawnCapture {
from: !qside,
to: (us & p & (!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) & us & p;
let blockers = blockers ^ capture_square.bitboard();
let pinned = pinned
| (lookup::rook(
king_square,
blockers & !(lookup::rook(king_square, blockers)),
) & them & r)
.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 & us & n).control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::knight(from),
})
})?;
(!pinned & us & (b | q)).control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::bishop(from, blockers),
})
})?;
(!pinned & us & (r | q)).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) & us & (b | q)).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) & us & (r | q)).control_for_each(|from| {
moves.extend(MoveSetNormal {
kind: MoveType::Normal,
from,
to: target_mask & lookup::line(king_square, from),
})
})?;
}
ControlFlow::Continue(())
}
}