use core::{
error, fmt,
hash::{Hash, Hasher},
num::NonZeroU32,
str::FromStr,
};
use bitflags::bitflags;
use crate::{
Bitboard, Board, ByColor, ByRole, Castles, CastlingMode, CastlingSide, Color,
Color::{Black, White},
EnPassantMode, Move, MoveList, Piece, Rank, RemainingChecks, Role, Setup, Square, attacks,
bitboard::Direction,
setup::EnPassant,
zobrist::ZobristValue,
};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum KnownOutcome {
Decisive { winner: Color },
Draw,
}
impl KnownOutcome {
pub const fn from_winner(winner: Option<Color>) -> KnownOutcome {
match winner {
Some(winner) => KnownOutcome::Decisive { winner },
None => KnownOutcome::Draw,
}
}
pub const fn winner(self) -> Option<Color> {
match self {
KnownOutcome::Decisive { winner } => Some(winner),
KnownOutcome::Draw => None,
}
}
pub const fn from_ascii(bytes: &[u8]) -> Result<KnownOutcome, ParseOutcomeError> {
Ok(match bytes {
b"1-0" => KnownOutcome::Decisive { winner: White },
b"0-1" => KnownOutcome::Decisive { winner: Black },
b"1/2-1/2" => KnownOutcome::Draw,
_ => return Err(ParseOutcomeError),
})
}
pub const fn as_str(self) -> &'static str {
match self {
KnownOutcome::Decisive { winner: White } => "1-0",
KnownOutcome::Decisive { winner: Black } => "0-1",
KnownOutcome::Draw => "1/2-1/2",
}
}
}
impl fmt::Display for KnownOutcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for KnownOutcome {
type Err = ParseOutcomeError;
fn from_str(s: &str) -> Result<KnownOutcome, ParseOutcomeError> {
KnownOutcome::from_ascii(s.as_bytes())
}
}
#[cfg(feature = "bincode")]
impl bincode::Encode for KnownOutcome {
fn encode<E: bincode::enc::Encoder>(
&self,
encoder: &mut E,
) -> Result<(), bincode::error::EncodeError> {
Outcome::Known(*self).encode(encoder)
}
}
#[cfg(feature = "bincode")]
impl<Config> bincode::Decode<Config> for KnownOutcome {
fn decode<D: bincode::de::Decoder>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
Outcome::decode(decoder).and_then(|outcome| {
outcome
.known()
.ok_or(bincode::error::DecodeError::Other("invalid KnownOutcome"))
})
}
}
#[cfg(feature = "bincode")]
bincode::impl_borrow_decode!(KnownOutcome);
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Outcome {
Known(KnownOutcome),
Unknown,
}
impl Outcome {
pub const fn from_known(outcome: Option<KnownOutcome>) -> Outcome {
match outcome {
Some(outcome) => Self::Known(outcome),
None => Self::Unknown,
}
}
pub const fn known(self) -> Option<KnownOutcome> {
match self {
Self::Known(outcome) => Some(outcome),
Self::Unknown => None,
}
}
pub const fn is_known(self) -> bool {
matches!(self, Self::Known(_))
}
pub const fn is_unknown(self) -> bool {
matches!(self, Self::Unknown)
}
pub const fn winner(self) -> Option<Color> {
match self {
Self::Known(outcome) => outcome.winner(),
Self::Unknown => None,
}
}
pub fn from_ascii(bytes: &[u8]) -> Result<Outcome, ParseOutcomeError> {
if bytes == b"*" {
Ok(Self::Unknown)
} else {
KnownOutcome::from_ascii(bytes).map(Self::Known)
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Known(outcome) => outcome.as_str(),
Self::Unknown => "*",
}
}
}
impl From<KnownOutcome> for Outcome {
fn from(outcome: KnownOutcome) -> Self {
Self::Known(outcome)
}
}
impl FromStr for Outcome {
type Err = ParseOutcomeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_ascii(s.as_bytes())
}
}
impl fmt::Display for Outcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug)]
pub struct ParseOutcomeError;
impl fmt::Display for ParseOutcomeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid outcome")
}
}
impl error::Error for ParseOutcomeError {}
#[cfg(feature = "bincode")]
impl bincode::Encode for Outcome {
fn encode<E: bincode::enc::Encoder>(
&self,
encoder: &mut E,
) -> Result<(), bincode::error::EncodeError> {
u8::encode(
&match self {
Outcome::Unknown => 0,
Outcome::Known(KnownOutcome::Decisive {
winner: Color::White,
}) => 1,
Outcome::Known(KnownOutcome::Decisive {
winner: Color::Black,
}) => 2,
Outcome::Known(KnownOutcome::Draw) => 3,
},
encoder,
)
}
}
#[cfg(feature = "bincode")]
impl<Config> bincode::Decode<Config> for Outcome {
fn decode<D: bincode::de::Decoder>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
Ok(match u8::decode(decoder)? {
0 => Outcome::Unknown,
1 => Outcome::Known(KnownOutcome::Decisive {
winner: Color::White,
}),
2 => Outcome::Known(KnownOutcome::Decisive {
winner: Color::Black,
}),
3 => Outcome::Known(KnownOutcome::Draw),
_ => return Err(bincode::error::DecodeError::Other("invalid Outcome")),
})
}
}
#[cfg(feature = "bincode")]
bincode::impl_borrow_decode!(Outcome);
#[derive(Debug)]
pub struct PlayError<P> {
pub m: Move,
pub position: P,
}
impl<P: fmt::Debug> fmt::Display for PlayError<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "illegal move {:?} in {:?}", self.m, self.position)
}
}
impl<P: fmt::Debug> error::Error for PlayError<P> {}
bitflags! {
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PositionErrorKinds: u32 {
const EMPTY_BOARD = 1 << 0;
const MISSING_KING = 1 << 1;
const TOO_MANY_KINGS = 1 << 2;
const PAWNS_ON_BACKRANK = 1 << 3;
const INVALID_CASTLING_RIGHTS = 1 << 4;
const INVALID_EP_SQUARE = 1 << 5;
const OPPOSITE_CHECK = 1 << 6;
const IMPOSSIBLE_CHECK = 1 << 7;
const TOO_MUCH_MATERIAL = 1 << 8;
const VARIANT = 1 << 9;
}
}
#[derive(Clone)]
pub struct PositionError<P> {
pub(crate) pos: P,
pub(crate) errors: PositionErrorKinds,
}
impl<P> PositionError<P> {
fn ignore(mut self, ignore: PositionErrorKinds) -> Result<P, Self> {
self.errors -= ignore;
match self {
PositionError { pos, errors } if errors.is_empty() => Ok(pos),
_ => Err(self),
}
}
fn strict(self) -> Result<P, Self> {
self.ignore(PositionErrorKinds::empty())
}
pub fn ignore_invalid_castling_rights(self) -> Result<P, Self> {
self.ignore(PositionErrorKinds::INVALID_CASTLING_RIGHTS)
}
pub fn ignore_invalid_ep_square(self) -> Result<P, Self> {
self.ignore(PositionErrorKinds::INVALID_EP_SQUARE)
}
pub fn ignore_too_much_material(self) -> Result<P, Self> {
self.ignore(PositionErrorKinds::TOO_MUCH_MATERIAL)
}
pub fn ignore_impossible_check(self) -> Result<P, Self> {
self.ignore(PositionErrorKinds::IMPOSSIBLE_CHECK)
}
pub fn kinds(&self) -> PositionErrorKinds {
self.errors
}
}
impl<P> fmt::Debug for PositionError<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PositionError")
.field("errors", &self.errors)
.finish_non_exhaustive()
}
}
impl<P> fmt::Display for PositionError<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("illegal position: ")?;
let mut first = true;
let mut reason = |kind: PositionErrorKinds, display: &str| -> fmt::Result {
if self.errors.contains(kind) {
if !first {
f.write_str(", ")?;
}
f.write_str(display)?;
first = false;
}
Ok(())
};
reason(PositionErrorKinds::EMPTY_BOARD, "empty board")?;
reason(PositionErrorKinds::MISSING_KING, "missing king")?;
reason(PositionErrorKinds::TOO_MANY_KINGS, "too many kings")?;
reason(PositionErrorKinds::PAWNS_ON_BACKRANK, "pawns on backrank")?;
reason(
PositionErrorKinds::INVALID_CASTLING_RIGHTS,
"invalid castling rights",
)?;
reason(PositionErrorKinds::INVALID_EP_SQUARE, "invalid ep square")?;
reason(PositionErrorKinds::OPPOSITE_CHECK, "opposite check")?;
reason(PositionErrorKinds::IMPOSSIBLE_CHECK, "impossible check")?;
reason(PositionErrorKinds::TOO_MUCH_MATERIAL, "too much material")?;
reason(PositionErrorKinds::VARIANT, "variant rule violated")?;
if first {
f.write_str("unknown reason")?;
}
Ok(())
}
}
impl<P> error::Error for PositionError<P> {}
pub trait FromSetup: Sized {
fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Self, PositionError<Self>>;
}
pub trait Position {
fn board(&self) -> &Board;
fn promoted(&self) -> Bitboard;
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>>;
fn turn(&self) -> Color;
fn castles(&self) -> &Castles;
fn maybe_ep_square(&self) -> Option<Square>;
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>>;
fn halfmoves(&self) -> u32;
fn fullmoves(&self) -> NonZeroU32;
fn to_setup(&self, mode: EnPassantMode) -> Setup {
Setup {
board: self.board().clone(),
promoted: self.promoted(),
pockets: self.pockets().copied(),
turn: self.turn(),
castling_rights: self.castles().castling_rights(),
ep_square: self.ep_square(mode),
remaining_checks: self.remaining_checks().copied(),
halfmoves: self.halfmoves(),
fullmoves: self.fullmoves(),
}
}
fn legal_moves(&self) -> MoveList;
fn san_candidates(&self, role: Role, to: Square) -> MoveList {
let mut moves = self.legal_moves();
filter_san_candidates(role, to, &mut moves);
moves
}
fn castling_moves(&self, side: CastlingSide) -> MoveList {
let mut moves = self.legal_moves();
moves.retain(|m| m.castling_side().is_some_and(|s| side == s));
moves
}
fn en_passant_moves(&self) -> MoveList {
let mut moves = self.legal_moves();
moves.retain(|m| m.is_en_passant());
moves
}
fn capture_moves(&self) -> MoveList {
let mut moves = self.legal_moves();
moves.retain(|m| m.is_capture());
moves
}
fn promotion_moves(&self) -> MoveList {
let mut moves = self.legal_moves();
moves.retain(|m| m.is_promotion());
moves
}
fn is_irreversible(&self, m: Move) -> bool {
(match m {
Move::Normal {
role: Role::Pawn, ..
}
| Move::Normal {
capture: Some(_), ..
}
| Move::Castle { .. }
| Move::EnPassant { .. }
| Move::Put { .. } => true,
Move::Normal { role, from, to, .. } => {
self.castles().castling_rights().contains(from)
|| self.castles().castling_rights().contains(to)
|| (role == Role::King && self.castles().has_color(self.turn()))
}
}) || self.legal_ep_square().is_some()
}
fn king_attackers(&self, square: Square, attacker: Color, occupied: Bitboard) -> Bitboard {
self.board().attacks_to(square, attacker, occupied)
}
fn is_variant_end(&self) -> bool;
fn has_insufficient_material(&self, color: Color) -> bool;
fn variant_outcome(&self) -> Outcome;
fn play_unchecked(&mut self, m: Move);
fn us(&self) -> Bitboard {
self.board().by_color(self.turn())
}
fn our(&self, role: Role) -> Bitboard {
self.board().by_piece(role.of(self.turn()))
}
fn them(&self) -> Bitboard {
self.board().by_color(!self.turn())
}
fn their(&self, role: Role) -> Bitboard {
self.board().by_piece(role.of(!self.turn()))
}
fn is_legal(&self, m: Move) -> bool {
let moves = match m {
Move::Normal { role, to, .. } | Move::Put { role, to } => self.san_candidates(role, to),
Move::EnPassant { to, .. } => self.san_candidates(Role::Pawn, to),
Move::Castle { king, rook } if king.file() < rook.file() => {
self.castling_moves(CastlingSide::KingSide)
}
Move::Castle { .. } => self.castling_moves(CastlingSide::QueenSide),
};
moves.contains(&m)
}
fn pseudo_legal_ep_square(&self) -> Option<Square> {
self.maybe_ep_square().filter(|ep_square| {
(attacks::pawn_attacks(!self.turn(), *ep_square) & self.our(Role::Pawn)).any()
})
}
fn legal_ep_square(&self) -> Option<Square> {
self.pseudo_legal_ep_square()
.filter(|_| !self.en_passant_moves().is_empty())
}
fn ep_square(&self, mode: EnPassantMode) -> Option<Square> {
match mode {
EnPassantMode::Always => self.maybe_ep_square(),
EnPassantMode::PseudoLegal => self.pseudo_legal_ep_square(),
EnPassantMode::Legal => self.legal_ep_square(),
}
}
fn checkers(&self) -> Bitboard {
self.our(Role::King).first().map_or(Bitboard(0), |king| {
self.king_attackers(king, !self.turn(), self.board().occupied())
})
}
fn is_check(&self) -> bool {
self.checkers().any()
}
fn is_checkmate(&self) -> bool {
!self.checkers().is_empty() && self.legal_moves().is_empty()
}
fn is_stalemate(&self) -> bool {
self.checkers().is_empty() && !self.is_variant_end() && self.legal_moves().is_empty()
}
fn is_insufficient_material(&self) -> bool {
self.has_insufficient_material(White) && self.has_insufficient_material(Black)
}
fn is_game_over(&self) -> bool {
self.is_variant_end() || self.legal_moves().is_empty() || self.is_insufficient_material()
}
fn outcome(&self) -> Outcome {
let variant_outcome = self.variant_outcome();
if variant_outcome.is_known() {
return variant_outcome;
}
if self.legal_moves().is_empty() {
if self.is_check() {
Outcome::Known(KnownOutcome::Decisive {
winner: !self.turn(),
})
} else {
Outcome::Known(KnownOutcome::Draw) }
} else if self.is_insufficient_material() {
Outcome::Known(KnownOutcome::Draw)
} else {
Outcome::Unknown
}
}
fn play(mut self, m: Move) -> Result<Self, PlayError<Self>>
where
Self: Sized,
{
if self.is_legal(m) {
self.play_unchecked(m);
Ok(self)
} else {
Err(PlayError { m, position: self })
}
}
fn swap_turn(self) -> Result<Self, PositionError<Self>>
where
Self: Sized + FromSetup,
{
let mode = self.castles().mode();
let mut setup = self.to_setup(EnPassantMode::Always);
setup.swap_turn();
Self::from_setup(setup, mode)
}
fn zobrist_hash<V: ZobristValue>(&self, mode: EnPassantMode) -> V
where
Self: Sized,
{
let mut zobrist = self.board().board_zobrist_hash();
for sq in self.promoted() {
zobrist ^= V::zobrist_for_promoted(sq);
}
if let Some(pockets) = self.pockets() {
for (color, pocket) in pockets.zip_color() {
for (role, pieces) in pocket.zip_role() {
zobrist ^= V::zobrist_for_pocket(color, role, pieces);
}
}
}
if self.turn() == Color::White {
zobrist ^= V::zobrist_for_white_turn();
}
let castles = self.castles();
for color in Color::ALL {
for side in CastlingSide::ALL {
if castles.has(color, side) {
zobrist ^= V::zobrist_for_castling_right(color, side);
}
}
}
if let Some(sq) = self.ep_square(mode) {
zobrist ^= V::zobrist_for_en_passant_file(sq.file());
}
if let Some(remaining_checks) = self.remaining_checks() {
for (color, remaining) in remaining_checks.zip_color() {
zobrist ^= V::zobrist_for_remaining_checks(color, remaining);
}
}
zobrist
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
current: V,
m: Move,
mode: EnPassantMode,
) -> Option<V>
where
Self: Sized,
{
let _current = current;
let _m = m;
let _mode = mode;
None
}
}
#[cfg(feature = "arbitrary")]
#[derive(arbitrary::Arbitrary)]
enum PositionMutation {
LegalMove { idx: u8 },
DiscardPieceAt { sq: Square },
SetPieceAt { sq: Square, piece: Piece },
ToggleCastles { rooks: Bitboard, mode: CastlingMode },
}
#[cfg(feature = "arbitrary")]
fn arbitrary_position<P>(
mutations: impl IntoIterator<Item = arbitrary::Result<PositionMutation>>,
) -> arbitrary::Result<P>
where
P: Position + FromSetup + Default,
{
let mut pos = P::default();
for mutation in mutations {
let mutation = mutation?;
match mutation {
PositionMutation::LegalMove { idx } => {
let moves = pos.legal_moves();
if let Some(idx) = usize::from(idx).checked_rem(moves.len()) {
pos.play_unchecked(moves[idx]);
}
}
PositionMutation::DiscardPieceAt { sq } => {
let mut setup = pos.to_setup(EnPassantMode::PseudoLegal);
setup.board.discard_piece_at(sq);
if let Ok(updated_pos) = setup
.position(pos.castles().mode())
.or_else(PositionError::ignore_invalid_castling_rights)
.or_else(PositionError::ignore_invalid_ep_square)
{
pos = updated_pos;
}
}
PositionMutation::SetPieceAt { sq, piece } => {
let mut setup = pos.to_setup(EnPassantMode::PseudoLegal);
setup.board.set_piece_at(sq, piece);
if let Ok(updated_pos) = setup
.position(pos.castles().mode())
.or_else(PositionError::ignore_invalid_castling_rights)
.or_else(PositionError::ignore_invalid_ep_square)
{
pos = updated_pos;
}
}
PositionMutation::ToggleCastles { rooks, mode } => {
let mut setup = pos.to_setup(EnPassantMode::PseudoLegal);
setup.castling_rights.toggle(rooks);
if let Ok(updated_pos) = setup
.position(mode)
.or_else(PositionError::ignore_invalid_castling_rights)
.or_else(PositionError::ignore_invalid_ep_square)
{
pos = updated_pos;
}
}
}
}
Ok(pos)
}
#[derive(Clone, Debug)]
pub struct Chess {
board: Board,
turn: Color,
castles: Castles,
ep_square: Option<EnPassant>,
halfmoves: u32,
fullmoves: NonZeroU32,
}
impl Chess {
#[cfg(feature = "variant")]
fn gives_check(&self, m: Move) -> bool {
let mut pos = self.clone();
pos.play_unchecked(m);
pos.is_check()
}
#[allow(clippy::type_complexity)]
fn from_setup_unchecked(
setup: Setup,
mode: CastlingMode,
) -> (
Chess,
Option<ByColor<ByRole<u8>>>,
Option<ByColor<RemainingChecks>>,
PositionErrorKinds,
) {
let mut errors = PositionErrorKinds::empty();
let castles = match Castles::from_setup(&setup, mode) {
Ok(castles) => castles,
Err(castles) => {
errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
castles
}
};
let ep_square = match EnPassant::from_setup(&setup) {
Ok(ep_square) => ep_square,
Err(()) => {
errors |= PositionErrorKinds::INVALID_EP_SQUARE;
None
}
};
let pos = Chess {
board: setup.board,
turn: setup.turn,
castles,
ep_square,
halfmoves: setup.halfmoves,
fullmoves: setup.fullmoves,
};
errors |= validate(&pos, ep_square);
(pos, setup.pockets, setup.remaining_checks, errors)
}
pub const fn new() -> Chess {
Chess {
board: Board::new(),
turn: White,
castles: Castles::new(),
ep_square: None,
halfmoves: 0,
fullmoves: NonZeroU32::MIN,
}
}
}
impl Default for Chess {
fn default() -> Chess {
Chess::new()
}
}
impl Hash for Chess {
fn hash<H: Hasher>(&self, state: &mut H) {
self.board.hash(state);
self.turn.hash(state);
self.castles.castling_rights().hash(state);
}
}
impl PartialEq for Chess {
fn eq(&self, other: &Chess) -> bool {
self.board == other.board
&& self.turn == other.turn
&& self.castles.castling_rights() == other.castles.castling_rights()
&& self.legal_ep_square() == other.legal_ep_square()
}
}
impl Eq for Chess {}
impl FromSetup for Chess {
fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Chess, PositionError<Chess>> {
let (pos, _, _, errors) = Chess::from_setup_unchecked(setup, mode);
PositionError { pos, errors }.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Chess {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for Chess {
fn board(&self) -> &Board {
&self.board
}
fn promoted(&self) -> Bitboard {
Bitboard::EMPTY
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
None
}
fn turn(&self) -> Color {
self.turn
}
fn castles(&self) -> &Castles {
&self.castles
}
fn maybe_ep_square(&self) -> Option<Square> {
self.ep_square.map(EnPassant::square)
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
None
}
fn halfmoves(&self) -> u32 {
self.halfmoves
}
fn fullmoves(&self) -> NonZeroU32 {
self.fullmoves
}
fn play_unchecked(&mut self, m: Move) {
do_move(
&mut self.board,
&mut Bitboard(0),
&mut self.turn,
&mut self.castles,
&mut self.ep_square,
&mut self.halfmoves,
&mut self.fullmoves,
m,
);
}
fn legal_moves(&self) -> MoveList {
let mut moves = MoveList::new();
let king = self
.board()
.king_of(self.turn())
.expect("king in standard chess");
let has_ep = gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
let checkers = self.checkers();
if checkers.is_empty() {
let target = !self.us();
gen_non_king(self, target, &mut moves);
gen_safe_king(self, king, target, &mut moves);
gen_castling_moves(
self,
&self.castles,
king,
CastlingSide::KingSide,
&mut moves,
);
gen_castling_moves(
self,
&self.castles,
king,
CastlingSide::QueenSide,
&mut moves,
);
} else {
evasions(self, king, checkers, &mut moves);
}
let blockers = slider_blockers(self.board(), self.them(), king);
if blockers.any() || has_ep {
moves.retain(|m| is_safe(self, king, *m, blockers));
}
moves
}
fn castling_moves(&self, side: CastlingSide) -> MoveList {
let mut moves = MoveList::new();
let king = self
.board()
.king_of(self.turn())
.expect("king in standard chess");
gen_castling_moves(self, &self.castles, king, side, &mut moves);
moves
}
fn en_passant_moves(&self) -> MoveList {
let mut moves = MoveList::new();
if gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves) {
let king = self
.board()
.king_of(self.turn())
.expect("king in standard chess");
let blockers = slider_blockers(self.board(), self.them(), king);
moves.retain(|m| is_safe(self, king, *m, blockers));
}
moves
}
fn promotion_moves(&self) -> MoveList {
let mut moves = MoveList::new();
let king = self
.board()
.king_of(self.turn())
.expect("king in standard chess");
let checkers = self.checkers();
if checkers.is_empty() {
gen_pawn_moves(self, Bitboard::BACKRANKS, &mut moves);
} else {
evasions(self, king, checkers, &mut moves);
moves.retain(|m| m.is_promotion());
}
let blockers = slider_blockers(self.board(), self.them(), king);
if blockers.any() {
moves.retain(|m| is_safe(self, king, *m, blockers));
}
moves
}
fn san_candidates(&self, role: Role, to: Square) -> MoveList {
let mut moves = MoveList::new();
let king = self
.board()
.king_of(self.turn())
.expect("king in standard chess");
let checkers = self.checkers();
if checkers.is_empty() {
let piece_from = match role {
Role::Pawn | Role::King => Bitboard(0),
Role::Knight => attacks::knight_attacks(to),
Role::Bishop => attacks::bishop_attacks(to, self.board().occupied()),
Role::Rook => attacks::rook_attacks(to, self.board().occupied()),
Role::Queen => attacks::queen_attacks(to, self.board().occupied()),
};
if !self.us().contains(to) {
match role {
Role::Pawn => gen_pawn_moves(self, Bitboard::from_square(to), &mut moves),
Role::King => gen_safe_king(self, king, Bitboard::from_square(to), &mut moves),
_ => {}
}
(piece_from & self.our(role)).for_each(|from| {
moves.push(Move::Normal {
role,
from,
capture: self.board().role_at(to),
to,
promotion: None,
});
});
}
} else {
evasions(self, king, checkers, &mut moves);
filter_san_candidates(role, to, &mut moves);
}
let has_ep = role == Role::Pawn
&& self.ep_square.map(Square::from) == Some(to)
&& gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
let blockers = slider_blockers(self.board(), self.them(), king);
if blockers.any() || has_ep {
moves.retain(|m| is_safe(self, king, *m, blockers));
}
moves
}
fn has_insufficient_material(&self, color: Color) -> bool {
if (self.board.by_color(color) & (self.board.pawns() | self.board.rooks_and_queens())).any()
{
return false;
}
if (self.board.by_color(color) & self.board.knights()).any() {
return self.board.by_color(color).count() <= 2
&& (self.board.by_color(!color) & !self.board.kings() & !self.board().queens())
.is_empty();
}
if (self.board.by_color(color) & self.board.bishops()).any() {
let same_color = (self.board().bishops() & Bitboard::DARK_SQUARES).is_empty()
|| (self.board().bishops() & Bitboard::LIGHT_SQUARES).is_empty();
return same_color
&& self.board().knights().is_empty()
&& self.board().pawns().is_empty();
}
true
}
fn is_variant_end(&self) -> bool {
false
}
fn variant_outcome(&self) -> Outcome {
Outcome::Unknown
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
current: V,
m: Move,
_mode: EnPassantMode,
) -> Option<V> {
do_update_zobrist_hash(current, m, self.turn, &self.castles, self.ep_square)
}
}
#[cfg(feature = "variant")]
pub(crate) mod variant {
use core::{cmp::min, ops::Not};
use super::*;
enum KingTag {}
impl Stepper for KingTag {
const ROLE: Role = Role::King;
fn attacks(from: Square) -> Bitboard {
attacks::king_attacks(from)
}
}
#[derive(Clone, Debug)]
pub struct Atomic {
board: Board,
turn: Color,
castles: Castles,
ep_square: Option<EnPassant>,
halfmoves: u32,
fullmoves: NonZeroU32,
}
impl Atomic {
pub const fn new() -> Atomic {
Atomic {
board: Board::new(),
turn: White,
castles: Castles::new(),
ep_square: None,
halfmoves: 0,
fullmoves: NonZeroU32::MIN,
}
}
}
impl Default for Atomic {
fn default() -> Atomic {
Atomic::new()
}
}
impl Hash for Atomic {
fn hash<H: Hasher>(&self, state: &mut H) {
self.board.hash(state);
self.turn.hash(state);
self.castles.castling_rights().hash(state);
}
}
impl PartialEq for Atomic {
fn eq(&self, other: &Self) -> bool {
self.board == other.board
&& self.turn == other.turn
&& self.castles.castling_rights() == other.castles.castling_rights()
&& self.legal_ep_square() == other.legal_ep_square()
}
}
impl Eq for Atomic {}
impl FromSetup for Atomic {
fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Atomic, PositionError<Atomic>> {
let mut errors = PositionErrorKinds::empty();
let castles = match Castles::from_setup(&setup, mode) {
Ok(castles) => castles,
Err(castles) => {
errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
castles
}
};
let ep_square = match EnPassant::from_setup(&setup) {
Ok(ep_square) => ep_square,
Err(()) => {
errors |= PositionErrorKinds::INVALID_EP_SQUARE;
None
}
};
let pos = Atomic {
board: setup.board,
turn: setup.turn,
castles,
ep_square,
halfmoves: setup.halfmoves,
fullmoves: setup.fullmoves,
};
errors |= validate(&pos, ep_square);
if ep_square.is_none() {
errors.remove(PositionErrorKinds::IMPOSSIBLE_CHECK);
}
if (pos.them() & pos.board().kings()).any() {
errors.remove(PositionErrorKinds::MISSING_KING);
}
PositionError { pos, errors }.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Atomic {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for Atomic {
fn board(&self) -> &Board {
&self.board
}
fn promoted(&self) -> Bitboard {
Bitboard::EMPTY
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
None
}
fn turn(&self) -> Color {
self.turn
}
fn castles(&self) -> &Castles {
&self.castles
}
fn maybe_ep_square(&self) -> Option<Square> {
self.ep_square.map(EnPassant::square)
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
None
}
fn halfmoves(&self) -> u32 {
self.halfmoves
}
fn fullmoves(&self) -> NonZeroU32 {
self.fullmoves
}
fn play_unchecked(&mut self, m: Move) {
do_move(
&mut self.board,
&mut Bitboard(0),
&mut self.turn,
&mut self.castles,
&mut self.ep_square,
&mut self.halfmoves,
&mut self.fullmoves,
m,
);
match m {
Move::Normal {
capture: Some(_),
to,
..
}
| Move::EnPassant { to, .. } => {
self.board.discard_piece_at(to);
let explosion_radius =
attacks::king_attacks(to) & self.board().occupied() & !self.board.pawns();
if (explosion_radius & self.board().kings() & self.us()).any() {
self.castles.discard_color(self.turn());
}
for explosion in explosion_radius {
self.board.discard_piece_at(explosion);
self.castles.discard_rook(explosion);
}
}
_ => (),
}
}
fn legal_moves(&self) -> MoveList {
let mut moves = MoveList::new();
gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
gen_non_king(self, !self.us(), &mut moves);
KingTag::gen_moves(self, !self.board().occupied(), &mut moves);
if let Some(king) = self.board().king_of(self.turn()) {
gen_castling_moves(
self,
&self.castles,
king,
CastlingSide::KingSide,
&mut moves,
);
gen_castling_moves(
self,
&self.castles,
king,
CastlingSide::QueenSide,
&mut moves,
);
}
moves.retain(|m| {
let mut after = self.clone();
after.play_unchecked(*m);
if let Some(our_king) = after.board().king_of(self.turn()) {
(after.board.kings() & after.board().by_color(!self.turn())).is_empty()
|| after
.king_attackers(our_king, !self.turn(), after.board.occupied())
.is_empty()
} else {
false
}
});
moves
}
fn king_attackers(&self, square: Square, attacker: Color, occupied: Bitboard) -> Bitboard {
let attacker_kings = self.board().kings() & self.board().by_color(attacker);
if attacker_kings.is_empty() || (attacks::king_attacks(square) & attacker_kings).any() {
Bitboard(0)
} else {
self.board().attacks_to(square, attacker, occupied)
}
}
fn is_variant_end(&self) -> bool {
self.variant_outcome().is_known()
}
fn has_insufficient_material(&self, color: Color) -> bool {
if (self.board.by_color(!color) & self.board.kings()).is_empty() {
return false;
}
if (self.board.by_color(color) & !self.board.kings()).is_empty() {
return true;
}
if (self.board.by_color(!color) & !self.board.kings()).any() {
if self.board().occupied() == self.board().kings() | self.board().bishops() {
if (self.board().bishops() & self.board().white() & Bitboard::DARK_SQUARES)
.is_empty()
{
return (self.board().bishops()
& self.board().black()
& Bitboard::LIGHT_SQUARES)
.is_empty();
}
if (self.board().bishops() & self.board().white() & Bitboard::LIGHT_SQUARES)
.is_empty()
{
return (self.board().bishops()
& self.board().black()
& Bitboard::DARK_SQUARES)
.is_empty();
}
}
return false;
}
if self.board().queens().any() || self.board.pawns().any() {
return false;
}
if (self.board().knights() | self.board().bishops() | self.board().rooks()).count() == 1
{
return true;
}
if self.board().occupied() == self.board().kings() | self.board().knights() {
return self.board().knights().count() <= 2;
}
false
}
fn variant_outcome(&self) -> Outcome {
for color in Color::ALL {
if (self.board().by_color(color) & self.board().kings()).is_empty() {
return Outcome::Known(KnownOutcome::Decisive { winner: !color });
}
}
Outcome::Unknown
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
mut current: V,
m: Move,
_mode: EnPassantMode,
) -> Option<V> {
if self.ep_square.is_some() {
return None;
}
match m {
Move::Normal {
role,
from,
capture: None, to,
promotion,
} if (role != Role::Pawn || Square::abs_diff(from, to) != 16)
&& role != Role::King
&& !self.castles.castling_rights().contains(from)
&& !self.castles.castling_rights().contains(to) =>
{
current ^= V::zobrist_for_white_turn();
current ^= V::zobrist_for_piece(from, role.of(self.turn));
current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(self.turn));
Some(current)
}
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Antichess {
board: Board,
turn: Color,
castles: Castles,
ep_square: Option<EnPassant>,
halfmoves: u32,
fullmoves: NonZeroU32,
}
impl Antichess {
pub const fn new() -> Antichess {
Antichess {
board: Board::new(),
turn: White,
castles: Castles::empty(CastlingMode::Standard),
ep_square: None,
halfmoves: 0,
fullmoves: NonZeroU32::MIN,
}
}
}
impl Default for Antichess {
fn default() -> Antichess {
Antichess::new()
}
}
impl Hash for Antichess {
fn hash<H: Hasher>(&self, state: &mut H) {
self.board.hash(state);
self.turn.hash(state);
self.castles.castling_rights().hash(state);
}
}
impl PartialEq for Antichess {
fn eq(&self, other: &Self) -> bool {
self.board == other.board
&& self.turn == other.turn
&& self.castles.castling_rights() == other.castles.castling_rights()
&& self.legal_ep_square() == other.legal_ep_square()
}
}
impl Eq for Antichess {}
impl FromSetup for Antichess {
fn from_setup(
setup: Setup,
mode: CastlingMode,
) -> Result<Antichess, PositionError<Antichess>> {
let mut errors = PositionErrorKinds::empty();
let ep_square = match EnPassant::from_setup(&setup) {
Ok(ep_square) => ep_square,
Err(()) => {
errors |= PositionErrorKinds::INVALID_EP_SQUARE;
None
}
};
let pos = Antichess {
board: setup.board,
turn: setup.turn,
castles: Castles::empty(mode),
ep_square,
halfmoves: setup.halfmoves,
fullmoves: setup.fullmoves,
};
if setup.castling_rights.any() {
errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
}
errors |= validate(&pos, ep_square)
- PositionErrorKinds::MISSING_KING
- PositionErrorKinds::TOO_MANY_KINGS
- PositionErrorKinds::OPPOSITE_CHECK
- PositionErrorKinds::IMPOSSIBLE_CHECK;
PositionError { pos, errors }.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Antichess {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for Antichess {
fn board(&self) -> &Board {
&self.board
}
fn promoted(&self) -> Bitboard {
Bitboard::EMPTY
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
None
}
fn turn(&self) -> Color {
self.turn
}
fn castles(&self) -> &Castles {
&self.castles
}
fn maybe_ep_square(&self) -> Option<Square> {
self.ep_square.map(EnPassant::square)
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
None
}
fn halfmoves(&self) -> u32 {
self.halfmoves
}
fn fullmoves(&self) -> NonZeroU32 {
self.fullmoves
}
fn play_unchecked(&mut self, m: Move) {
do_move(
&mut self.board,
&mut Bitboard(0),
&mut self.turn,
&mut self.castles,
&mut self.ep_square,
&mut self.halfmoves,
&mut self.fullmoves,
m,
);
}
fn en_passant_moves(&self) -> MoveList {
let mut moves = MoveList::new();
gen_en_passant(self.board(), self.turn, self.ep_square, &mut moves);
moves
}
fn capture_moves(&self) -> MoveList {
let mut moves = self.en_passant_moves();
let them = self.them();
gen_non_king(self, them, &mut moves);
add_king_promotions(&mut moves);
KingTag::gen_moves(self, them, &mut moves);
moves
}
fn legal_moves(&self) -> MoveList {
let mut moves = self.capture_moves();
if moves.is_empty() {
gen_non_king(self, !self.board().occupied(), &mut moves);
add_king_promotions(&mut moves);
KingTag::gen_moves(self, !self.board().occupied(), &mut moves);
}
moves
}
fn king_attackers(
&self,
_square: Square,
_attacker: Color,
_occupied: Bitboard,
) -> Bitboard {
Bitboard(0)
}
fn is_variant_end(&self) -> bool {
self.board().white().is_empty() || self.board().black().is_empty()
}
fn has_insufficient_material(&self, color: Color) -> bool {
if self.board.by_color(color).is_empty() {
false
} else if self.board.by_color(!color).is_empty() {
true
} else if self.board.occupied() == self.board.bishops() {
let we_some_on_light = (self.board.by_color(color) & Bitboard::LIGHT_SQUARES).any();
let we_some_on_dark = (self.board.by_color(color) & Bitboard::DARK_SQUARES).any();
let they_all_on_dark =
(self.board.by_color(!color) & Bitboard::LIGHT_SQUARES).is_empty();
let they_all_on_light =
(self.board.by_color(!color) & Bitboard::DARK_SQUARES).is_empty();
(we_some_on_light && they_all_on_dark) || (we_some_on_dark && they_all_on_light)
} else if self.board.occupied() == self.board.knights() {
match (
self.board.white().single_square(),
self.board.black().single_square(),
) {
(Some(white_single_knight), Some(black_single_knight)) => {
self.turn
== color
^ white_single_knight.is_light()
^ black_single_knight.is_dark()
}
_ => false,
}
} else {
false
}
}
fn variant_outcome(&self) -> Outcome {
if self.us().is_empty() || self.is_stalemate() {
Outcome::Known(KnownOutcome::Decisive {
winner: self.turn(),
})
} else {
Outcome::Unknown
}
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
current: V,
m: Move,
_mode: EnPassantMode,
) -> Option<V> {
do_update_zobrist_hash(current, m, self.turn, &self.castles, self.ep_square)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct KingOfTheHill {
chess: Chess,
}
impl KingOfTheHill {
pub const fn new() -> KingOfTheHill {
KingOfTheHill {
chess: Chess::new(),
}
}
}
impl FromSetup for KingOfTheHill {
fn from_setup(
setup: Setup,
mode: CastlingMode,
) -> Result<KingOfTheHill, PositionError<KingOfTheHill>> {
let (chess, _, _, errors) = Chess::from_setup_unchecked(setup, mode);
PositionError {
errors,
pos: KingOfTheHill { chess },
}
.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for KingOfTheHill {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for KingOfTheHill {
fn board(&self) -> &Board {
self.chess.board()
}
fn promoted(&self) -> Bitboard {
Bitboard::EMPTY
}
fn castles(&self) -> &Castles {
self.chess.castles()
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
None
}
fn turn(&self) -> Color {
self.chess.turn()
}
fn maybe_ep_square(&self) -> Option<Square> {
self.chess.maybe_ep_square()
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
None
}
fn halfmoves(&self) -> u32 {
self.chess.halfmoves()
}
fn fullmoves(&self) -> NonZeroU32 {
self.chess.fullmoves()
}
fn play_unchecked(&mut self, m: Move) {
self.chess.play_unchecked(m);
}
fn legal_moves(&self) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.legal_moves()
}
}
fn castling_moves(&self, side: CastlingSide) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.castling_moves(side)
}
}
fn en_passant_moves(&self) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.en_passant_moves()
}
}
fn san_candidates(&self, role: Role, to: Square) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.san_candidates(role, to)
}
}
fn has_insufficient_material(&self, _color: Color) -> bool {
false
}
fn is_variant_end(&self) -> bool {
(self.chess.board().kings() & Bitboard::CENTER).any()
}
fn variant_outcome(&self) -> Outcome {
for color in Color::ALL {
if (self.board().by_color(color) & self.board().kings() & Bitboard::CENTER).any() {
return Outcome::Known(KnownOutcome::Decisive { winner: color });
}
}
Outcome::Unknown
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
current: V,
m: Move,
mode: EnPassantMode,
) -> Option<V>
where
Self: Sized,
{
self.chess.update_zobrist_hash(current, m, mode)
}
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct ThreeCheck {
chess: Chess,
remaining_checks: ByColor<RemainingChecks>,
}
impl ThreeCheck {
pub const fn new() -> ThreeCheck {
ThreeCheck {
chess: Chess::new(),
remaining_checks: ByColor {
black: RemainingChecks::new(3),
white: RemainingChecks::new(3),
},
}
}
}
impl FromSetup for ThreeCheck {
fn from_setup(
setup: Setup,
mode: CastlingMode,
) -> Result<ThreeCheck, PositionError<ThreeCheck>> {
let (chess, _, remaining_checks, mut errors) = Chess::from_setup_unchecked(setup, mode);
let remaining_checks = remaining_checks.unwrap_or_default();
if remaining_checks.iter().all(|remaining| remaining.is_zero()) {
errors |= PositionErrorKinds::VARIANT;
}
PositionError {
errors,
pos: ThreeCheck {
chess,
remaining_checks,
},
}
.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for ThreeCheck {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for ThreeCheck {
fn board(&self) -> &Board {
self.chess.board()
}
fn promoted(&self) -> Bitboard {
Bitboard::EMPTY
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
None
}
fn turn(&self) -> Color {
self.chess.turn()
}
fn castles(&self) -> &Castles {
self.chess.castles()
}
fn maybe_ep_square(&self) -> Option<Square> {
self.chess.maybe_ep_square()
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
Some(&self.remaining_checks)
}
fn halfmoves(&self) -> u32 {
self.chess.halfmoves()
}
fn fullmoves(&self) -> NonZeroU32 {
self.chess.fullmoves
}
fn play_unchecked(&mut self, m: Move) {
let turn = self.chess.turn();
self.chess.play_unchecked(m);
if self.is_check() {
let checks = self.remaining_checks.get_mut(turn);
*checks = checks.saturating_sub(1);
}
}
fn legal_moves(&self) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.legal_moves()
}
}
fn castling_moves(&self, side: CastlingSide) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.castling_moves(side)
}
}
fn en_passant_moves(&self) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.en_passant_moves()
}
}
fn san_candidates(&self, role: Role, to: Square) -> MoveList {
if self.is_variant_end() {
MoveList::new()
} else {
self.chess.san_candidates(role, to)
}
}
fn has_insufficient_material(&self, color: Color) -> bool {
(self.board().by_color(color) & !self.board().kings()).is_empty()
}
fn is_irreversible(&self, m: Move) -> bool {
self.chess.is_irreversible(m) || self.chess.gives_check(m)
}
fn is_variant_end(&self) -> bool {
self.remaining_checks
.iter()
.any(|remaining| remaining.is_zero())
}
fn variant_outcome(&self) -> Outcome {
self.remaining_checks
.find(|remaining| remaining.is_zero())
.map_or(Outcome::Unknown, |winner| {
Outcome::Known(KnownOutcome::Decisive { winner })
})
}
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct Crazyhouse {
chess: Chess,
promoted: Bitboard,
pockets: ByColor<ByRole<u8>>,
}
impl Crazyhouse {
pub const fn new() -> Crazyhouse {
Crazyhouse {
chess: Chess::new(),
promoted: Bitboard::EMPTY,
pockets: ByColor {
black: ByRole {
pawn: 0,
knight: 0,
bishop: 0,
rook: 0,
queen: 0,
king: 0,
},
white: ByRole {
pawn: 0,
knight: 0,
bishop: 0,
rook: 0,
queen: 0,
king: 0,
},
},
}
}
fn our_pocket(&self) -> &ByRole<u8> {
self.pockets.get(self.turn())
}
fn our_pocket_mut(&mut self) -> &mut ByRole<u8> {
let turn = self.turn();
self.pockets.get_mut(turn)
}
fn legal_put_squares(&self) -> Bitboard {
let checkers = self.checkers();
if checkers.is_empty() {
!self.board().occupied()
} else if let Some(checker) = checkers.single_square() {
let king = self
.board()
.king_of(self.turn())
.expect("king in crazyhouse");
attacks::between(checker, king)
} else {
Bitboard(0)
}
}
}
impl FromSetup for Crazyhouse {
fn from_setup(
setup: Setup,
mode: CastlingMode,
) -> Result<Crazyhouse, PositionError<Crazyhouse>> {
let promoted = setup.promoted
& setup.board.occupied()
& !setup.board.pawns()
& !setup.board.kings();
let (chess, pockets, _, mut errors) = Chess::from_setup_unchecked(setup, mode);
let pockets = pockets.unwrap_or_default();
if pockets.white.king > 0 || pockets.black.king > 0 {
errors |= PositionErrorKinds::TOO_MANY_KINGS;
}
if pockets.count() + chess.board().occupied().count() > 64 {
errors |= PositionErrorKinds::VARIANT;
}
errors -= PositionErrorKinds::TOO_MUCH_MATERIAL;
if promoted.count()
+ chess.board().pawns().count()
+ usize::from(pockets.white.pawn)
+ usize::from(pockets.black.pawn)
> 16
|| (chess.board().knights() & !promoted).count()
+ usize::from(pockets.white.knight)
+ usize::from(pockets.black.knight)
> 4
|| (chess.board().bishops() & !promoted).count()
+ usize::from(pockets.white.bishop)
+ usize::from(pockets.black.bishop)
> 4
|| (chess.board().rooks() & !promoted).count()
+ usize::from(pockets.white.rook)
+ usize::from(pockets.black.rook)
> 4
|| (chess.board().queens() & !promoted).count()
+ usize::from(pockets.white.queen)
+ usize::from(pockets.black.queen)
> 2
{
errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
}
PositionError {
errors,
pos: Crazyhouse {
chess,
promoted,
pockets,
},
}
.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Crazyhouse {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for Crazyhouse {
fn board(&self) -> &Board {
self.chess.board()
}
fn promoted(&self) -> Bitboard {
self.promoted
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
Some(&self.pockets)
}
fn turn(&self) -> Color {
self.chess.turn()
}
fn castles(&self) -> &Castles {
self.chess.castles()
}
fn maybe_ep_square(&self) -> Option<Square> {
self.chess.maybe_ep_square()
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
None
}
fn halfmoves(&self) -> u32 {
self.chess.halfmoves()
}
fn fullmoves(&self) -> NonZeroU32 {
self.chess.fullmoves()
}
fn play_unchecked(&mut self, m: Move) {
match m {
Move::Normal {
capture: Some(capture),
to,
..
} => {
let capture = if self.promoted.contains(to) {
Role::Pawn
} else {
capture
};
*self.our_pocket_mut().get_mut(capture) += 1;
}
Move::EnPassant { .. } => {
self.our_pocket_mut().pawn += 1;
}
Move::Put { role, .. } => {
*self.our_pocket_mut().get_mut(role) -= 1;
}
_ => {}
}
do_move(
&mut self.chess.board,
&mut self.promoted,
&mut self.chess.turn,
&mut self.chess.castles,
&mut self.chess.ep_square,
&mut self.chess.halfmoves,
&mut self.chess.fullmoves,
m,
);
}
fn legal_moves(&self) -> MoveList {
let mut moves = self.chess.legal_moves();
let pocket = self.our_pocket();
let targets = self.legal_put_squares();
for to in targets {
for role in [Role::Knight, Role::Bishop, Role::Rook, Role::Queen] {
if *pocket.get(role) > 0 {
moves.push(Move::Put { role, to });
}
}
}
if pocket.pawn > 0 {
for to in targets & !Bitboard::BACKRANKS {
moves.push(Move::Put {
role: Role::Pawn,
to,
});
}
}
moves
}
fn castling_moves(&self, side: CastlingSide) -> MoveList {
self.chess.castling_moves(side)
}
fn en_passant_moves(&self) -> MoveList {
self.chess.en_passant_moves()
}
fn san_candidates(&self, role: Role, to: Square) -> MoveList {
let mut moves = self.chess.san_candidates(role, to);
if *self.our_pocket().get(role) > 0
&& self.legal_put_squares().contains(to)
&& (role != Role::Pawn || !Bitboard::BACKRANKS.contains(to))
{
moves.push(Move::Put { role, to });
}
moves
}
fn is_irreversible(&self, m: Move) -> bool {
match m {
Move::Castle { .. } => true,
Move::Normal { role, from, to, .. } => {
self.chess.castles.castling_rights().contains(from)
|| self.chess.castles.castling_rights().contains(to)
|| (role == Role::King && self.chess.castles.has_color(self.turn()))
}
_ => false,
}
}
fn has_insufficient_material(&self, _color: Color) -> bool {
self.board().occupied().count() + self.pockets.count() <= 3
&& self.promoted.is_empty()
&& self.board().pawns().is_empty()
&& self.board().rooks_and_queens().is_empty()
&& self.pockets.white.pawn == 0
&& self.pockets.black.pawn == 0
&& self.pockets.white.rook == 0
&& self.pockets.black.rook == 0
&& self.pockets.white.queen == 0
&& self.pockets.black.queen == 0
}
fn is_variant_end(&self) -> bool {
false
}
fn variant_outcome(&self) -> Outcome {
Outcome::Unknown
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
mut current: V,
m: Move,
_mode: EnPassantMode,
) -> Option<V> {
if self.chess.ep_square.is_some() {
return None;
}
match m {
Move::Normal {
role,
from,
capture,
to,
promotion,
} if (role != Role::Pawn || Square::abs_diff(from, to) != 16)
&& role != Role::King
&& !self.castles().castling_rights().contains(from)
&& !self.castles().castling_rights().contains(to) =>
{
current ^= V::zobrist_for_white_turn();
current ^= V::zobrist_for_piece(from, role.of(self.turn()));
current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(self.turn()));
if let Some(capture) = capture {
current ^= V::zobrist_for_piece(to, capture.of(!self.turn()));
let capture = if self.promoted.contains(to) {
current ^= V::zobrist_for_promoted(to);
Role::Pawn
} else {
capture
};
let pocket_pieces = self.pockets[self.turn()][capture];
current ^= V::zobrist_for_pocket(self.turn(), capture, pocket_pieces);
current ^= V::zobrist_for_pocket(self.turn(), capture, pocket_pieces + 1);
}
if self.promoted.contains(from) {
current ^= V::zobrist_for_promoted(from);
}
if self.promoted.contains(from) || promotion.is_some() {
current ^= V::zobrist_for_promoted(to);
}
Some(current)
}
Move::Put { role, to } => {
current ^= V::zobrist_for_white_turn();
current ^= V::zobrist_for_piece(to, role.of(self.turn()));
let pocket_pieces = self.pockets[self.turn()][role];
current ^= V::zobrist_for_pocket(self.turn(), role, pocket_pieces);
current ^= V::zobrist_for_pocket(self.turn(), role, pocket_pieces - 1);
Some(current)
}
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct RacingKings {
board: Board,
turn: Color,
castles: Castles,
halfmoves: u32,
fullmoves: NonZeroU32,
}
impl RacingKings {
pub const fn new() -> RacingKings {
RacingKings {
board: Board::racing_kings(),
turn: White,
castles: Castles::empty(CastlingMode::Standard),
halfmoves: 0,
fullmoves: NonZeroU32::MIN,
}
}
}
impl Default for RacingKings {
fn default() -> RacingKings {
RacingKings::new()
}
}
impl Hash for RacingKings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.board.hash(state);
self.turn.hash(state);
self.castles.castling_rights().hash(state);
}
}
impl PartialEq for RacingKings {
fn eq(&self, other: &Self) -> bool {
self.board == other.board
&& self.turn == other.turn
&& self.castles.castling_rights() == other.castles.castling_rights()
}
}
impl Eq for RacingKings {}
impl FromSetup for RacingKings {
fn from_setup(
setup: Setup,
mode: CastlingMode,
) -> Result<RacingKings, PositionError<RacingKings>> {
let mut errors = PositionErrorKinds::empty();
if setup.castling_rights.any() {
errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
}
if setup.board.pawns().any() {
errors |= PositionErrorKinds::VARIANT;
}
for color in Color::ALL {
let us = setup.board.by_color(color);
if (setup.board.knights() & us).count() > 2
|| (setup.board.bishops() & us).count() > 2
|| (setup.board.rooks() & us).count() > 2
|| (setup.board.queens() & us).more_than_one()
{
errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
}
}
if setup.ep_square.is_some() {
errors |= PositionErrorKinds::INVALID_EP_SQUARE;
}
let pos = RacingKings {
board: setup.board,
turn: setup.turn,
castles: Castles::empty(mode),
halfmoves: setup.halfmoves,
fullmoves: setup.fullmoves,
};
if pos.is_check() {
errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
}
if pos.turn().is_black()
&& (pos.board().white() & pos.board().kings() & Rank::Eighth).any()
&& (pos.board().black() & pos.board().kings() & Rank::Eighth).any()
{
errors |= PositionErrorKinds::VARIANT;
}
errors |= validate(&pos, None);
PositionError { pos, errors }.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for RacingKings {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for RacingKings {
fn board(&self) -> &Board {
&self.board
}
fn promoted(&self) -> Bitboard {
Bitboard::EMPTY
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
None
}
fn turn(&self) -> Color {
self.turn
}
fn castles(&self) -> &Castles {
&self.castles
}
fn maybe_ep_square(&self) -> Option<Square> {
None
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
None
}
fn halfmoves(&self) -> u32 {
self.halfmoves
}
fn fullmoves(&self) -> NonZeroU32 {
self.fullmoves
}
fn play_unchecked(&mut self, m: Move) {
do_move(
&mut self.board,
&mut Bitboard(0),
&mut self.turn,
&mut self.castles,
&mut None,
&mut self.halfmoves,
&mut self.fullmoves,
m,
);
}
fn legal_moves(&self) -> MoveList {
let mut moves = MoveList::new();
if self.is_variant_end() {
return moves;
}
let target = !self.us();
gen_non_king(self, target, &mut moves);
let king = self
.board()
.king_of(self.turn())
.expect("king in racingkings");
gen_safe_king(self, king, target, &mut moves);
let blockers = slider_blockers(self.board(), self.them(), king);
if blockers.any() {
moves.retain(|m| is_safe(self, king, *m, blockers));
}
moves.retain(|m| {
let mut after = self.clone();
after.play_unchecked(*m);
!after.is_check()
});
moves
}
fn has_insufficient_material(&self, _color: Color) -> bool {
false
}
fn is_variant_end(&self) -> bool {
let in_goal = self.board().kings() & Rank::Eighth;
if in_goal.is_empty() {
return false;
}
if self.turn().is_white() || (in_goal & self.board().black()).any() {
return true;
}
let black_king = self.board().king_of(Black).expect("king in racingkings");
for target in attacks::king_attacks(black_king) & Rank::Eighth & !self.board().black() {
if self
.king_attackers(target, White, self.board().occupied())
.is_empty()
{
return false;
}
}
true
}
fn variant_outcome(&self) -> Outcome {
if self.is_variant_end() {
let in_goal = self.board().kings() & Rank::Eighth;
if (in_goal & self.board().white()).any() && (in_goal & self.board().black()).any()
{
Outcome::Known(KnownOutcome::Draw)
} else if (in_goal & self.board().white()).any() {
Outcome::Known(KnownOutcome::Decisive { winner: White })
} else {
Outcome::Known(KnownOutcome::Decisive { winner: Black })
}
} else {
Outcome::Unknown
}
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
mut current: V,
m: Move,
_mode: EnPassantMode,
) -> Option<V> {
match m {
Move::Normal {
role,
from,
capture,
to,
promotion,
} => {
current ^= V::zobrist_for_white_turn();
current ^= V::zobrist_for_piece(from, role.of(self.turn));
current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(self.turn));
if let Some(capture) = capture {
current ^= V::zobrist_for_piece(to, capture.of(!self.turn));
}
Some(current)
}
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Horde {
board: Board,
turn: Color,
castles: Castles,
ep_square: Option<EnPassant>,
halfmoves: u32,
fullmoves: NonZeroU32,
}
impl Default for Horde {
fn default() -> Horde {
let mut castles = Castles::default();
castles.discard_color(White);
Horde {
board: Board::horde(),
turn: White,
castles,
ep_square: None,
halfmoves: 0,
fullmoves: NonZeroU32::MIN,
}
}
}
impl Hash for Horde {
fn hash<H: Hasher>(&self, state: &mut H) {
self.board.hash(state);
self.turn.hash(state);
self.castles.castling_rights().hash(state);
}
}
impl PartialEq for Horde {
fn eq(&self, other: &Self) -> bool {
self.board == other.board
&& self.turn == other.turn
&& self.castles.castling_rights() == other.castles.castling_rights()
&& self.legal_ep_square() == other.legal_ep_square()
}
}
impl Eq for Horde {}
impl FromSetup for Horde {
fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Horde, PositionError<Horde>> {
let mut errors = PositionErrorKinds::empty();
let castles = match Castles::from_setup(&setup, mode) {
Ok(castles) => castles,
Err(castles) => {
errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
castles
}
};
let ep_square = match EnPassant::from_setup(&setup) {
Ok(ep_square) => ep_square,
Err(()) => {
errors |= PositionErrorKinds::INVALID_EP_SQUARE;
None
}
};
let pos = Horde {
board: setup.board,
turn: setup.turn,
castles,
ep_square,
halfmoves: setup.halfmoves,
fullmoves: setup.fullmoves,
};
errors |= validate(&pos, ep_square)
- PositionErrorKinds::PAWNS_ON_BACKRANK
- PositionErrorKinds::MISSING_KING
- PositionErrorKinds::TOO_MUCH_MATERIAL;
if pos.board().kings().is_empty() {
errors |= PositionErrorKinds::MISSING_KING;
} else if pos.board().kings().more_than_one() {
errors |= PositionErrorKinds::TOO_MANY_KINGS;
}
for color in Color::ALL {
let us = pos.board.by_color(color);
if (pos.board().kings() & us).any() {
if !is_standard_material(pos.board(), color) {
errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
}
if (pos.board().pawns() & us & Bitboard::BACKRANKS).any() {
errors |= PositionErrorKinds::PAWNS_ON_BACKRANK;
}
} else {
if us.count() > 36 {
errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
}
if (pos.board().pawns() & us & (!color).backrank()).any() {
errors |= PositionErrorKinds::PAWNS_ON_BACKRANK;
}
}
}
PositionError { pos, errors }.strict()
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Horde {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_iter()?)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
arbitrary_position(u.arbitrary_take_rest_iter()?)
}
}
impl Position for Horde {
fn board(&self) -> &Board {
&self.board
}
fn promoted(&self) -> Bitboard {
Bitboard::EMPTY
}
fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
None
}
fn turn(&self) -> Color {
self.turn
}
fn castles(&self) -> &Castles {
&self.castles
}
fn maybe_ep_square(&self) -> Option<Square> {
self.ep_square.map(EnPassant::square)
}
fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
None
}
fn halfmoves(&self) -> u32 {
self.halfmoves
}
fn fullmoves(&self) -> NonZeroU32 {
self.fullmoves
}
fn play_unchecked(&mut self, m: Move) {
do_move(
&mut self.board,
&mut Bitboard(0),
&mut self.turn,
&mut self.castles,
&mut self.ep_square,
&mut self.halfmoves,
&mut self.fullmoves,
m,
);
}
fn legal_moves(&self) -> MoveList {
let mut moves = MoveList::new();
let king = self.board().king_of(self.turn());
let has_ep = gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
let checkers = self.checkers();
if checkers.is_empty() {
let target = !self.us();
gen_non_king(self, target, &mut moves);
if let Some(king) = king {
gen_safe_king(self, king, target, &mut moves);
gen_castling_moves(
self,
&self.castles,
king,
CastlingSide::KingSide,
&mut moves,
);
gen_castling_moves(
self,
&self.castles,
king,
CastlingSide::QueenSide,
&mut moves,
);
}
} else {
evasions(self, king.expect("king in check"), checkers, &mut moves);
}
if let Some(king) = king {
let blockers = slider_blockers(self.board(), self.them(), king);
if blockers.any() || has_ep {
moves.retain(|m| is_safe(self, king, *m, blockers));
}
}
moves
}
fn is_variant_end(&self) -> bool {
self.board().white().is_empty() || self.board().black().is_empty()
}
#[allow(clippy::nonminimal_bool)] fn has_insufficient_material(&self, color: Color) -> bool {
#[derive(Copy, Clone)]
enum SquareColor {
Dark,
Light,
}
impl From<SquareColor> for Bitboard {
fn from(square_color: SquareColor) -> Bitboard {
match square_color {
SquareColor::Light => Bitboard::LIGHT_SQUARES,
SquareColor::Dark => Bitboard::DARK_SQUARES,
}
}
}
impl Not for SquareColor {
type Output = SquareColor;
fn not(self) -> SquareColor {
match self {
SquareColor::Dark => SquareColor::Light,
SquareColor::Light => SquareColor::Dark,
}
}
}
if (self.board.by_color(color) & self.board.kings()).any() {
return false;
}
let has_bishop_pair = |side: Color| -> bool {
let bishops = self.board.bishops() & self.board.by_color(side);
(bishops & Bitboard::DARK_SQUARES).any()
&& (bishops & Bitboard::LIGHT_SQUARES).any()
};
let horde = self.board.material_side(color);
let horde_bishops = |square_color: SquareColor| -> u8 {
(Bitboard::from(square_color) & self.board.by_color(color) & self.board.bishops())
.count() as u8
};
let horde_bishop_color = if horde_bishops(SquareColor::Light) >= 1 {
SquareColor::Light
} else {
SquareColor::Dark
};
let horde_num = horde.pawn
+ horde.knight
+ horde.rook
+ horde.queen
+ min(horde_bishops(SquareColor::Dark), 2)
+ min(horde_bishops(SquareColor::Light), 2);
let pieces = self.board.material_side(!color);
let pieces_bishops = |square_color: SquareColor| -> u8 {
(Bitboard::from(square_color) & self.board.by_color(!color) & self.board.bishops())
.count() as u8
};
let pieces_num = pieces.count() as u8;
let pieces_of_type_not = |piece: u8| -> u8 { pieces_num - piece };
if horde_num == 0 {
return true;
}
if horde_num >= 4 {
return false;
}
if (horde.pawn >= 1 || horde.queen >= 1) && horde_num >= 2 {
return false;
}
if horde.rook >= 1 && horde_num >= 2 {
if !(horde_num == 2
&& horde.rook == 1
&& horde.bishop == 1
&& pieces_of_type_not(pieces_bishops(horde_bishop_color)) == 1)
{
return false;
}
}
if horde_num == 1 {
if pieces_num == 1 {
return true;
} else if horde.queen == 1 {
return !(pieces.pawn >= 1
|| pieces.rook >= 1
|| pieces_bishops(SquareColor::Light) >= 2
|| pieces_bishops(SquareColor::Dark) >= 2);
} else if horde.pawn == 1 {
let pawn_square = (self.board.pawns() & self.board.by_color(color))
.single_square()
.unwrap();
let mut promote_to_queen = self.clone();
promote_to_queen
.board
.set_piece_at(pawn_square, color.queen());
let mut promote_to_knight = self.clone();
promote_to_knight
.board
.set_piece_at(pawn_square, color.knight());
return promote_to_queen.has_insufficient_material(color)
&& promote_to_knight.has_insufficient_material(color);
} else if horde.rook == 1 {
return !(pieces.pawn >= 2
|| (pieces.rook >= 1 && pieces.pawn >= 1)
|| (pieces.rook >= 1 && pieces.knight >= 1)
|| (pieces.pawn >= 1 && pieces.knight >= 1));
} else if horde.bishop == 1 {
return !(
pieces_bishops(!horde_bishop_color) >= 2
|| (pieces_bishops(!horde_bishop_color) >= 1 && pieces.pawn >= 1)
|| pieces.pawn >= 2
);
} else if horde.knight == 1 {
return !(
pieces_num >= 4
&& (pieces.knight >= 2
|| pieces.pawn >= 2
|| (pieces.rook >= 1 && pieces.knight >= 1)
|| (pieces.rook >= 1 && pieces.bishop >= 1)
|| (pieces.knight >= 1 && pieces.bishop >= 1)
|| (pieces.rook >= 1 && pieces.pawn >= 1)
|| (pieces.knight >= 1 && pieces.pawn >= 1)
|| (pieces.bishop >= 1 && pieces.pawn >= 1)
|| (has_bishop_pair(!color) && pieces.pawn >= 1))
&& (pieces_bishops(SquareColor::Dark) < 2
|| pieces_of_type_not(pieces_bishops(SquareColor::Dark)) >= 3)
&& (pieces_bishops(SquareColor::Light) < 2
|| pieces_of_type_not(pieces_bishops(SquareColor::Light)) >= 3)
);
}
} else if horde_num == 2 {
if pieces_num == 1 {
return true;
} else if horde.knight == 2 {
return pieces.pawn + pieces.bishop + pieces.knight < 1;
} else if has_bishop_pair(color) {
return !(
pieces.pawn >= 1 || pieces.bishop >= 1 ||
( pieces.knight >= 1 && pieces.rook + pieces.queen >= 1 )
);
} else if horde.bishop >= 1 && horde.knight >= 1 {
return !(
pieces.pawn >= 1 || pieces_bishops(!horde_bishop_color) >= 1 ||
pieces_of_type_not( pieces_bishops(horde_bishop_color) ) >=3
);
} else {
return !(
(pieces.pawn >= 1 && pieces_bishops(!horde_bishop_color) >= 1)
|| (pieces.pawn >= 1 && pieces.knight >= 1)
|| (pieces_bishops(!horde_bishop_color) >= 1 && pieces.knight >= 1)
|| (pieces_bishops(!horde_bishop_color) >= 2)
|| pieces.knight >= 2
|| pieces.pawn >= 2
);
}
} else if horde_num == 3 {
if (horde.knight == 2 && horde.bishop == 1)
|| horde.knight == 3
|| has_bishop_pair(color)
{
return false;
} else {
return pieces_num == 1;
}
}
true
}
fn variant_outcome(&self) -> Outcome {
if self.board().occupied().is_empty() {
Outcome::Known(KnownOutcome::Draw)
} else if self.board().white().is_empty() {
Outcome::Known(KnownOutcome::Decisive { winner: Black })
} else if self.board().black().is_empty() {
Outcome::Known(KnownOutcome::Decisive { winner: White })
} else {
Outcome::Unknown
}
}
fn update_zobrist_hash<V: ZobristValue>(
&self,
current: V,
m: Move,
_mode: EnPassantMode,
) -> Option<V> {
do_update_zobrist_hash(current, m, self.turn, &self.castles, self.ep_square)
}
}
fn add_king_promotions(moves: &mut MoveList) {
let mut king_promotions = MoveList::new();
for m in &moves[..] {
if let Move::Normal {
role,
from,
capture,
to,
promotion: Some(Role::Queen),
} = *m
{
king_promotions.push(Move::Normal {
role,
from,
capture,
to,
promotion: Some(Role::King),
});
}
}
moves.extend(king_promotions);
}
}
#[allow(clippy::too_many_arguments)] fn do_move(
board: &mut Board,
promoted: &mut Bitboard,
turn: &mut Color,
castles: &mut Castles,
ep_square: &mut Option<EnPassant>,
halfmoves: &mut u32,
fullmoves: &mut NonZeroU32,
m: Move,
) {
let color = *turn;
ep_square.take();
*halfmoves = if m.is_zeroing() {
0
} else {
halfmoves.saturating_add(1)
};
match m {
Move::Normal {
role,
from,
capture,
to,
promotion,
} => {
if role == Role::Pawn && to - from == 16 && from.rank() == Rank::Second {
*ep_square = from.offset(8).map(EnPassant);
} else if role == Role::Pawn && from - to == 16 && from.rank() == Rank::Seventh {
*ep_square = from.offset(-8).map(EnPassant);
}
if role == Role::King {
castles.discard_color(color);
} else if role == Role::Rook {
castles.discard_rook(from);
}
if capture == Some(Role::Rook) {
castles.discard_rook(to);
}
board.discard_piece_at(from);
board.set_piece_at(to, promotion.map_or(role.of(color), |p| p.of(color)));
let is_promoted = promoted.remove(from) || promotion.is_some();
promoted.set(to, is_promoted);
}
Move::Castle { king, rook } => {
let side = CastlingSide::from_queen_side(rook < king);
board.discard_piece_at(king);
board.discard_piece_at(rook);
board.set_new_piece_at(
Square::from_coords(side.rook_to_file(), rook.rank()),
color.rook(),
);
board.set_new_piece_at(
Square::from_coords(side.king_to_file(), king.rank()),
color.king(),
);
castles.discard_color(color);
}
Move::EnPassant { from, to } => {
board.discard_piece_at(Square::from_coords(to.file(), from.rank())); board.discard_piece_at(from);
board.set_new_piece_at(to, color.pawn());
}
Move::Put { role, to } => {
board.set_new_piece_at(to, Piece { color, role });
}
}
if color.is_black() {
*fullmoves = NonZeroU32::new(fullmoves.get().saturating_add(1)).unwrap();
}
*turn = !color;
}
fn validate<P: Position>(pos: &P, ep_square: Option<EnPassant>) -> PositionErrorKinds {
let mut errors = PositionErrorKinds::empty();
if pos.board().occupied().is_empty() {
errors |= PositionErrorKinds::EMPTY_BOARD;
}
if (pos.board().pawns() & Bitboard::BACKRANKS).any() {
errors |= PositionErrorKinds::PAWNS_ON_BACKRANK;
}
for color in Color::ALL {
let kings = pos.board().kings() & pos.board().by_color(color);
if kings.is_empty() {
errors |= PositionErrorKinds::MISSING_KING;
} else if kings.more_than_one() {
errors |= PositionErrorKinds::TOO_MANY_KINGS;
}
if !is_standard_material(pos.board(), color) {
errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
}
}
if let Some(their_king) = pos.board().king_of(!pos.turn())
&& pos
.king_attackers(their_king, pos.turn(), pos.board().occupied())
.any()
{
errors |= PositionErrorKinds::OPPOSITE_CHECK;
}
let checkers = pos.checkers();
if let (Some(a), Some(b), Some(our_king)) = (
checkers.first(),
checkers.last(),
pos.board().king_of(pos.turn()),
) {
if let Some(ep_square) = ep_square {
if a != b
|| (a != ep_square.pawn_pushed_to()
&& pos
.king_attackers(
our_king,
!pos.turn(),
pos.board()
.occupied()
.without(ep_square.pawn_pushed_to())
.with(ep_square.pawn_pushed_from()),
)
.any())
{
errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
}
} else {
if a != b && (checkers.count() > 2 || attacks::aligned(a, our_king, b)) {
errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
}
}
}
if (checkers & pos.board().steppers()).more_than_one() {
errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
}
errors
}
fn do_update_zobrist_hash<V: ZobristValue>(
mut current: V,
m: Move,
turn: Color,
castles: &Castles,
ep_square: Option<EnPassant>,
) -> Option<V> {
if ep_square.is_some() {
return None;
}
match m {
Move::Normal {
role,
from,
capture,
to,
promotion,
} if (role != Role::Pawn || Square::abs_diff(from, to) != 16)
&& role != Role::King
&& !castles.castling_rights().contains(from)
&& !castles.castling_rights().contains(to) =>
{
current ^= V::zobrist_for_white_turn();
current ^= V::zobrist_for_piece(from, role.of(turn));
current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(turn));
if let Some(capture) = capture {
current ^= V::zobrist_for_piece(to, capture.of(!turn));
}
Some(current)
}
_ => None,
}
}
const fn is_standard_material(board: &Board, color: Color) -> bool {
let our = board.by_color(color);
let promoted_pieces = board
.queens()
.intersect_const(our)
.count()
.saturating_sub(1)
+ board.rooks().intersect_const(our).count().saturating_sub(2)
+ board
.knights()
.intersect_const(our)
.count()
.saturating_sub(2)
+ board
.bishops()
.intersect_const(our)
.intersect_const(Bitboard::LIGHT_SQUARES)
.count()
.saturating_sub(1)
+ board
.bishops()
.intersect_const(our)
.intersect_const(Bitboard::DARK_SQUARES)
.count()
.saturating_sub(1);
board.pawns().intersect_const(our).count() + promoted_pieces <= 8
}
fn gen_non_king<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
gen_pawn_moves(pos, target, moves);
KnightTag::gen_moves(pos, target, moves);
BishopTag::gen_moves(pos, target, moves);
RookTag::gen_moves(pos, target, moves);
QueenTag::gen_moves(pos, target, moves);
}
fn gen_safe_king<P: Position>(pos: &P, king: Square, target: Bitboard, moves: &mut MoveList) {
(attacks::king_attacks(king) & target).for_each(|to| {
if pos
.board()
.attacks_to(to, !pos.turn(), pos.board().occupied())
.is_empty()
{
moves.push(Move::Normal {
role: Role::King,
from: king,
capture: pos.board().role_at(to),
to,
promotion: None,
});
}
});
}
fn evasions<P: Position>(pos: &P, king: Square, checkers: Bitboard, moves: &mut MoveList) {
let sliders = checkers & pos.board().sliders();
let mut attacked = Bitboard(0);
sliders.for_each(|checker| {
attacked |= attacks::ray(checker, king) ^ checker;
});
gen_safe_king(pos, king, !pos.us() & !attacked, moves);
if let Some(checker) = checkers.single_square() {
let target = attacks::between(king, checker).with(checker);
gen_non_king(pos, target, moves);
}
}
fn gen_castling_moves<P: Position>(
pos: &P,
castles: &Castles,
king: Square,
side: CastlingSide,
moves: &mut MoveList,
) {
if let Some(rook) = castles.rook(pos.turn(), side) {
let path = castles.path(pos.turn(), side);
if (path & pos.board().occupied()).any() {
return;
}
let king_to = side.king_to(pos.turn());
if pos
.king_attackers(
king_to,
!pos.turn(),
pos.board().occupied() ^ king ^ rook ^ side.rook_to(pos.turn()),
)
.any()
{
return;
}
let king_path = attacks::between(king, king_to).with(king);
for sq in king_path {
if pos
.king_attackers(sq, !pos.turn(), pos.board().occupied() ^ king)
.any()
{
return;
}
}
moves.push(Move::Castle { king, rook });
}
}
trait Stepper {
const ROLE: Role;
fn attacks(from: Square) -> Bitboard;
fn gen_moves<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
pos.our(Self::ROLE).for_each(|from| {
(Self::attacks(from) & target).for_each(|to| {
moves.push(Move::Normal {
role: Self::ROLE,
from,
capture: pos.board().role_at(to),
to,
promotion: None,
});
});
});
}
}
trait Slider {
const ROLE: Role;
fn attacks(from: Square, occupied: Bitboard) -> Bitboard;
fn gen_moves<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
pos.our(Self::ROLE).for_each(|from| {
(Self::attacks(from, pos.board().occupied()) & target).for_each(|to| {
moves.push(Move::Normal {
role: Self::ROLE,
from,
capture: pos.board().role_at(to),
to,
promotion: None,
});
});
});
}
}
enum KnightTag {}
enum BishopTag {}
enum RookTag {}
enum QueenTag {}
impl Stepper for KnightTag {
const ROLE: Role = Role::Knight;
#[inline]
fn attacks(from: Square) -> Bitboard {
attacks::knight_attacks(from)
}
}
impl Slider for BishopTag {
const ROLE: Role = Role::Bishop;
#[inline]
fn attacks(from: Square, occupied: Bitboard) -> Bitboard {
attacks::bishop_attacks(from, occupied)
}
}
impl Slider for RookTag {
const ROLE: Role = Role::Rook;
#[inline]
fn attacks(from: Square, occupied: Bitboard) -> Bitboard {
attacks::rook_attacks(from, occupied)
}
}
impl Slider for QueenTag {
const ROLE: Role = Role::Queen;
#[inline]
fn attacks(from: Square, occupied: Bitboard) -> Bitboard {
attacks::queen_attacks(from, occupied)
}
}
fn gen_pawn_moves<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
#[inline(always)]
fn gen_pawn_captures<P: Position>(
pos: &P,
dir: Direction,
target: Bitboard,
moves: &mut MoveList,
) {
let captures = dir.translate(pos.our(Role::Pawn)) & pos.them() & target;
(captures & !Bitboard::BACKRANKS).for_each(|to| {
let from = unsafe { to.offset_unchecked(-dir.offset()) };
moves.push(Move::Normal {
role: Role::Pawn,
from,
capture: pos.board().role_at(to),
to,
promotion: None,
});
});
(captures & Bitboard::BACKRANKS).for_each(|to| {
let from = unsafe { to.offset_unchecked(-dir.offset()) };
push_promotions(moves, from, to, pos.board().role_at(to));
});
}
gen_pawn_captures(
pos,
pos.turn()
.fold_wb(Direction::NorthWest, Direction::SouthWest),
target,
moves,
);
gen_pawn_captures(
pos,
pos.turn()
.fold_wb(Direction::NorthEast, Direction::SouthEast),
target,
moves,
);
let single_moves =
pos.our(Role::Pawn).shift(pos.turn().fold_wb(8, -8)) & !pos.board().occupied();
(single_moves & target & !Bitboard::BACKRANKS).for_each(|to| {
let from = unsafe { to.offset_unchecked(pos.turn().fold_wb(-8, 8)) };
moves.push(Move::Normal {
role: Role::Pawn,
from,
capture: None,
to,
promotion: None,
});
});
(single_moves & target & Bitboard::BACKRANKS).for_each(|to| {
let from = unsafe { to.offset_unchecked(pos.turn().fold_wb(-8, 8)) };
push_promotions(moves, from, to, None);
});
let double_moves = single_moves.shift(pos.turn().fold_wb(8, -8))
& pos.turn().fold_wb(Bitboard::SOUTH, Bitboard::NORTH)
& !pos.board().occupied();
(double_moves & target).for_each(|to| {
let from = unsafe { to.offset_unchecked(pos.turn().fold_wb(-16, 16)) };
moves.push(Move::Normal {
role: Role::Pawn,
from,
capture: None,
to,
promotion: None,
});
});
}
fn push_promotions(moves: &mut MoveList, from: Square, to: Square, capture: Option<Role>) {
for promotion in [Role::Queen, Role::Rook, Role::Bishop, Role::Knight] {
moves.push(Move::Normal {
role: Role::Pawn,
from,
capture,
to,
promotion: Some(promotion),
});
}
}
fn gen_en_passant(
board: &Board,
turn: Color,
ep_square: Option<EnPassant>,
moves: &mut MoveList,
) -> bool {
let mut found = false;
if let Some(EnPassant(to)) = ep_square {
(board.pawns() & board.by_color(turn) & attacks::pawn_attacks(!turn, to)).for_each(
|from| {
moves.push(Move::EnPassant { from, to });
found = true;
},
);
}
found
}
fn slider_blockers(board: &Board, enemy: Bitboard, king: Square) -> Bitboard {
let snipers = (attacks::rook_attacks(king, Bitboard(0)) & board.rooks_and_queens())
| (attacks::bishop_attacks(king, Bitboard(0)) & board.bishops_and_queens());
let mut blockers = Bitboard(0);
(snipers & enemy).for_each(|sniper| {
let b = attacks::between(king, sniper) & board.occupied();
if !b.more_than_one() {
blockers.add(b);
}
});
blockers
}
fn is_safe<P: Position>(pos: &P, king: Square, m: Move, blockers: Bitboard) -> bool {
match m {
Move::Normal { from, to, .. } => {
!blockers.contains(from) || attacks::aligned(from, to, king)
}
Move::EnPassant { from, to } => {
let capture = Square::from_coords(to.file(), from.rank());
pos.board()
.attacks_to(
king,
!pos.turn(),
pos.board()
.occupied()
.toggled(from)
.toggled(capture)
.with(to),
)
.without(capture)
.is_empty()
}
_ => true,
}
}
fn filter_san_candidates(role: Role, to: Square, moves: &mut MoveList) {
moves.retain(|m| match *m {
Move::Normal { role: r, to: t, .. } | Move::Put { role: r, to: t } => to == t && role == r,
Move::EnPassant { to: t, .. } => role == Role::Pawn && t == to,
Move::Castle { .. } => false,
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fen::{Epd, Fen};
#[cfg(feature = "alloc")]
struct _AssertObjectSafe(alloc::boxed::Box<dyn Position>);
fn setup_fen<T: Position + FromSetup>(fen: &str) -> T {
fen.parse::<Fen>()
.expect("valid fen")
.into_position::<T>(CastlingMode::Chess960)
.expect("legal position")
}
#[test]
fn test_most_known_legals() {
let pos: Chess = setup_fen("R6R/3Q4/1Q4Q1/4Q3/2Q4Q/Q4Q2/pp1Q4/kBNN1KB1 w - - 0 1");
assert_eq!(pos.legal_moves().len(), 218);
}
#[test]
fn test_pinned_san_candidate() {
let pos: Chess = setup_fen("R2r2k1/6pp/1Np2p2/1p2pP2/4p3/4K3/3r2PP/8 b - - 5 37");
let moves = pos.san_candidates(Role::Rook, Square::D3);
assert_eq!(
moves[0],
Move::Normal {
role: Role::Rook,
from: Square::D2,
capture: None,
to: Square::D3,
promotion: None,
}
);
assert_eq!(moves.len(), 1);
}
#[test]
fn test_promotion() {
let pos: Chess = setup_fen("3r3K/6PP/8/8/8/2k5/8/8 w - - 0 1");
let moves = pos.legal_moves();
assert!(moves.iter().all(|m| m.role() == Role::Pawn));
assert!(moves.iter().all(|m| m.is_promotion()));
}
fn assert_insufficient_material<P>(fen: &str, white: bool, black: bool)
where
P: Position + FromSetup,
{
let pos: P = setup_fen(fen);
assert_eq!(
pos.has_insufficient_material(White),
white,
"expected white {}",
if white { "cannot win" } else { "can win " }
);
assert_eq!(
pos.has_insufficient_material(Black),
black,
"expected black {}",
if black { "cannot win" } else { "can win" }
);
}
#[test]
fn test_insufficient_material() {
assert_insufficient_material::<Chess>("8/5k2/8/8/8/8/3K4/8 w - - 0 1", true, true);
assert_insufficient_material::<Chess>("8/3k4/8/8/2N5/8/3K4/8 b - - 0 1", true, true);
assert_insufficient_material::<Chess>("8/4rk2/8/8/8/8/3K4/8 w - - 0 1", true, false);
assert_insufficient_material::<Chess>("8/4qk2/8/8/8/8/3K4/8 w - - 0 1", true, false);
assert_insufficient_material::<Chess>("8/4bk2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
assert_insufficient_material::<Chess>("8/8/3Q4/2bK4/B7/8/1k6/8 w - - 1 68", false, false);
assert_insufficient_material::<Chess>("8/5k2/8/8/8/4B3/3K1B2/8 w - - 0 1", true, true);
assert_insufficient_material::<Chess>("5K2/8/8/1B6/8/k7/6b1/8 w - - 0 39", true, true);
assert_insufficient_material::<Chess>("8/8/8/4k3/5b2/3K4/8/2B5 w - - 0 33", true, true);
assert_insufficient_material::<Chess>("3b4/8/8/6b1/8/8/R7/K1k5 w - - 0 1", false, true);
}
#[test]
fn test_outcome() {
for (fen, outcome) in [
(
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
Outcome::Unknown,
),
(
"2k5/8/8/8/8/8/8/3KB3 w - - 0 1",
Outcome::Known(KnownOutcome::Draw),
),
(
"8/8/8/8/8/Q1K5/8/1k6 b - - 0 1",
Outcome::Known(KnownOutcome::Draw),
),
(
"8/8/8/8/8/Q7/2K5/k7 b - - 0 1",
Outcome::Known(KnownOutcome::Decisive { winner: White }),
),
] {
let pos: Chess = setup_fen(fen);
assert_eq!(pos.outcome(), outcome);
}
}
#[test]
#[cfg(feature = "std")]
fn test_eq() {
fn hash<T: Hash>(value: &T) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
assert_eq!(Chess::default(), Chess::default());
assert_eq!(hash(&Chess::default()), hash(&Chess::default()));
assert_ne!(
Chess::default(),
Chess::default().swap_turn().expect("swap turn legal")
);
let pos: Chess = setup_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBK1BNR w KQkq - 0 1");
let pos_after_move_played = pos
.play(Move::Normal {
role: Role::King,
from: Square::D1,
to: Square::E1,
capture: None,
promotion: None,
})
.expect("Ke1 is legal");
let pos_after_move =
setup_fen::<Chess>("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR b kq - 1 1");
assert_eq!(pos_after_move, pos_after_move_played);
assert_eq!(hash(&pos_after_move), hash(&pos_after_move_played));
let pos: Chess = setup_fen("rnbqkbn1/pppppppP/8/8/8/8/PPPPPPP1/RNB~QKBNR w KQq - 0 26");
let pos_after_queen_promotion = pos
.clone()
.play(Move::Normal {
role: Role::Pawn,
from: Square::H7,
to: Square::H8,
capture: None,
promotion: Some(Role::Queen),
})
.expect("h8=Q is legal");
let pos_after_knight_promotion = pos
.play(Move::Normal {
role: Role::Pawn,
from: Square::H7,
to: Square::H8,
capture: None,
promotion: Some(Role::Knight),
})
.expect("h8=N is legal");
let final_pos: Chess =
setup_fen("rnbqkbnQ/ppppppp1/8/8/8/8/PPPPPPP1/RNBQKBNR b KQq - 0 26");
assert_eq!(pos_after_queen_promotion, final_pos);
assert_eq!(hash(&pos_after_queen_promotion), hash(&final_pos));
assert_ne!(pos_after_knight_promotion, final_pos);
let pos: Chess = setup_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1");
let pos_with_irrelevant_ep: Chess =
setup_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1");
assert_eq!(pos, pos_with_irrelevant_ep);
assert_eq!(hash(&pos), hash(&pos_with_irrelevant_ep));
}
#[cfg(feature = "variant")]
#[test]
fn test_variant_insufficient_material() {
use super::variant::*;
let false_negative = false;
assert_insufficient_material::<Atomic>("8/3k4/8/8/2N5/8/3K4/8 b - - 0 1", true, true);
assert_insufficient_material::<Atomic>("8/4rk2/8/8/8/8/3K4/8 w - - 0 1", true, true);
assert_insufficient_material::<Atomic>("8/4qk2/8/8/8/8/3K4/8 w - - 0 1", true, false);
assert_insufficient_material::<Atomic>("8/1k6/8/2n5/8/3NK3/8/8 b - - 0 1", false, false);
assert_insufficient_material::<Atomic>("8/4bk2/8/8/8/8/3KB3/8 w - - 0 1", true, true);
assert_insufficient_material::<Atomic>("4b3/5k2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
assert_insufficient_material::<Atomic>("3Q4/5kKB/8/8/8/8/8/8 b - - 0 1", false, true);
assert_insufficient_material::<Atomic>("8/5k2/8/8/8/8/5K2/4bb2 w - - 0 1", true, false);
assert_insufficient_material::<Atomic>("8/5k2/8/8/8/8/5K2/4nb2 w - - 0 1", true, false);
assert_insufficient_material::<Antichess>("8/4bk2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
assert_insufficient_material::<Antichess>("4b3/5k2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
assert_insufficient_material::<Antichess>("8/8/8/6b1/8/3B4/4B3/5B2 w - - 0 1", true, true);
assert_insufficient_material::<Antichess>("8/8/5b2/8/8/3B4/3B4/8 w - - 0 1", true, false);
assert_insufficient_material::<Antichess>(
"8/5p2/5P2/8/3B4/1bB5/8/8 b - - 0 1",
false_negative,
false_negative,
);
assert_insufficient_material::<Antichess>(
"8/8/6b1/8/3P4/8/5B2/8 w - - 0 1",
false_negative,
false,
);
assert_insufficient_material::<KingOfTheHill>(
"8/5k2/8/8/8/8/3K4/8 w - - 0 1",
false,
false,
);
assert_insufficient_material::<RacingKings>("8/5k2/8/8/8/8/3K4/8 w - - 0 1", false, false);
assert_insufficient_material::<ThreeCheck>("8/5k2/8/8/8/8/3K4/8 w - - 0 1", true, true);
assert_insufficient_material::<ThreeCheck>("8/5k2/8/8/8/8/3K2N1/8 w - - 0 1", false, true);
assert_insufficient_material::<Crazyhouse>("8/5k2/8/8/8/8/3K2N1/8 w - - 0 1", true, true);
assert_insufficient_material::<Crazyhouse>(
"8/5k2/8/8/8/5B2/3KB3/8 w - - 0 1",
false,
false,
);
assert_insufficient_material::<Crazyhouse>(
"8/8/8/8/3k4/3N~4/3K4/8 w - - 0 1",
false,
false,
);
assert_insufficient_material::<Horde>("8/5k2/8/8/8/4NN2/8/8 w - - 0 1", true, false);
assert_insufficient_material::<Horde>("8/8/8/7k/7P/7P/8/8 b - - 0 58", false, false);
}
#[cfg(feature = "variant")]
#[test]
fn test_exploded_king_loses_castling_rights() {
use super::variant::Atomic;
let pos: Atomic = setup_fen("rnb1kbnr/pppppppp/8/4q3/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1");
let pos = pos
.play(Move::Normal {
role: Role::Queen,
from: Square::E5,
to: Square::E2,
capture: Some(Role::Pawn),
promotion: None,
})
.expect("Qxe2# is legal");
assert_eq!(
pos.castles().castling_rights(),
Bitboard::from(Square::A8) | Bitboard::from(Square::H8)
);
assert_eq!(
pos.castles().rook(Color::White, CastlingSide::QueenSide),
None
);
assert_eq!(
pos.castles().rook(Color::White, CastlingSide::KingSide),
None
);
assert_eq!(
pos.castles().rook(Color::Black, CastlingSide::QueenSide),
Some(Square::A8)
);
assert_eq!(
pos.castles().rook(Color::Black, CastlingSide::KingSide),
Some(Square::H8)
);
}
#[cfg(feature = "variant")]
#[test]
fn test_atomic_exploded_king() {
use super::variant::Atomic;
let pos: Atomic = setup_fen("rn5r/pp4pp/2p3Nn/5p2/1b2P1PP/8/PPP2P2/R1B1KB1R b KQ - 0 9");
assert_eq!(
pos.outcome(),
Outcome::Known(KnownOutcome::Decisive {
winner: Color::White
})
);
}
#[cfg(feature = "variant")]
#[test]
fn test_racing_kings_end() {
use super::variant::RacingKings;
let pos: RacingKings = setup_fen("kr3NK1/1q2R3/8/8/8/5n2/2N5/1rb2B1R w - - 11 14");
assert!(pos.is_variant_end());
assert_eq!(pos.variant_outcome(), Outcome::Known(KnownOutcome::Draw));
let pos: RacingKings = setup_fen("1k6/6K1/8/8/8/8/8/8 w - - 0 1");
assert!(pos.is_variant_end());
assert_eq!(
pos.variant_outcome(),
Outcome::Known(KnownOutcome::Decisive {
winner: Color::Black
})
);
let pos: RacingKings = setup_fen("1K6/7k/8/8/8/8/8/8 b - - 0 1");
assert_eq!(pos.variant_outcome(), Outcome::Unknown);
let pos: RacingKings = setup_fen("2KR4/k7/2Q5/4q3/8/8/8/2N5 b - - 0 1");
assert!(pos.is_variant_end());
assert_eq!(
pos.variant_outcome(),
Outcome::Known(KnownOutcome::Decisive {
winner: Color::White
})
);
}
#[cfg(feature = "variant")]
#[test]
fn test_antichess_insufficient_material() {
use super::variant::Antichess;
for (fen, possible_winner) in [
("8/8/8/1n2N3/8/8/8/8 w - - 0 32", Color::Black),
("8/3N4/8/1n6/8/8/8/8 b - - 1 32", Color::Black),
("8/8/8/8/2N5/8/8/n7 w - - 0 30", Color::Black),
("8/8/8/4N3/8/8/8/n7 b - - 1 30", Color::Black),
("8/8/8/4N3/8/8/2n5/8 w - - 2 31", Color::Black),
("8/8/6N1/8/8/8/2n5/8 b - - 3 31", Color::Black),
("8/8/6N1/8/8/4n3/8/8 w - - 4 32", Color::Black),
("5N2/8/8/8/8/4n3/8/8 b - - 5 32", Color::Black),
("5N2/8/8/5n2/8/8/8/8 w - - 6 33", Color::Black),
("6n1/8/8/4N3/8/8/8/8 b - - 0 27", Color::White),
("8/8/5n2/4N3/8/8/8/8 w - - 1 28", Color::White),
("8/3N4/5n2/8/8/8/8/8 b - - 2 28", Color::White),
("8/3n4/8/8/8/8/8/8 w - - 0 29", Color::White),
] {
let pos = fen
.parse::<Fen>()
.expect("valid fen")
.into_position::<Antichess>(CastlingMode::Standard)
.expect("legal position");
assert!(
!pos.has_insufficient_material(possible_winner),
"{possible_winner} can win {fen}"
);
assert!(
pos.has_insufficient_material(!possible_winner),
"{possible_winner} can not win {fen}"
);
}
}
#[test]
fn test_aligned_checkers() {
let res = "2Nq4/2K5/1b6/8/7R/3k4/7P/8 w - - 0 1"
.parse::<Fen>()
.expect("valid fen")
.into_position::<Chess>(CastlingMode::Chess960);
assert_eq!(
res.expect_err("impossible check").kinds(),
PositionErrorKinds::IMPOSSIBLE_CHECK
);
let _ = "8/8/5k2/p1q5/PP1rp1P1/3P1N2/2RK1r2/5nN1 w - - 0 3"
.parse::<Fen>()
.expect("valid fen")
.into_position::<Chess>(CastlingMode::Standard)
.expect("checkers aligned with opponent king not relevant");
let res = "8/8/8/1k6/3Pp3/8/8/4KQ2 b - d3 0 1"
.parse::<Fen>()
.expect("valid fen")
.into_position::<Chess>(CastlingMode::Standard);
assert_eq!(
res.expect_err("impossible check due to ep square").kinds(),
PositionErrorKinds::IMPOSSIBLE_CHECK
);
}
#[cfg(feature = "alloc")]
#[test]
fn test_swap_turn() {
use alloc::string::ToString as _;
let pos: Chess = "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"
.parse::<Fen>()
.expect("valid fen")
.into_position(CastlingMode::Chess960)
.expect("valid position");
let swapped = pos.swap_turn().expect("swap turn");
assert_eq!(
Fen::from_position(&swapped, EnPassantMode::Always).to_string(),
"rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 3"
);
}
#[test]
fn test_invalid_ep_square() {
let fen: Fen = "4k3/8/8/8/8/8/8/4K3 w - e3 0 1".parse().expect("valid fen");
let err = fen
.into_position::<Chess>(CastlingMode::Standard)
.expect_err("invalid ep square");
assert_eq!(err.kinds(), PositionErrorKinds::INVALID_EP_SQUARE);
assert_eq!(
err.ignore_invalid_ep_square()
.expect("now valid")
.maybe_ep_square(),
None
);
}
#[test]
fn test_check_with_unrelated_ep_square() {
let fen: Fen = "rnbqk1nr/bb3p1p/1q2r3/2pPp3/3P4/7P/1PP1NpPP/R1BQKBNR w KQkq c6 0 1"
.parse()
.expect("valid fen");
let pos = fen
.into_position::<Chess>(CastlingMode::Standard)
.expect_err("impossible check")
.ignore_impossible_check()
.expect("legal otherwise");
assert!(pos.san_candidates(Role::Pawn, Square::C6).is_empty());
assert!(pos.en_passant_moves().is_empty());
assert_eq!(pos.legal_moves().len(), 2);
}
#[test]
fn test_put_in_standard() {
let pos = Chess::default();
assert!(!pos.is_legal(Move::Put {
role: Role::Pawn,
to: Square::D4
}));
}
#[test]
fn test_from_setup() {
for (epd, expected_error_kinds) in [
(
"1rrrrrk1/1PPPPPPP/8/8/8/8/8/6K1 b - -",
PositionErrorKinds::IMPOSSIBLE_CHECK,
),
(
"1rrrrrk1/PPPPPPPP/8/8/8/8/8/6K1 b - -",
PositionErrorKinds::IMPOSSIBLE_CHECK,
),
(
"1rrrrrkr/PPPPPPPP/8/8/8/8/8/6K1 b - -",
PositionErrorKinds::IMPOSSIBLE_CHECK,
),
(
"1q4k1/3r1Ppp/5NP1/pP6/8/1Q6/3B4/2K2R2 b - -",
PositionErrorKinds::IMPOSSIBLE_CHECK,
),
(
"2b5/1nbn4/n3n3/1kn5/n3n3/1n1n4/5RQ1/2KQ1R2 w K -",
PositionErrorKinds::IMPOSSIBLE_CHECK,
),
(
"1n1b1Q2/1b4rp/1q5P/2Pppr2/p2kP1pR/2NP4/P2Bq1K1/RQ3Br1 w - -",
PositionErrorKinds::default(),
),
(
"1n1b1Q2/1b4rp/1q5P/2Pppr2/p2kP1pR/2NP4/P2Bq1K1/RQ3Bq1 w - -",
PositionErrorKinds::default(),
),
(
"1n1b1Q2/qb4rp/7P/2Pppr2/p2kP1p1/2NP4/P2Bq1KR/RQ3Br1 w - -",
PositionErrorKinds::default(),
),
(
"1n1b1Q2/qb4rp/7P/2Pppr2/p2kP1p1/2NP4/P2Bq1KR/RQ3Bq1 w - -",
PositionErrorKinds::default(),
),
] {
let error_kinds = epd
.parse::<Epd>()
.expect("valid epd")
.into_position::<Chess>(CastlingMode::Chess960)
.map_err(|err| err.kinds())
.err()
.unwrap_or_default();
assert_eq!(error_kinds, expected_error_kinds, "epd: {epd}");
}
}
}