1use core::{
2 error, fmt,
3 hash::{Hash, Hasher},
4 num::NonZeroU32,
5 str::FromStr,
6};
7
8use bitflags::bitflags;
9
10use crate::{
11 Bitboard, Board, ByColor, ByRole, Castles, CastlingMode, CastlingSide, Color,
12 Color::{Black, White},
13 EnPassantMode, Move, MoveList, Piece, Rank, RemainingChecks, Role, Setup, Square, attacks,
14 bitboard::Direction,
15 setup::EnPassant,
16 zobrist::ZobristValue,
17};
18
19#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
23#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
24pub enum KnownOutcome {
25 Decisive { winner: Color },
26 Draw,
27}
28
29impl KnownOutcome {
30 pub const fn from_winner(winner: Option<Color>) -> KnownOutcome {
31 match winner {
32 Some(winner) => KnownOutcome::Decisive { winner },
33 None => KnownOutcome::Draw,
34 }
35 }
36
37 pub const fn winner(self) -> Option<Color> {
38 match self {
39 KnownOutcome::Decisive { winner } => Some(winner),
40 KnownOutcome::Draw => None,
41 }
42 }
43
44 pub const fn from_ascii(bytes: &[u8]) -> Result<KnownOutcome, ParseOutcomeError> {
45 Ok(match bytes {
46 b"1-0" => KnownOutcome::Decisive { winner: White },
47 b"0-1" => KnownOutcome::Decisive { winner: Black },
48 b"1/2-1/2" => KnownOutcome::Draw,
49 _ => return Err(ParseOutcomeError),
50 })
51 }
52
53 pub const fn as_str(self) -> &'static str {
54 match self {
55 KnownOutcome::Decisive { winner: White } => "1-0",
56 KnownOutcome::Decisive { winner: Black } => "0-1",
57 KnownOutcome::Draw => "1/2-1/2",
58 }
59 }
60}
61
62impl fmt::Display for KnownOutcome {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.write_str(self.as_str())
65 }
66}
67
68impl FromStr for KnownOutcome {
69 type Err = ParseOutcomeError;
70
71 fn from_str(s: &str) -> Result<KnownOutcome, ParseOutcomeError> {
72 KnownOutcome::from_ascii(s.as_bytes())
73 }
74}
75
76#[cfg(feature = "bincode")]
77impl bincode::Encode for KnownOutcome {
78 fn encode<E: bincode::enc::Encoder>(
79 &self,
80 encoder: &mut E,
81 ) -> Result<(), bincode::error::EncodeError> {
82 Outcome::Known(*self).encode(encoder)
83 }
84}
85
86#[cfg(feature = "bincode")]
87impl<Config> bincode::Decode<Config> for KnownOutcome {
88 fn decode<D: bincode::de::Decoder>(
89 decoder: &mut D,
90 ) -> Result<Self, bincode::error::DecodeError> {
91 Outcome::decode(decoder).and_then(|outcome| {
92 outcome
93 .known()
94 .ok_or(bincode::error::DecodeError::Other("invalid KnownOutcome"))
95 })
96 }
97}
98
99#[cfg(feature = "bincode")]
100bincode::impl_borrow_decode!(KnownOutcome);
101
102#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
104#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
105pub enum Outcome {
106 Known(KnownOutcome),
108 Unknown,
110}
111
112impl Outcome {
113 pub const fn from_known(outcome: Option<KnownOutcome>) -> Outcome {
114 match outcome {
115 Some(outcome) => Self::Known(outcome),
116 None => Self::Unknown,
117 }
118 }
119
120 pub const fn known(self) -> Option<KnownOutcome> {
121 match self {
122 Self::Known(outcome) => Some(outcome),
123 Self::Unknown => None,
124 }
125 }
126
127 pub const fn is_known(self) -> bool {
128 matches!(self, Self::Known(_))
129 }
130
131 pub const fn is_unknown(self) -> bool {
132 matches!(self, Self::Unknown)
133 }
134
135 pub const fn winner(self) -> Option<Color> {
136 match self {
137 Self::Known(outcome) => outcome.winner(),
138 Self::Unknown => None,
139 }
140 }
141
142 pub fn from_ascii(bytes: &[u8]) -> Result<Outcome, ParseOutcomeError> {
143 if bytes == b"*" {
144 Ok(Self::Unknown)
145 } else {
146 KnownOutcome::from_ascii(bytes).map(Self::Known)
147 }
148 }
149
150 pub const fn as_str(self) -> &'static str {
151 match self {
152 Self::Known(outcome) => outcome.as_str(),
153 Self::Unknown => "*",
154 }
155 }
156}
157
158impl From<KnownOutcome> for Outcome {
159 fn from(outcome: KnownOutcome) -> Self {
160 Self::Known(outcome)
161 }
162}
163
164impl FromStr for Outcome {
165 type Err = ParseOutcomeError;
166
167 fn from_str(s: &str) -> Result<Self, Self::Err> {
168 Self::from_ascii(s.as_bytes())
169 }
170}
171
172impl fmt::Display for Outcome {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.write_str(self.as_str())
175 }
176}
177
178#[derive(Clone, Debug)]
180pub struct ParseOutcomeError;
181
182impl fmt::Display for ParseOutcomeError {
183 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184 f.write_str("invalid outcome")
185 }
186}
187
188impl error::Error for ParseOutcomeError {}
189
190#[cfg(feature = "bincode")]
191impl bincode::Encode for Outcome {
192 fn encode<E: bincode::enc::Encoder>(
193 &self,
194 encoder: &mut E,
195 ) -> Result<(), bincode::error::EncodeError> {
196 u8::encode(
197 &match self {
198 Outcome::Unknown => 0,
199 Outcome::Known(KnownOutcome::Decisive {
200 winner: Color::White,
201 }) => 1,
202 Outcome::Known(KnownOutcome::Decisive {
203 winner: Color::Black,
204 }) => 2,
205 Outcome::Known(KnownOutcome::Draw) => 3,
206 },
207 encoder,
208 )
209 }
210}
211
212#[cfg(feature = "bincode")]
213impl<Config> bincode::Decode<Config> for Outcome {
214 fn decode<D: bincode::de::Decoder>(
215 decoder: &mut D,
216 ) -> Result<Self, bincode::error::DecodeError> {
217 Ok(match u8::decode(decoder)? {
218 0 => Outcome::Unknown,
219 1 => Outcome::Known(KnownOutcome::Decisive {
220 winner: Color::White,
221 }),
222 2 => Outcome::Known(KnownOutcome::Decisive {
223 winner: Color::Black,
224 }),
225 3 => Outcome::Known(KnownOutcome::Draw),
226 _ => return Err(bincode::error::DecodeError::Other("invalid Outcome")),
227 })
228 }
229}
230
231#[cfg(feature = "bincode")]
232bincode::impl_borrow_decode!(Outcome);
233
234#[derive(Debug)]
236pub struct PlayError<P> {
237 pub m: Move,
239 pub position: P,
241}
242
243impl<P: fmt::Debug> fmt::Display for PlayError<P> {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 write!(f, "illegal move {:?} in {:?}", self.m, self.position)
246 }
247}
248
249impl<P: fmt::Debug> error::Error for PlayError<P> {}
250
251bitflags! {
252 #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
265 pub struct PositionErrorKinds: u32 {
266 const EMPTY_BOARD = 1 << 0;
268
269 const MISSING_KING = 1 << 1;
271
272 const TOO_MANY_KINGS = 1 << 2;
274
275 const PAWNS_ON_BACKRANK = 1 << 3;
278
279 const INVALID_CASTLING_RIGHTS = 1 << 4;
284
285 const INVALID_EP_SQUARE = 1 << 5;
291
292 const OPPOSITE_CHECK = 1 << 6;
294
295 const IMPOSSIBLE_CHECK = 1 << 7;
304
305 const TOO_MUCH_MATERIAL = 1 << 8;
317
318 const VARIANT = 1 << 9;
320 }
321}
322
323#[derive(Clone)]
327pub struct PositionError<P> {
328 pub(crate) pos: P,
329 pub(crate) errors: PositionErrorKinds,
330}
331
332impl<P> PositionError<P> {
333 fn ignore(mut self, ignore: PositionErrorKinds) -> Result<P, Self> {
334 self.errors -= ignore;
335 match self {
336 PositionError { pos, errors } if errors.is_empty() => Ok(pos),
337 _ => Err(self),
338 }
339 }
340
341 fn strict(self) -> Result<P, Self> {
342 self.ignore(PositionErrorKinds::empty())
343 }
344
345 pub fn ignore_invalid_castling_rights(self) -> Result<P, Self> {
348 self.ignore(PositionErrorKinds::INVALID_CASTLING_RIGHTS)
349 }
350
351 pub fn ignore_invalid_ep_square(self) -> Result<P, Self> {
354 self.ignore(PositionErrorKinds::INVALID_EP_SQUARE)
355 }
356
357 pub fn ignore_too_much_material(self) -> Result<P, Self> {
361 self.ignore(PositionErrorKinds::TOO_MUCH_MATERIAL)
362 }
363
364 pub fn ignore_impossible_check(self) -> Result<P, Self> {
369 self.ignore(PositionErrorKinds::IMPOSSIBLE_CHECK)
370 }
371
372 pub fn kinds(&self) -> PositionErrorKinds {
374 self.errors
375 }
376}
377
378impl<P> fmt::Debug for PositionError<P> {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 f.debug_struct("PositionError")
381 .field("errors", &self.errors)
382 .finish_non_exhaustive()
383 }
384}
385
386impl<P> fmt::Display for PositionError<P> {
387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388 f.write_str("illegal position: ")?;
389
390 let mut first = true;
391 let mut reason = |kind: PositionErrorKinds, display: &str| -> fmt::Result {
392 if self.errors.contains(kind) {
393 if !first {
394 f.write_str(", ")?;
395 }
396 f.write_str(display)?;
397 first = false;
398 }
399 Ok(())
400 };
401
402 reason(PositionErrorKinds::EMPTY_BOARD, "empty board")?;
403 reason(PositionErrorKinds::MISSING_KING, "missing king")?;
404 reason(PositionErrorKinds::TOO_MANY_KINGS, "too many kings")?;
405 reason(PositionErrorKinds::PAWNS_ON_BACKRANK, "pawns on backrank")?;
406 reason(
407 PositionErrorKinds::INVALID_CASTLING_RIGHTS,
408 "invalid castling rights",
409 )?;
410 reason(PositionErrorKinds::INVALID_EP_SQUARE, "invalid ep square")?;
411 reason(PositionErrorKinds::OPPOSITE_CHECK, "opposite check")?;
412 reason(PositionErrorKinds::IMPOSSIBLE_CHECK, "impossible check")?;
413 reason(PositionErrorKinds::TOO_MUCH_MATERIAL, "too much material")?;
414 reason(PositionErrorKinds::VARIANT, "variant rule violated")?;
415 if first {
416 f.write_str("unknown reason")?;
417 }
418
419 Ok(())
420 }
421}
422
423impl<P> error::Error for PositionError<P> {}
424
425pub trait FromSetup: Sized {
428 fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Self, PositionError<Self>>;
453}
454
455pub trait Position {
483 fn board(&self) -> &Board;
485 fn promoted(&self) -> Bitboard;
487 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>>;
489 fn turn(&self) -> Color;
491 fn castles(&self) -> &Castles;
493 fn maybe_ep_square(&self) -> Option<Square>;
500 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>>;
502 fn halfmoves(&self) -> u32;
505 fn fullmoves(&self) -> NonZeroU32;
507
508 fn to_setup(&self, mode: EnPassantMode) -> Setup {
510 Setup {
511 board: self.board().clone(),
512 promoted: self.promoted(),
513 pockets: self.pockets().copied(),
514 turn: self.turn(),
515 castling_rights: self.castles().castling_rights(),
516 ep_square: self.ep_square(mode),
517 remaining_checks: self.remaining_checks().copied(),
518 halfmoves: self.halfmoves(),
519 fullmoves: self.fullmoves(),
520 }
521 }
522
523 fn legal_moves(&self) -> MoveList;
525
526 fn san_candidates(&self, role: Role, to: Square) -> MoveList {
529 let mut moves = self.legal_moves();
530 filter_san_candidates(role, to, &mut moves);
531 moves
532 }
533
534 fn castling_moves(&self, side: CastlingSide) -> MoveList {
536 let mut moves = self.legal_moves();
537 moves.retain(|m| m.castling_side().is_some_and(|s| side == s));
538 moves
539 }
540
541 fn en_passant_moves(&self) -> MoveList {
543 let mut moves = self.legal_moves();
544 moves.retain(|m| m.is_en_passant());
545 moves
546 }
547
548 fn capture_moves(&self) -> MoveList {
550 let mut moves = self.legal_moves();
551 moves.retain(|m| m.is_capture());
552 moves
553 }
554
555 fn promotion_moves(&self) -> MoveList {
557 let mut moves = self.legal_moves();
558 moves.retain(|m| m.is_promotion());
559 moves
560 }
561
562 fn is_irreversible(&self, m: Move) -> bool {
572 (match m {
573 Move::Normal {
574 role: Role::Pawn, ..
575 }
576 | Move::Normal {
577 capture: Some(_), ..
578 }
579 | Move::Castle { .. }
580 | Move::EnPassant { .. }
581 | Move::Put { .. } => true,
582 Move::Normal { role, from, to, .. } => {
583 self.castles().castling_rights().contains(from)
584 || self.castles().castling_rights().contains(to)
585 || (role == Role::King && self.castles().has_color(self.turn()))
586 }
587 }) || self.legal_ep_square().is_some()
588 }
589
590 fn king_attackers(&self, square: Square, attacker: Color, occupied: Bitboard) -> Bitboard {
592 self.board().attacks_to(square, attacker, occupied)
593 }
594
595 fn is_variant_end(&self) -> bool;
602
603 fn has_insufficient_material(&self, color: Color) -> bool;
622
623 fn variant_outcome(&self) -> Outcome;
625
626 fn play_unchecked(&mut self, m: Move);
635
636 fn us(&self) -> Bitboard {
643 self.board().by_color(self.turn())
644 }
645
646 fn our(&self, role: Role) -> Bitboard {
648 self.board().by_piece(role.of(self.turn()))
649 }
650
651 fn them(&self) -> Bitboard {
653 self.board().by_color(!self.turn())
654 }
655
656 fn their(&self, role: Role) -> Bitboard {
659 self.board().by_piece(role.of(!self.turn()))
660 }
661
662 fn is_legal(&self, m: Move) -> bool {
664 let moves = match m {
665 Move::Normal { role, to, .. } | Move::Put { role, to } => self.san_candidates(role, to),
666 Move::EnPassant { to, .. } => self.san_candidates(Role::Pawn, to),
667 Move::Castle { king, rook } if king.file() < rook.file() => {
668 self.castling_moves(CastlingSide::KingSide)
669 }
670 Move::Castle { .. } => self.castling_moves(CastlingSide::QueenSide),
671 };
672 moves.contains(&m)
673 }
674
675 fn pseudo_legal_ep_square(&self) -> Option<Square> {
678 self.maybe_ep_square().filter(|ep_square| {
679 (attacks::pawn_attacks(!self.turn(), *ep_square) & self.our(Role::Pawn)).any()
680 })
681 }
682
683 fn legal_ep_square(&self) -> Option<Square> {
687 self.pseudo_legal_ep_square()
688 .filter(|_| !self.en_passant_moves().is_empty())
689 }
690
691 fn ep_square(&self, mode: EnPassantMode) -> Option<Square> {
693 match mode {
694 EnPassantMode::Always => self.maybe_ep_square(),
695 EnPassantMode::PseudoLegal => self.pseudo_legal_ep_square(),
696 EnPassantMode::Legal => self.legal_ep_square(),
697 }
698 }
699
700 fn checkers(&self) -> Bitboard {
702 self.our(Role::King).first().map_or(Bitboard(0), |king| {
703 self.king_attackers(king, !self.turn(), self.board().occupied())
704 })
705 }
706
707 fn is_check(&self) -> bool {
709 self.checkers().any()
710 }
711
712 fn is_checkmate(&self) -> bool {
714 !self.checkers().is_empty() && self.legal_moves().is_empty()
715 }
716
717 fn is_stalemate(&self) -> bool {
719 self.checkers().is_empty() && !self.is_variant_end() && self.legal_moves().is_empty()
720 }
721
722 fn is_insufficient_material(&self) -> bool {
725 self.has_insufficient_material(White) && self.has_insufficient_material(Black)
726 }
727
728 fn is_game_over(&self) -> bool {
733 self.is_variant_end() || self.legal_moves().is_empty() || self.is_insufficient_material()
734 }
735
736 fn outcome(&self) -> Outcome {
738 let variant_outcome = self.variant_outcome();
739
740 if variant_outcome.is_known() {
741 return variant_outcome;
742 }
743
744 if self.legal_moves().is_empty() {
745 if self.is_check() {
746 Outcome::Known(KnownOutcome::Decisive {
747 winner: !self.turn(),
748 })
749 } else {
750 Outcome::Known(KnownOutcome::Draw) }
752 } else if self.is_insufficient_material() {
753 Outcome::Known(KnownOutcome::Draw)
754 } else {
755 Outcome::Unknown
756 }
757 }
758
759 fn play(mut self, m: Move) -> Result<Self, PlayError<Self>>
767 where
768 Self: Sized,
769 {
770 if self.is_legal(m) {
771 self.play_unchecked(m);
772 Ok(self)
773 } else {
774 Err(PlayError { m, position: self })
775 }
776 }
777
778 fn swap_turn(self) -> Result<Self, PositionError<Self>>
786 where
787 Self: Sized + FromSetup,
788 {
789 let mode = self.castles().mode();
790 let mut setup = self.to_setup(EnPassantMode::Always);
791 setup.swap_turn();
792 Self::from_setup(setup, mode)
793 }
794
795 fn zobrist_hash<V: ZobristValue>(&self, mode: EnPassantMode) -> V
798 where
799 Self: Sized, {
801 let mut zobrist = self.board().board_zobrist_hash();
802
803 for sq in self.promoted() {
804 zobrist ^= V::zobrist_for_promoted(sq);
805 }
806
807 if let Some(pockets) = self.pockets() {
808 for (color, pocket) in pockets.zip_color() {
809 for (role, pieces) in pocket.zip_role() {
810 zobrist ^= V::zobrist_for_pocket(color, role, pieces);
811 }
812 }
813 }
814
815 if self.turn() == Color::White {
816 zobrist ^= V::zobrist_for_white_turn();
817 }
818
819 let castles = self.castles();
820 for color in Color::ALL {
821 for side in CastlingSide::ALL {
822 if castles.has(color, side) {
823 zobrist ^= V::zobrist_for_castling_right(color, side);
824 }
825 }
826 }
827
828 if let Some(sq) = self.ep_square(mode) {
829 zobrist ^= V::zobrist_for_en_passant_file(sq.file());
830 }
831
832 if let Some(remaining_checks) = self.remaining_checks() {
833 for (color, remaining) in remaining_checks.zip_color() {
834 zobrist ^= V::zobrist_for_remaining_checks(color, remaining);
835 }
836 }
837
838 zobrist
839 }
840
841 fn update_zobrist_hash<V: ZobristValue>(
852 &self,
853 current: V,
854 m: Move,
855 mode: EnPassantMode,
856 ) -> Option<V>
857 where
858 Self: Sized,
859 {
860 let _current = current;
861 let _m = m;
862 let _mode = mode;
863 None
864 }
865}
866
867#[cfg(feature = "arbitrary")]
868#[derive(arbitrary::Arbitrary)]
869enum PositionMutation {
870 LegalMove { idx: u8 },
871 DiscardPieceAt { sq: Square },
872 SetPieceAt { sq: Square, piece: Piece },
873 ToggleCastles { rooks: Bitboard, mode: CastlingMode },
874}
875
876#[cfg(feature = "arbitrary")]
877fn arbitrary_position<P>(
878 mutations: impl IntoIterator<Item = arbitrary::Result<PositionMutation>>,
879) -> arbitrary::Result<P>
880where
881 P: Position + FromSetup + Default,
882{
883 let mut pos = P::default();
884 for mutation in mutations {
885 let mutation = mutation?;
886 match mutation {
887 PositionMutation::LegalMove { idx } => {
888 let moves = pos.legal_moves();
889 if let Some(idx) = usize::from(idx).checked_rem(moves.len()) {
890 pos.play_unchecked(moves[idx]);
891 }
892 }
893 PositionMutation::DiscardPieceAt { sq } => {
894 let mut setup = pos.to_setup(EnPassantMode::PseudoLegal);
895 setup.board.discard_piece_at(sq);
896 if let Ok(updated_pos) = setup
897 .position(pos.castles().mode())
898 .or_else(PositionError::ignore_invalid_castling_rights)
899 .or_else(PositionError::ignore_invalid_ep_square)
900 {
901 pos = updated_pos;
902 }
903 }
904 PositionMutation::SetPieceAt { sq, piece } => {
905 let mut setup = pos.to_setup(EnPassantMode::PseudoLegal);
906 setup.board.set_piece_at(sq, piece);
907 if let Ok(updated_pos) = setup
908 .position(pos.castles().mode())
909 .or_else(PositionError::ignore_invalid_castling_rights)
910 .or_else(PositionError::ignore_invalid_ep_square)
911 {
912 pos = updated_pos;
913 }
914 }
915 PositionMutation::ToggleCastles { rooks, mode } => {
916 let mut setup = pos.to_setup(EnPassantMode::PseudoLegal);
917 setup.castling_rights.toggle(rooks);
918 if let Ok(updated_pos) = setup
919 .position(mode)
920 .or_else(PositionError::ignore_invalid_castling_rights)
921 .or_else(PositionError::ignore_invalid_ep_square)
922 {
923 pos = updated_pos;
924 }
925 }
926 }
927 }
928 Ok(pos)
929}
930
931#[derive(Clone, Debug)]
939pub struct Chess {
940 board: Board,
941 turn: Color,
942 castles: Castles,
943 ep_square: Option<EnPassant>,
944 halfmoves: u32,
945 fullmoves: NonZeroU32,
946}
947
948impl Chess {
949 #[cfg(feature = "variant")]
950 fn gives_check(&self, m: Move) -> bool {
951 let mut pos = self.clone();
952 pos.play_unchecked(m);
953 pos.is_check()
954 }
955
956 #[allow(clippy::type_complexity)]
957 fn from_setup_unchecked(
958 setup: Setup,
959 mode: CastlingMode,
960 ) -> (
961 Chess,
962 Option<ByColor<ByRole<u8>>>,
963 Option<ByColor<RemainingChecks>>,
964 PositionErrorKinds,
965 ) {
966 let mut errors = PositionErrorKinds::empty();
967
968 let castles = match Castles::from_setup(&setup, mode) {
969 Ok(castles) => castles,
970 Err(castles) => {
971 errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
972 castles
973 }
974 };
975
976 let ep_square = match EnPassant::from_setup(&setup) {
977 Ok(ep_square) => ep_square,
978 Err(()) => {
979 errors |= PositionErrorKinds::INVALID_EP_SQUARE;
980 None
981 }
982 };
983
984 let pos = Chess {
985 board: setup.board,
986 turn: setup.turn,
987 castles,
988 ep_square,
989 halfmoves: setup.halfmoves,
990 fullmoves: setup.fullmoves,
991 };
992
993 errors |= validate(&pos, ep_square);
994
995 (pos, setup.pockets, setup.remaining_checks, errors)
996 }
997
998 pub const fn new() -> Chess {
1000 Chess {
1001 board: Board::new(),
1002 turn: White,
1003 castles: Castles::new(),
1004 ep_square: None,
1005 halfmoves: 0,
1006 fullmoves: NonZeroU32::MIN,
1007 }
1008 }
1009}
1010
1011impl Default for Chess {
1012 fn default() -> Chess {
1013 Chess::new()
1014 }
1015}
1016
1017impl Hash for Chess {
1018 fn hash<H: Hasher>(&self, state: &mut H) {
1019 self.board.hash(state);
1020 self.turn.hash(state);
1021 self.castles.castling_rights().hash(state);
1022 }
1025}
1026
1027impl PartialEq for Chess {
1028 fn eq(&self, other: &Chess) -> bool {
1029 self.board == other.board
1030 && self.turn == other.turn
1031 && self.castles.castling_rights() == other.castles.castling_rights()
1032 && self.legal_ep_square() == other.legal_ep_square()
1033 }
1034}
1035
1036impl Eq for Chess {}
1058
1059impl FromSetup for Chess {
1060 fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Chess, PositionError<Chess>> {
1061 let (pos, _, _, errors) = Chess::from_setup_unchecked(setup, mode);
1062 PositionError { pos, errors }.strict()
1063 }
1064}
1065
1066#[cfg(feature = "arbitrary")]
1067impl arbitrary::Arbitrary<'_> for Chess {
1068 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
1069 arbitrary_position(u.arbitrary_iter()?)
1070 }
1071
1072 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1073 arbitrary_position(u.arbitrary_take_rest_iter()?)
1074 }
1075}
1076
1077impl Position for Chess {
1078 fn board(&self) -> &Board {
1079 &self.board
1080 }
1081
1082 fn promoted(&self) -> Bitboard {
1083 Bitboard::EMPTY
1084 }
1085
1086 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
1087 None
1088 }
1089
1090 fn turn(&self) -> Color {
1091 self.turn
1092 }
1093
1094 fn castles(&self) -> &Castles {
1095 &self.castles
1096 }
1097
1098 fn maybe_ep_square(&self) -> Option<Square> {
1099 self.ep_square.map(EnPassant::square)
1100 }
1101
1102 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
1103 None
1104 }
1105
1106 fn halfmoves(&self) -> u32 {
1107 self.halfmoves
1108 }
1109
1110 fn fullmoves(&self) -> NonZeroU32 {
1111 self.fullmoves
1112 }
1113
1114 fn play_unchecked(&mut self, m: Move) {
1115 do_move(
1116 &mut self.board,
1117 &mut Bitboard(0),
1118 &mut self.turn,
1119 &mut self.castles,
1120 &mut self.ep_square,
1121 &mut self.halfmoves,
1122 &mut self.fullmoves,
1123 m,
1124 );
1125 }
1126
1127 fn legal_moves(&self) -> MoveList {
1128 let mut moves = MoveList::new();
1129
1130 let king = self
1131 .board()
1132 .king_of(self.turn())
1133 .expect("king in standard chess");
1134
1135 let has_ep = gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
1136
1137 let checkers = self.checkers();
1138 if checkers.is_empty() {
1139 let target = !self.us();
1140 gen_non_king(self, target, &mut moves);
1141 gen_safe_king(self, king, target, &mut moves);
1142 gen_castling_moves(
1143 self,
1144 &self.castles,
1145 king,
1146 CastlingSide::KingSide,
1147 &mut moves,
1148 );
1149 gen_castling_moves(
1150 self,
1151 &self.castles,
1152 king,
1153 CastlingSide::QueenSide,
1154 &mut moves,
1155 );
1156 } else {
1157 evasions(self, king, checkers, &mut moves);
1158 }
1159
1160 let blockers = slider_blockers(self.board(), self.them(), king);
1161 if blockers.any() || has_ep {
1162 moves.retain(|m| is_safe(self, king, *m, blockers));
1163 }
1164
1165 moves
1166 }
1167
1168 fn castling_moves(&self, side: CastlingSide) -> MoveList {
1169 let mut moves = MoveList::new();
1170 let king = self
1171 .board()
1172 .king_of(self.turn())
1173 .expect("king in standard chess");
1174 gen_castling_moves(self, &self.castles, king, side, &mut moves);
1175 moves
1176 }
1177
1178 fn en_passant_moves(&self) -> MoveList {
1179 let mut moves = MoveList::new();
1180
1181 if gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves) {
1182 let king = self
1183 .board()
1184 .king_of(self.turn())
1185 .expect("king in standard chess");
1186 let blockers = slider_blockers(self.board(), self.them(), king);
1187 moves.retain(|m| is_safe(self, king, *m, blockers));
1188 }
1189
1190 moves
1191 }
1192
1193 fn promotion_moves(&self) -> MoveList {
1194 let mut moves = MoveList::new();
1195
1196 let king = self
1197 .board()
1198 .king_of(self.turn())
1199 .expect("king in standard chess");
1200 let checkers = self.checkers();
1201
1202 if checkers.is_empty() {
1203 gen_pawn_moves(self, Bitboard::BACKRANKS, &mut moves);
1204 } else {
1205 evasions(self, king, checkers, &mut moves);
1206 moves.retain(|m| m.is_promotion());
1207 }
1208
1209 let blockers = slider_blockers(self.board(), self.them(), king);
1210 if blockers.any() {
1211 moves.retain(|m| is_safe(self, king, *m, blockers));
1212 }
1213
1214 moves
1215 }
1216
1217 fn san_candidates(&self, role: Role, to: Square) -> MoveList {
1218 let mut moves = MoveList::new();
1219
1220 let king = self
1221 .board()
1222 .king_of(self.turn())
1223 .expect("king in standard chess");
1224 let checkers = self.checkers();
1225
1226 if checkers.is_empty() {
1227 let piece_from = match role {
1228 Role::Pawn | Role::King => Bitboard(0),
1229 Role::Knight => attacks::knight_attacks(to),
1230 Role::Bishop => attacks::bishop_attacks(to, self.board().occupied()),
1231 Role::Rook => attacks::rook_attacks(to, self.board().occupied()),
1232 Role::Queen => attacks::queen_attacks(to, self.board().occupied()),
1233 };
1234
1235 if !self.us().contains(to) {
1236 match role {
1237 Role::Pawn => gen_pawn_moves(self, Bitboard::from_square(to), &mut moves),
1238 Role::King => gen_safe_king(self, king, Bitboard::from_square(to), &mut moves),
1239 _ => {}
1240 }
1241
1242 (piece_from & self.our(role)).for_each(|from| {
1243 moves.push(Move::Normal {
1244 role,
1245 from,
1246 capture: self.board().role_at(to),
1247 to,
1248 promotion: None,
1249 });
1250 });
1251 }
1252 } else {
1253 evasions(self, king, checkers, &mut moves);
1254 filter_san_candidates(role, to, &mut moves);
1255 }
1256
1257 let has_ep = role == Role::Pawn
1258 && self.ep_square.map(Square::from) == Some(to)
1259 && gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
1260
1261 let blockers = slider_blockers(self.board(), self.them(), king);
1262 if blockers.any() || has_ep {
1263 moves.retain(|m| is_safe(self, king, *m, blockers));
1264 }
1265
1266 moves
1267 }
1268
1269 fn has_insufficient_material(&self, color: Color) -> bool {
1270 if (self.board.by_color(color) & (self.board.pawns() | self.board.rooks_and_queens())).any()
1272 {
1273 return false;
1274 }
1275
1276 if (self.board.by_color(color) & self.board.knights()).any() {
1281 return self.board.by_color(color).count() <= 2
1282 && (self.board.by_color(!color) & !self.board.kings() & !self.board().queens())
1283 .is_empty();
1284 }
1285
1286 if (self.board.by_color(color) & self.board.bishops()).any() {
1292 let same_color = (self.board().bishops() & Bitboard::DARK_SQUARES).is_empty()
1293 || (self.board().bishops() & Bitboard::LIGHT_SQUARES).is_empty();
1294 return same_color
1295 && self.board().knights().is_empty()
1296 && self.board().pawns().is_empty();
1297 }
1298
1299 true
1300 }
1301
1302 fn is_variant_end(&self) -> bool {
1304 false
1305 }
1306
1307 fn variant_outcome(&self) -> Outcome {
1309 Outcome::Unknown
1310 }
1311
1312 fn update_zobrist_hash<V: ZobristValue>(
1313 &self,
1314 current: V,
1315 m: Move,
1316 _mode: EnPassantMode,
1317 ) -> Option<V> {
1318 do_update_zobrist_hash(current, m, self.turn, &self.castles, self.ep_square)
1319 }
1320}
1321
1322#[cfg(feature = "variant")]
1323pub(crate) mod variant {
1324 use core::{cmp::min, ops::Not};
1325
1326 use super::*;
1327
1328 enum KingTag {}
1329
1330 impl Stepper for KingTag {
1331 const ROLE: Role = Role::King;
1332 fn attacks(from: Square) -> Bitboard {
1333 attacks::king_attacks(from)
1334 }
1335 }
1336
1337 #[derive(Clone, Debug)]
1339 pub struct Atomic {
1340 board: Board,
1341 turn: Color,
1342 castles: Castles,
1343 ep_square: Option<EnPassant>,
1344 halfmoves: u32,
1345 fullmoves: NonZeroU32,
1346 }
1347
1348 impl Atomic {
1349 pub const fn new() -> Atomic {
1350 Atomic {
1351 board: Board::new(),
1352 turn: White,
1353 castles: Castles::new(),
1354 ep_square: None,
1355 halfmoves: 0,
1356 fullmoves: NonZeroU32::MIN,
1357 }
1358 }
1359 }
1360
1361 impl Default for Atomic {
1362 fn default() -> Atomic {
1363 Atomic::new()
1364 }
1365 }
1366
1367 impl Hash for Atomic {
1368 fn hash<H: Hasher>(&self, state: &mut H) {
1369 self.board.hash(state);
1370 self.turn.hash(state);
1371 self.castles.castling_rights().hash(state);
1372 }
1373 }
1374
1375 impl PartialEq for Atomic {
1376 fn eq(&self, other: &Self) -> bool {
1377 self.board == other.board
1378 && self.turn == other.turn
1379 && self.castles.castling_rights() == other.castles.castling_rights()
1380 && self.legal_ep_square() == other.legal_ep_square()
1381 }
1382 }
1383
1384 impl Eq for Atomic {}
1385
1386 impl FromSetup for Atomic {
1387 fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Atomic, PositionError<Atomic>> {
1388 let mut errors = PositionErrorKinds::empty();
1389
1390 let castles = match Castles::from_setup(&setup, mode) {
1391 Ok(castles) => castles,
1392 Err(castles) => {
1393 errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
1394 castles
1395 }
1396 };
1397
1398 let ep_square = match EnPassant::from_setup(&setup) {
1399 Ok(ep_square) => ep_square,
1400 Err(()) => {
1401 errors |= PositionErrorKinds::INVALID_EP_SQUARE;
1402 None
1403 }
1404 };
1405
1406 let pos = Atomic {
1407 board: setup.board,
1408 turn: setup.turn,
1409 castles,
1410 ep_square,
1411 halfmoves: setup.halfmoves,
1412 fullmoves: setup.fullmoves,
1413 };
1414
1415 errors |= validate(&pos, ep_square);
1416
1417 if ep_square.is_none() {
1418 errors.remove(PositionErrorKinds::IMPOSSIBLE_CHECK);
1422 }
1423
1424 if (pos.them() & pos.board().kings()).any() {
1425 errors.remove(PositionErrorKinds::MISSING_KING);
1427 }
1428
1429 PositionError { pos, errors }.strict()
1430 }
1431 }
1432
1433 #[cfg(feature = "arbitrary")]
1434 impl arbitrary::Arbitrary<'_> for Atomic {
1435 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
1436 arbitrary_position(u.arbitrary_iter()?)
1437 }
1438
1439 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1440 arbitrary_position(u.arbitrary_take_rest_iter()?)
1441 }
1442 }
1443
1444 impl Position for Atomic {
1445 fn board(&self) -> &Board {
1446 &self.board
1447 }
1448
1449 fn promoted(&self) -> Bitboard {
1450 Bitboard::EMPTY
1451 }
1452
1453 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
1454 None
1455 }
1456
1457 fn turn(&self) -> Color {
1458 self.turn
1459 }
1460
1461 fn castles(&self) -> &Castles {
1462 &self.castles
1463 }
1464
1465 fn maybe_ep_square(&self) -> Option<Square> {
1466 self.ep_square.map(EnPassant::square)
1467 }
1468
1469 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
1470 None
1471 }
1472
1473 fn halfmoves(&self) -> u32 {
1474 self.halfmoves
1475 }
1476
1477 fn fullmoves(&self) -> NonZeroU32 {
1478 self.fullmoves
1479 }
1480
1481 fn play_unchecked(&mut self, m: Move) {
1482 do_move(
1483 &mut self.board,
1484 &mut Bitboard(0),
1485 &mut self.turn,
1486 &mut self.castles,
1487 &mut self.ep_square,
1488 &mut self.halfmoves,
1489 &mut self.fullmoves,
1490 m,
1491 );
1492
1493 match m {
1494 Move::Normal {
1495 capture: Some(_),
1496 to,
1497 ..
1498 }
1499 | Move::EnPassant { to, .. } => {
1500 self.board.discard_piece_at(to);
1501
1502 let explosion_radius =
1503 attacks::king_attacks(to) & self.board().occupied() & !self.board.pawns();
1504
1505 if (explosion_radius & self.board().kings() & self.us()).any() {
1506 self.castles.discard_color(self.turn());
1507 }
1508
1509 for explosion in explosion_radius {
1510 self.board.discard_piece_at(explosion);
1511 self.castles.discard_rook(explosion);
1512 }
1513 }
1514 _ => (),
1515 }
1516 }
1517
1518 fn legal_moves(&self) -> MoveList {
1519 let mut moves = MoveList::new();
1520
1521 gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
1522 gen_non_king(self, !self.us(), &mut moves);
1523 KingTag::gen_moves(self, !self.board().occupied(), &mut moves);
1524 if let Some(king) = self.board().king_of(self.turn()) {
1525 gen_castling_moves(
1526 self,
1527 &self.castles,
1528 king,
1529 CastlingSide::KingSide,
1530 &mut moves,
1531 );
1532 gen_castling_moves(
1533 self,
1534 &self.castles,
1535 king,
1536 CastlingSide::QueenSide,
1537 &mut moves,
1538 );
1539 }
1540
1541 moves.retain(|m| {
1544 let mut after = self.clone();
1545 after.play_unchecked(*m);
1546 if let Some(our_king) = after.board().king_of(self.turn()) {
1547 (after.board.kings() & after.board().by_color(!self.turn())).is_empty()
1548 || after
1549 .king_attackers(our_king, !self.turn(), after.board.occupied())
1550 .is_empty()
1551 } else {
1552 false
1553 }
1554 });
1555
1556 moves
1557 }
1558
1559 fn king_attackers(&self, square: Square, attacker: Color, occupied: Bitboard) -> Bitboard {
1560 let attacker_kings = self.board().kings() & self.board().by_color(attacker);
1561 if attacker_kings.is_empty() || (attacks::king_attacks(square) & attacker_kings).any() {
1562 Bitboard(0)
1563 } else {
1564 self.board().attacks_to(square, attacker, occupied)
1565 }
1566 }
1567
1568 fn is_variant_end(&self) -> bool {
1569 self.variant_outcome().is_known()
1570 }
1571
1572 fn has_insufficient_material(&self, color: Color) -> bool {
1573 if (self.board.by_color(!color) & self.board.kings()).is_empty() {
1576 return false;
1577 }
1578
1579 if (self.board.by_color(color) & !self.board.kings()).is_empty() {
1581 return true;
1582 }
1583
1584 if (self.board.by_color(!color) & !self.board.kings()).any() {
1587 if self.board().occupied() == self.board().kings() | self.board().bishops() {
1589 if (self.board().bishops() & self.board().white() & Bitboard::DARK_SQUARES)
1590 .is_empty()
1591 {
1592 return (self.board().bishops()
1593 & self.board().black()
1594 & Bitboard::LIGHT_SQUARES)
1595 .is_empty();
1596 }
1597 if (self.board().bishops() & self.board().white() & Bitboard::LIGHT_SQUARES)
1598 .is_empty()
1599 {
1600 return (self.board().bishops()
1601 & self.board().black()
1602 & Bitboard::DARK_SQUARES)
1603 .is_empty();
1604 }
1605 }
1606
1607 return false;
1608 }
1609
1610 if self.board().queens().any() || self.board.pawns().any() {
1612 return false;
1613 }
1614
1615 if (self.board().knights() | self.board().bishops() | self.board().rooks()).count() == 1
1617 {
1618 return true;
1619 }
1620
1621 if self.board().occupied() == self.board().kings() | self.board().knights() {
1623 return self.board().knights().count() <= 2;
1624 }
1625
1626 false
1627 }
1628
1629 fn variant_outcome(&self) -> Outcome {
1630 for color in Color::ALL {
1631 if (self.board().by_color(color) & self.board().kings()).is_empty() {
1632 return Outcome::Known(KnownOutcome::Decisive { winner: !color });
1633 }
1634 }
1635 Outcome::Unknown
1636 }
1637
1638 fn update_zobrist_hash<V: ZobristValue>(
1639 &self,
1640 mut current: V,
1641 m: Move,
1642 _mode: EnPassantMode,
1643 ) -> Option<V> {
1644 if self.ep_square.is_some() {
1645 return None;
1646 }
1647
1648 match m {
1649 Move::Normal {
1650 role,
1651 from,
1652 capture: None, to,
1654 promotion,
1655 } if (role != Role::Pawn || Square::abs_diff(from, to) != 16)
1656 && role != Role::King
1657 && !self.castles.castling_rights().contains(from)
1658 && !self.castles.castling_rights().contains(to) =>
1659 {
1660 current ^= V::zobrist_for_white_turn();
1661 current ^= V::zobrist_for_piece(from, role.of(self.turn));
1662 current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(self.turn));
1663 Some(current)
1664 }
1665 _ => None,
1666 }
1667 }
1668 }
1669
1670 #[derive(Clone, Debug)]
1673 pub struct Antichess {
1674 board: Board,
1675 turn: Color,
1676 castles: Castles,
1677 ep_square: Option<EnPassant>,
1678 halfmoves: u32,
1679 fullmoves: NonZeroU32,
1680 }
1681
1682 impl Antichess {
1683 pub const fn new() -> Antichess {
1684 Antichess {
1685 board: Board::new(),
1686 turn: White,
1687 castles: Castles::empty(CastlingMode::Standard),
1688 ep_square: None,
1689 halfmoves: 0,
1690 fullmoves: NonZeroU32::MIN,
1691 }
1692 }
1693 }
1694
1695 impl Default for Antichess {
1696 fn default() -> Antichess {
1697 Antichess::new()
1698 }
1699 }
1700
1701 impl Hash for Antichess {
1702 fn hash<H: Hasher>(&self, state: &mut H) {
1703 self.board.hash(state);
1704 self.turn.hash(state);
1705 self.castles.castling_rights().hash(state);
1706 }
1707 }
1708
1709 impl PartialEq for Antichess {
1710 fn eq(&self, other: &Self) -> bool {
1711 self.board == other.board
1712 && self.turn == other.turn
1713 && self.castles.castling_rights() == other.castles.castling_rights()
1714 && self.legal_ep_square() == other.legal_ep_square()
1715 }
1716 }
1717
1718 impl Eq for Antichess {}
1719
1720 impl FromSetup for Antichess {
1721 fn from_setup(
1722 setup: Setup,
1723 mode: CastlingMode,
1724 ) -> Result<Antichess, PositionError<Antichess>> {
1725 let mut errors = PositionErrorKinds::empty();
1726
1727 let ep_square = match EnPassant::from_setup(&setup) {
1728 Ok(ep_square) => ep_square,
1729 Err(()) => {
1730 errors |= PositionErrorKinds::INVALID_EP_SQUARE;
1731 None
1732 }
1733 };
1734
1735 let pos = Antichess {
1736 board: setup.board,
1737 turn: setup.turn,
1738 castles: Castles::empty(mode),
1739 ep_square,
1740 halfmoves: setup.halfmoves,
1741 fullmoves: setup.fullmoves,
1742 };
1743
1744 if setup.castling_rights.any() {
1745 errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
1746 }
1747
1748 errors |= validate(&pos, ep_square)
1749 - PositionErrorKinds::MISSING_KING
1750 - PositionErrorKinds::TOO_MANY_KINGS
1751 - PositionErrorKinds::OPPOSITE_CHECK
1752 - PositionErrorKinds::IMPOSSIBLE_CHECK;
1753
1754 PositionError { pos, errors }.strict()
1755 }
1756 }
1757
1758 #[cfg(feature = "arbitrary")]
1759 impl arbitrary::Arbitrary<'_> for Antichess {
1760 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
1761 arbitrary_position(u.arbitrary_iter()?)
1762 }
1763
1764 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1765 arbitrary_position(u.arbitrary_take_rest_iter()?)
1766 }
1767 }
1768
1769 impl Position for Antichess {
1770 fn board(&self) -> &Board {
1771 &self.board
1772 }
1773
1774 fn promoted(&self) -> Bitboard {
1775 Bitboard::EMPTY
1776 }
1777
1778 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
1779 None
1780 }
1781
1782 fn turn(&self) -> Color {
1783 self.turn
1784 }
1785
1786 fn castles(&self) -> &Castles {
1787 &self.castles
1788 }
1789
1790 fn maybe_ep_square(&self) -> Option<Square> {
1791 self.ep_square.map(EnPassant::square)
1792 }
1793
1794 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
1795 None
1796 }
1797
1798 fn halfmoves(&self) -> u32 {
1799 self.halfmoves
1800 }
1801
1802 fn fullmoves(&self) -> NonZeroU32 {
1803 self.fullmoves
1804 }
1805
1806 fn play_unchecked(&mut self, m: Move) {
1807 do_move(
1808 &mut self.board,
1809 &mut Bitboard(0),
1810 &mut self.turn,
1811 &mut self.castles,
1812 &mut self.ep_square,
1813 &mut self.halfmoves,
1814 &mut self.fullmoves,
1815 m,
1816 );
1817 }
1818
1819 fn en_passant_moves(&self) -> MoveList {
1820 let mut moves = MoveList::new();
1821 gen_en_passant(self.board(), self.turn, self.ep_square, &mut moves);
1822 moves
1823 }
1824
1825 fn capture_moves(&self) -> MoveList {
1826 let mut moves = self.en_passant_moves();
1827 let them = self.them();
1828 gen_non_king(self, them, &mut moves);
1829 add_king_promotions(&mut moves);
1830 KingTag::gen_moves(self, them, &mut moves);
1831 moves
1832 }
1833
1834 fn legal_moves(&self) -> MoveList {
1835 let mut moves = self.capture_moves();
1836
1837 if moves.is_empty() {
1838 gen_non_king(self, !self.board().occupied(), &mut moves);
1840 add_king_promotions(&mut moves);
1841 KingTag::gen_moves(self, !self.board().occupied(), &mut moves);
1842 }
1843
1844 moves
1845 }
1846
1847 fn king_attackers(
1848 &self,
1849 _square: Square,
1850 _attacker: Color,
1851 _occupied: Bitboard,
1852 ) -> Bitboard {
1853 Bitboard(0)
1854 }
1855
1856 fn is_variant_end(&self) -> bool {
1857 self.board().white().is_empty() || self.board().black().is_empty()
1858 }
1859
1860 fn has_insufficient_material(&self, color: Color) -> bool {
1861 if self.board.by_color(color).is_empty() {
1862 false
1863 } else if self.board.by_color(!color).is_empty() {
1864 true
1865 } else if self.board.occupied() == self.board.bishops() {
1866 let we_some_on_light = (self.board.by_color(color) & Bitboard::LIGHT_SQUARES).any();
1869 let we_some_on_dark = (self.board.by_color(color) & Bitboard::DARK_SQUARES).any();
1870 let they_all_on_dark =
1871 (self.board.by_color(!color) & Bitboard::LIGHT_SQUARES).is_empty();
1872 let they_all_on_light =
1873 (self.board.by_color(!color) & Bitboard::DARK_SQUARES).is_empty();
1874 (we_some_on_light && they_all_on_dark) || (we_some_on_dark && they_all_on_light)
1875 } else if self.board.occupied() == self.board.knights() {
1876 match (
1877 self.board.white().single_square(),
1878 self.board.black().single_square(),
1879 ) {
1880 (Some(white_single_knight), Some(black_single_knight)) => {
1881 self.turn
1882 == color
1883 ^ white_single_knight.is_light()
1884 ^ black_single_knight.is_dark()
1885 }
1886 _ => false,
1887 }
1888 } else {
1889 false
1890 }
1891 }
1892
1893 fn variant_outcome(&self) -> Outcome {
1894 if self.us().is_empty() || self.is_stalemate() {
1895 Outcome::Known(KnownOutcome::Decisive {
1896 winner: self.turn(),
1897 })
1898 } else {
1899 Outcome::Unknown
1900 }
1901 }
1902
1903 fn update_zobrist_hash<V: ZobristValue>(
1904 &self,
1905 current: V,
1906 m: Move,
1907 _mode: EnPassantMode,
1908 ) -> Option<V> {
1909 do_update_zobrist_hash(current, m, self.turn, &self.castles, self.ep_square)
1910 }
1911 }
1912
1913 #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
1915 pub struct KingOfTheHill {
1916 chess: Chess,
1917 }
1918
1919 impl KingOfTheHill {
1920 pub const fn new() -> KingOfTheHill {
1921 KingOfTheHill {
1922 chess: Chess::new(),
1923 }
1924 }
1925 }
1926
1927 impl FromSetup for KingOfTheHill {
1928 fn from_setup(
1929 setup: Setup,
1930 mode: CastlingMode,
1931 ) -> Result<KingOfTheHill, PositionError<KingOfTheHill>> {
1932 let (chess, _, _, errors) = Chess::from_setup_unchecked(setup, mode);
1933 PositionError {
1934 errors,
1935 pos: KingOfTheHill { chess },
1936 }
1937 .strict()
1938 }
1939 }
1940
1941 #[cfg(feature = "arbitrary")]
1942 impl arbitrary::Arbitrary<'_> for KingOfTheHill {
1943 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
1944 arbitrary_position(u.arbitrary_iter()?)
1945 }
1946
1947 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1948 arbitrary_position(u.arbitrary_take_rest_iter()?)
1949 }
1950 }
1951
1952 impl Position for KingOfTheHill {
1953 fn board(&self) -> &Board {
1954 self.chess.board()
1955 }
1956
1957 fn promoted(&self) -> Bitboard {
1958 Bitboard::EMPTY
1959 }
1960
1961 fn castles(&self) -> &Castles {
1962 self.chess.castles()
1963 }
1964
1965 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
1966 None
1967 }
1968
1969 fn turn(&self) -> Color {
1970 self.chess.turn()
1971 }
1972
1973 fn maybe_ep_square(&self) -> Option<Square> {
1974 self.chess.maybe_ep_square()
1975 }
1976
1977 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
1978 None
1979 }
1980
1981 fn halfmoves(&self) -> u32 {
1982 self.chess.halfmoves()
1983 }
1984
1985 fn fullmoves(&self) -> NonZeroU32 {
1986 self.chess.fullmoves()
1987 }
1988
1989 fn play_unchecked(&mut self, m: Move) {
1990 self.chess.play_unchecked(m);
1991 }
1992
1993 fn legal_moves(&self) -> MoveList {
1994 if self.is_variant_end() {
1995 MoveList::new()
1996 } else {
1997 self.chess.legal_moves()
1998 }
1999 }
2000
2001 fn castling_moves(&self, side: CastlingSide) -> MoveList {
2002 if self.is_variant_end() {
2003 MoveList::new()
2004 } else {
2005 self.chess.castling_moves(side)
2006 }
2007 }
2008
2009 fn en_passant_moves(&self) -> MoveList {
2010 if self.is_variant_end() {
2011 MoveList::new()
2012 } else {
2013 self.chess.en_passant_moves()
2014 }
2015 }
2016
2017 fn san_candidates(&self, role: Role, to: Square) -> MoveList {
2018 if self.is_variant_end() {
2019 MoveList::new()
2020 } else {
2021 self.chess.san_candidates(role, to)
2022 }
2023 }
2024
2025 fn has_insufficient_material(&self, _color: Color) -> bool {
2026 false
2028 }
2029
2030 fn is_variant_end(&self) -> bool {
2031 (self.chess.board().kings() & Bitboard::CENTER).any()
2032 }
2033
2034 fn variant_outcome(&self) -> Outcome {
2035 for color in Color::ALL {
2036 if (self.board().by_color(color) & self.board().kings() & Bitboard::CENTER).any() {
2037 return Outcome::Known(KnownOutcome::Decisive { winner: color });
2038 }
2039 }
2040 Outcome::Unknown
2041 }
2042
2043 fn update_zobrist_hash<V: ZobristValue>(
2044 &self,
2045 current: V,
2046 m: Move,
2047 mode: EnPassantMode,
2048 ) -> Option<V>
2049 where
2050 Self: Sized,
2051 {
2052 self.chess.update_zobrist_hash(current, m, mode)
2053 }
2054 }
2055
2056 #[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
2058 pub struct ThreeCheck {
2059 chess: Chess,
2060 remaining_checks: ByColor<RemainingChecks>,
2061 }
2062
2063 impl ThreeCheck {
2064 pub const fn new() -> ThreeCheck {
2065 ThreeCheck {
2066 chess: Chess::new(),
2067 remaining_checks: ByColor {
2068 black: RemainingChecks::new(3),
2069 white: RemainingChecks::new(3),
2070 },
2071 }
2072 }
2073 }
2074
2075 impl FromSetup for ThreeCheck {
2076 fn from_setup(
2077 setup: Setup,
2078 mode: CastlingMode,
2079 ) -> Result<ThreeCheck, PositionError<ThreeCheck>> {
2080 let (chess, _, remaining_checks, mut errors) = Chess::from_setup_unchecked(setup, mode);
2081
2082 let remaining_checks = remaining_checks.unwrap_or_default();
2083 if remaining_checks.iter().all(|remaining| remaining.is_zero()) {
2084 errors |= PositionErrorKinds::VARIANT;
2085 }
2086
2087 PositionError {
2088 errors,
2089 pos: ThreeCheck {
2090 chess,
2091 remaining_checks,
2092 },
2093 }
2094 .strict()
2095 }
2096 }
2097
2098 #[cfg(feature = "arbitrary")]
2099 impl arbitrary::Arbitrary<'_> for ThreeCheck {
2100 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
2101 arbitrary_position(u.arbitrary_iter()?)
2102 }
2103
2104 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2105 arbitrary_position(u.arbitrary_take_rest_iter()?)
2106 }
2107 }
2108
2109 impl Position for ThreeCheck {
2110 fn board(&self) -> &Board {
2111 self.chess.board()
2112 }
2113
2114 fn promoted(&self) -> Bitboard {
2115 Bitboard::EMPTY
2116 }
2117
2118 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
2119 None
2120 }
2121
2122 fn turn(&self) -> Color {
2123 self.chess.turn()
2124 }
2125
2126 fn castles(&self) -> &Castles {
2127 self.chess.castles()
2128 }
2129
2130 fn maybe_ep_square(&self) -> Option<Square> {
2131 self.chess.maybe_ep_square()
2132 }
2133
2134 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
2135 Some(&self.remaining_checks)
2136 }
2137
2138 fn halfmoves(&self) -> u32 {
2139 self.chess.halfmoves()
2140 }
2141
2142 fn fullmoves(&self) -> NonZeroU32 {
2143 self.chess.fullmoves
2144 }
2145
2146 fn play_unchecked(&mut self, m: Move) {
2147 let turn = self.chess.turn();
2148 self.chess.play_unchecked(m);
2149 if self.is_check() {
2150 let checks = self.remaining_checks.get_mut(turn);
2151 *checks = checks.saturating_sub(1);
2152 }
2153 }
2154
2155 fn legal_moves(&self) -> MoveList {
2156 if self.is_variant_end() {
2157 MoveList::new()
2158 } else {
2159 self.chess.legal_moves()
2160 }
2161 }
2162
2163 fn castling_moves(&self, side: CastlingSide) -> MoveList {
2164 if self.is_variant_end() {
2165 MoveList::new()
2166 } else {
2167 self.chess.castling_moves(side)
2168 }
2169 }
2170
2171 fn en_passant_moves(&self) -> MoveList {
2172 if self.is_variant_end() {
2173 MoveList::new()
2174 } else {
2175 self.chess.en_passant_moves()
2176 }
2177 }
2178
2179 fn san_candidates(&self, role: Role, to: Square) -> MoveList {
2180 if self.is_variant_end() {
2181 MoveList::new()
2182 } else {
2183 self.chess.san_candidates(role, to)
2184 }
2185 }
2186
2187 fn has_insufficient_material(&self, color: Color) -> bool {
2188 (self.board().by_color(color) & !self.board().kings()).is_empty()
2190 }
2191
2192 fn is_irreversible(&self, m: Move) -> bool {
2193 self.chess.is_irreversible(m) || self.chess.gives_check(m)
2194 }
2195
2196 fn is_variant_end(&self) -> bool {
2197 self.remaining_checks
2198 .iter()
2199 .any(|remaining| remaining.is_zero())
2200 }
2201
2202 fn variant_outcome(&self) -> Outcome {
2203 self.remaining_checks
2204 .find(|remaining| remaining.is_zero())
2205 .map_or(Outcome::Unknown, |winner| {
2206 Outcome::Known(KnownOutcome::Decisive { winner })
2207 })
2208 }
2209 }
2210
2211 #[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
2213 pub struct Crazyhouse {
2214 chess: Chess,
2215 promoted: Bitboard,
2216 pockets: ByColor<ByRole<u8>>,
2217 }
2218
2219 impl Crazyhouse {
2220 pub const fn new() -> Crazyhouse {
2221 Crazyhouse {
2222 chess: Chess::new(),
2223 promoted: Bitboard::EMPTY,
2224 pockets: ByColor {
2225 black: ByRole {
2226 pawn: 0,
2227 knight: 0,
2228 bishop: 0,
2229 rook: 0,
2230 queen: 0,
2231 king: 0,
2232 },
2233 white: ByRole {
2234 pawn: 0,
2235 knight: 0,
2236 bishop: 0,
2237 rook: 0,
2238 queen: 0,
2239 king: 0,
2240 },
2241 },
2242 }
2243 }
2244
2245 fn our_pocket(&self) -> &ByRole<u8> {
2246 self.pockets.get(self.turn())
2247 }
2248
2249 fn our_pocket_mut(&mut self) -> &mut ByRole<u8> {
2250 let turn = self.turn();
2251 self.pockets.get_mut(turn)
2252 }
2253
2254 fn legal_put_squares(&self) -> Bitboard {
2255 let checkers = self.checkers();
2256
2257 if checkers.is_empty() {
2258 !self.board().occupied()
2259 } else if let Some(checker) = checkers.single_square() {
2260 let king = self
2261 .board()
2262 .king_of(self.turn())
2263 .expect("king in crazyhouse");
2264 attacks::between(checker, king)
2265 } else {
2266 Bitboard(0)
2267 }
2268 }
2269 }
2270
2271 impl FromSetup for Crazyhouse {
2272 fn from_setup(
2273 setup: Setup,
2274 mode: CastlingMode,
2275 ) -> Result<Crazyhouse, PositionError<Crazyhouse>> {
2276 let promoted = setup.promoted
2277 & setup.board.occupied()
2278 & !setup.board.pawns()
2279 & !setup.board.kings();
2280 let (chess, pockets, _, mut errors) = Chess::from_setup_unchecked(setup, mode);
2281 let pockets = pockets.unwrap_or_default();
2282
2283 if pockets.white.king > 0 || pockets.black.king > 0 {
2284 errors |= PositionErrorKinds::TOO_MANY_KINGS;
2285 }
2286
2287 if pockets.count() + chess.board().occupied().count() > 64 {
2288 errors |= PositionErrorKinds::VARIANT;
2289 }
2290
2291 errors -= PositionErrorKinds::TOO_MUCH_MATERIAL;
2292
2293 if promoted.count()
2294 + chess.board().pawns().count()
2295 + usize::from(pockets.white.pawn)
2296 + usize::from(pockets.black.pawn)
2297 > 16
2298 || (chess.board().knights() & !promoted).count()
2299 + usize::from(pockets.white.knight)
2300 + usize::from(pockets.black.knight)
2301 > 4
2302 || (chess.board().bishops() & !promoted).count()
2303 + usize::from(pockets.white.bishop)
2304 + usize::from(pockets.black.bishop)
2305 > 4
2306 || (chess.board().rooks() & !promoted).count()
2307 + usize::from(pockets.white.rook)
2308 + usize::from(pockets.black.rook)
2309 > 4
2310 || (chess.board().queens() & !promoted).count()
2311 + usize::from(pockets.white.queen)
2312 + usize::from(pockets.black.queen)
2313 > 2
2314 {
2315 errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
2316 }
2317
2318 PositionError {
2319 errors,
2320 pos: Crazyhouse {
2321 chess,
2322 promoted,
2323 pockets,
2324 },
2325 }
2326 .strict()
2327 }
2328 }
2329
2330 #[cfg(feature = "arbitrary")]
2331 impl arbitrary::Arbitrary<'_> for Crazyhouse {
2332 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
2333 arbitrary_position(u.arbitrary_iter()?)
2334 }
2335
2336 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2337 arbitrary_position(u.arbitrary_take_rest_iter()?)
2338 }
2339 }
2340
2341 impl Position for Crazyhouse {
2342 fn board(&self) -> &Board {
2343 self.chess.board()
2344 }
2345
2346 fn promoted(&self) -> Bitboard {
2347 self.promoted
2348 }
2349
2350 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
2351 Some(&self.pockets)
2352 }
2353
2354 fn turn(&self) -> Color {
2355 self.chess.turn()
2356 }
2357
2358 fn castles(&self) -> &Castles {
2359 self.chess.castles()
2360 }
2361
2362 fn maybe_ep_square(&self) -> Option<Square> {
2363 self.chess.maybe_ep_square()
2364 }
2365
2366 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
2367 None
2368 }
2369
2370 fn halfmoves(&self) -> u32 {
2371 self.chess.halfmoves()
2372 }
2373
2374 fn fullmoves(&self) -> NonZeroU32 {
2375 self.chess.fullmoves()
2376 }
2377
2378 fn play_unchecked(&mut self, m: Move) {
2379 match m {
2380 Move::Normal {
2381 capture: Some(capture),
2382 to,
2383 ..
2384 } => {
2385 let capture = if self.promoted.contains(to) {
2386 Role::Pawn
2387 } else {
2388 capture
2389 };
2390
2391 *self.our_pocket_mut().get_mut(capture) += 1;
2392 }
2393 Move::EnPassant { .. } => {
2394 self.our_pocket_mut().pawn += 1;
2395 }
2396 Move::Put { role, .. } => {
2397 *self.our_pocket_mut().get_mut(role) -= 1;
2398 }
2399 _ => {}
2400 }
2401
2402 do_move(
2403 &mut self.chess.board,
2404 &mut self.promoted,
2405 &mut self.chess.turn,
2406 &mut self.chess.castles,
2407 &mut self.chess.ep_square,
2408 &mut self.chess.halfmoves,
2409 &mut self.chess.fullmoves,
2410 m,
2411 );
2412 }
2413
2414 fn legal_moves(&self) -> MoveList {
2415 let mut moves = self.chess.legal_moves();
2416
2417 let pocket = self.our_pocket();
2418 let targets = self.legal_put_squares();
2419
2420 for to in targets {
2421 for role in [Role::Knight, Role::Bishop, Role::Rook, Role::Queen] {
2422 if *pocket.get(role) > 0 {
2423 moves.push(Move::Put { role, to });
2424 }
2425 }
2426 }
2427
2428 if pocket.pawn > 0 {
2429 for to in targets & !Bitboard::BACKRANKS {
2430 moves.push(Move::Put {
2431 role: Role::Pawn,
2432 to,
2433 });
2434 }
2435 }
2436
2437 moves
2438 }
2439
2440 fn castling_moves(&self, side: CastlingSide) -> MoveList {
2441 self.chess.castling_moves(side)
2442 }
2443
2444 fn en_passant_moves(&self) -> MoveList {
2445 self.chess.en_passant_moves()
2446 }
2447
2448 fn san_candidates(&self, role: Role, to: Square) -> MoveList {
2449 let mut moves = self.chess.san_candidates(role, to);
2450
2451 if *self.our_pocket().get(role) > 0
2452 && self.legal_put_squares().contains(to)
2453 && (role != Role::Pawn || !Bitboard::BACKRANKS.contains(to))
2454 {
2455 moves.push(Move::Put { role, to });
2456 }
2457
2458 moves
2459 }
2460
2461 fn is_irreversible(&self, m: Move) -> bool {
2462 match m {
2463 Move::Castle { .. } => true,
2464 Move::Normal { role, from, to, .. } => {
2465 self.chess.castles.castling_rights().contains(from)
2466 || self.chess.castles.castling_rights().contains(to)
2467 || (role == Role::King && self.chess.castles.has_color(self.turn()))
2468 }
2469 _ => false,
2470 }
2471 }
2472
2473 fn has_insufficient_material(&self, _color: Color) -> bool {
2474 self.board().occupied().count() + self.pockets.count() <= 3
2478 && self.promoted.is_empty()
2479 && self.board().pawns().is_empty()
2480 && self.board().rooks_and_queens().is_empty()
2481 && self.pockets.white.pawn == 0
2482 && self.pockets.black.pawn == 0
2483 && self.pockets.white.rook == 0
2484 && self.pockets.black.rook == 0
2485 && self.pockets.white.queen == 0
2486 && self.pockets.black.queen == 0
2487 }
2488
2489 fn is_variant_end(&self) -> bool {
2490 false
2491 }
2492 fn variant_outcome(&self) -> Outcome {
2493 Outcome::Unknown
2494 }
2495
2496 fn update_zobrist_hash<V: ZobristValue>(
2497 &self,
2498 mut current: V,
2499 m: Move,
2500 _mode: EnPassantMode,
2501 ) -> Option<V> {
2502 if self.chess.ep_square.is_some() {
2503 return None;
2504 }
2505
2506 match m {
2507 Move::Normal {
2508 role,
2509 from,
2510 capture,
2511 to,
2512 promotion,
2513 } if (role != Role::Pawn || Square::abs_diff(from, to) != 16)
2514 && role != Role::King
2515 && !self.castles().castling_rights().contains(from)
2516 && !self.castles().castling_rights().contains(to) =>
2517 {
2518 current ^= V::zobrist_for_white_turn();
2519 current ^= V::zobrist_for_piece(from, role.of(self.turn()));
2520 current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(self.turn()));
2521 if let Some(capture) = capture {
2522 current ^= V::zobrist_for_piece(to, capture.of(!self.turn()));
2523 let capture = if self.promoted.contains(to) {
2524 current ^= V::zobrist_for_promoted(to);
2525 Role::Pawn
2526 } else {
2527 capture
2528 };
2529 let pocket_pieces = self.pockets[self.turn()][capture];
2530 current ^= V::zobrist_for_pocket(self.turn(), capture, pocket_pieces);
2531 current ^= V::zobrist_for_pocket(self.turn(), capture, pocket_pieces + 1);
2532 }
2533 if self.promoted.contains(from) {
2534 current ^= V::zobrist_for_promoted(from);
2535 }
2536 if self.promoted.contains(from) || promotion.is_some() {
2537 current ^= V::zobrist_for_promoted(to);
2538 }
2539 Some(current)
2540 }
2541 Move::Put { role, to } => {
2542 current ^= V::zobrist_for_white_turn();
2543 current ^= V::zobrist_for_piece(to, role.of(self.turn()));
2544 let pocket_pieces = self.pockets[self.turn()][role];
2545 current ^= V::zobrist_for_pocket(self.turn(), role, pocket_pieces);
2546 current ^= V::zobrist_for_pocket(self.turn(), role, pocket_pieces - 1);
2547 Some(current)
2548 }
2549 _ => None,
2550 }
2551 }
2552 }
2553
2554 #[derive(Clone, Debug)]
2556 pub struct RacingKings {
2557 board: Board,
2558 turn: Color,
2559 castles: Castles,
2560 halfmoves: u32,
2561 fullmoves: NonZeroU32,
2562 }
2563
2564 impl RacingKings {
2565 pub const fn new() -> RacingKings {
2566 RacingKings {
2567 board: Board::racing_kings(),
2568 turn: White,
2569 castles: Castles::empty(CastlingMode::Standard),
2570 halfmoves: 0,
2571 fullmoves: NonZeroU32::MIN,
2572 }
2573 }
2574 }
2575
2576 impl Default for RacingKings {
2577 fn default() -> RacingKings {
2578 RacingKings::new()
2579 }
2580 }
2581
2582 impl Hash for RacingKings {
2583 fn hash<H: Hasher>(&self, state: &mut H) {
2584 self.board.hash(state);
2585 self.turn.hash(state);
2586 self.castles.castling_rights().hash(state);
2587 }
2588 }
2589
2590 impl PartialEq for RacingKings {
2591 fn eq(&self, other: &Self) -> bool {
2592 self.board == other.board
2593 && self.turn == other.turn
2594 && self.castles.castling_rights() == other.castles.castling_rights()
2595 }
2596 }
2597
2598 impl Eq for RacingKings {}
2599
2600 impl FromSetup for RacingKings {
2601 fn from_setup(
2602 setup: Setup,
2603 mode: CastlingMode,
2604 ) -> Result<RacingKings, PositionError<RacingKings>> {
2605 let mut errors = PositionErrorKinds::empty();
2606
2607 if setup.castling_rights.any() {
2608 errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
2609 }
2610
2611 if setup.board.pawns().any() {
2612 errors |= PositionErrorKinds::VARIANT;
2613 }
2614
2615 for color in Color::ALL {
2616 let us = setup.board.by_color(color);
2617 if (setup.board.knights() & us).count() > 2
2618 || (setup.board.bishops() & us).count() > 2
2619 || (setup.board.rooks() & us).count() > 2
2620 || (setup.board.queens() & us).more_than_one()
2621 {
2622 errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
2623 }
2624 }
2625
2626 if setup.ep_square.is_some() {
2627 errors |= PositionErrorKinds::INVALID_EP_SQUARE;
2628 }
2629
2630 let pos = RacingKings {
2631 board: setup.board,
2632 turn: setup.turn,
2633 castles: Castles::empty(mode),
2634 halfmoves: setup.halfmoves,
2635 fullmoves: setup.fullmoves,
2636 };
2637
2638 if pos.is_check() {
2639 errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
2640 }
2641
2642 if pos.turn().is_black()
2643 && (pos.board().white() & pos.board().kings() & Rank::Eighth).any()
2644 && (pos.board().black() & pos.board().kings() & Rank::Eighth).any()
2645 {
2646 errors |= PositionErrorKinds::VARIANT;
2647 }
2648
2649 errors |= validate(&pos, None);
2650
2651 PositionError { pos, errors }.strict()
2652 }
2653 }
2654
2655 #[cfg(feature = "arbitrary")]
2656 impl arbitrary::Arbitrary<'_> for RacingKings {
2657 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
2658 arbitrary_position(u.arbitrary_iter()?)
2659 }
2660
2661 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2662 arbitrary_position(u.arbitrary_take_rest_iter()?)
2663 }
2664 }
2665
2666 impl Position for RacingKings {
2667 fn board(&self) -> &Board {
2668 &self.board
2669 }
2670
2671 fn promoted(&self) -> Bitboard {
2672 Bitboard::EMPTY
2673 }
2674
2675 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
2676 None
2677 }
2678
2679 fn turn(&self) -> Color {
2680 self.turn
2681 }
2682
2683 fn castles(&self) -> &Castles {
2684 &self.castles
2685 }
2686
2687 fn maybe_ep_square(&self) -> Option<Square> {
2688 None
2689 }
2690
2691 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
2692 None
2693 }
2694
2695 fn halfmoves(&self) -> u32 {
2696 self.halfmoves
2697 }
2698
2699 fn fullmoves(&self) -> NonZeroU32 {
2700 self.fullmoves
2701 }
2702
2703 fn play_unchecked(&mut self, m: Move) {
2704 do_move(
2705 &mut self.board,
2706 &mut Bitboard(0),
2707 &mut self.turn,
2708 &mut self.castles,
2709 &mut None,
2710 &mut self.halfmoves,
2711 &mut self.fullmoves,
2712 m,
2713 );
2714 }
2715
2716 fn legal_moves(&self) -> MoveList {
2717 let mut moves = MoveList::new();
2718
2719 if self.is_variant_end() {
2720 return moves;
2721 }
2722
2723 let target = !self.us();
2725 gen_non_king(self, target, &mut moves);
2726 let king = self
2727 .board()
2728 .king_of(self.turn())
2729 .expect("king in racingkings");
2730 gen_safe_king(self, king, target, &mut moves);
2731
2732 let blockers = slider_blockers(self.board(), self.them(), king);
2733 if blockers.any() {
2734 moves.retain(|m| is_safe(self, king, *m, blockers));
2735 }
2736
2737 moves.retain(|m| {
2740 let mut after = self.clone();
2741 after.play_unchecked(*m);
2742 !after.is_check()
2743 });
2744
2745 moves
2746 }
2747
2748 fn has_insufficient_material(&self, _color: Color) -> bool {
2749 false
2751 }
2752
2753 fn is_variant_end(&self) -> bool {
2754 let in_goal = self.board().kings() & Rank::Eighth;
2755 if in_goal.is_empty() {
2756 return false;
2757 }
2758
2759 if self.turn().is_white() || (in_goal & self.board().black()).any() {
2760 return true;
2761 }
2762
2763 let black_king = self.board().king_of(Black).expect("king in racingkings");
2765 for target in attacks::king_attacks(black_king) & Rank::Eighth & !self.board().black() {
2766 if self
2767 .king_attackers(target, White, self.board().occupied())
2768 .is_empty()
2769 {
2770 return false;
2771 }
2772 }
2773
2774 true
2775 }
2776
2777 fn variant_outcome(&self) -> Outcome {
2778 if self.is_variant_end() {
2779 let in_goal = self.board().kings() & Rank::Eighth;
2780 if (in_goal & self.board().white()).any() && (in_goal & self.board().black()).any()
2781 {
2782 Outcome::Known(KnownOutcome::Draw)
2783 } else if (in_goal & self.board().white()).any() {
2784 Outcome::Known(KnownOutcome::Decisive { winner: White })
2785 } else {
2786 Outcome::Known(KnownOutcome::Decisive { winner: Black })
2787 }
2788 } else {
2789 Outcome::Unknown
2790 }
2791 }
2792
2793 fn update_zobrist_hash<V: ZobristValue>(
2794 &self,
2795 mut current: V,
2796 m: Move,
2797 _mode: EnPassantMode,
2798 ) -> Option<V> {
2799 match m {
2800 Move::Normal {
2801 role,
2802 from,
2803 capture,
2804 to,
2805 promotion,
2806 } => {
2807 current ^= V::zobrist_for_white_turn();
2808 current ^= V::zobrist_for_piece(from, role.of(self.turn));
2809 current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(self.turn));
2810 if let Some(capture) = capture {
2811 current ^= V::zobrist_for_piece(to, capture.of(!self.turn));
2812 }
2813 Some(current)
2814 }
2815 _ => None,
2816 }
2817 }
2818 }
2819
2820 #[derive(Clone, Debug)]
2822 pub struct Horde {
2823 board: Board,
2824 turn: Color,
2825 castles: Castles,
2826 ep_square: Option<EnPassant>,
2827 halfmoves: u32,
2828 fullmoves: NonZeroU32,
2829 }
2830
2831 impl Default for Horde {
2832 fn default() -> Horde {
2833 let mut castles = Castles::default();
2834 castles.discard_color(White);
2835
2836 Horde {
2837 board: Board::horde(),
2838 turn: White,
2839 castles,
2840 ep_square: None,
2841 halfmoves: 0,
2842 fullmoves: NonZeroU32::MIN,
2843 }
2844 }
2845 }
2846
2847 impl Hash for Horde {
2848 fn hash<H: Hasher>(&self, state: &mut H) {
2849 self.board.hash(state);
2850 self.turn.hash(state);
2851 self.castles.castling_rights().hash(state);
2852 }
2853 }
2854
2855 impl PartialEq for Horde {
2856 fn eq(&self, other: &Self) -> bool {
2857 self.board == other.board
2858 && self.turn == other.turn
2859 && self.castles.castling_rights() == other.castles.castling_rights()
2860 && self.legal_ep_square() == other.legal_ep_square()
2861 }
2862 }
2863
2864 impl Eq for Horde {}
2865
2866 impl FromSetup for Horde {
2867 fn from_setup(setup: Setup, mode: CastlingMode) -> Result<Horde, PositionError<Horde>> {
2868 let mut errors = PositionErrorKinds::empty();
2869
2870 let castles = match Castles::from_setup(&setup, mode) {
2871 Ok(castles) => castles,
2872 Err(castles) => {
2873 errors |= PositionErrorKinds::INVALID_CASTLING_RIGHTS;
2874 castles
2875 }
2876 };
2877
2878 let ep_square = match EnPassant::from_setup(&setup) {
2879 Ok(ep_square) => ep_square,
2880 Err(()) => {
2881 errors |= PositionErrorKinds::INVALID_EP_SQUARE;
2882 None
2883 }
2884 };
2885
2886 let pos = Horde {
2887 board: setup.board,
2888 turn: setup.turn,
2889 castles,
2890 ep_square,
2891 halfmoves: setup.halfmoves,
2892 fullmoves: setup.fullmoves,
2893 };
2894
2895 errors |= validate(&pos, ep_square)
2896 - PositionErrorKinds::PAWNS_ON_BACKRANK
2897 - PositionErrorKinds::MISSING_KING
2898 - PositionErrorKinds::TOO_MUCH_MATERIAL;
2899
2900 if pos.board().kings().is_empty() {
2901 errors |= PositionErrorKinds::MISSING_KING;
2902 } else if pos.board().kings().more_than_one() {
2903 errors |= PositionErrorKinds::TOO_MANY_KINGS;
2904 }
2905
2906 for color in Color::ALL {
2907 let us = pos.board.by_color(color);
2908 if (pos.board().kings() & us).any() {
2909 if !is_standard_material(pos.board(), color) {
2910 errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
2911 }
2912 if (pos.board().pawns() & us & Bitboard::BACKRANKS).any() {
2913 errors |= PositionErrorKinds::PAWNS_ON_BACKRANK;
2914 }
2915 } else {
2916 if us.count() > 36 {
2917 errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
2918 }
2919 if (pos.board().pawns() & us & (!color).backrank()).any() {
2920 errors |= PositionErrorKinds::PAWNS_ON_BACKRANK;
2921 }
2922 }
2923 }
2924
2925 PositionError { pos, errors }.strict()
2926 }
2927 }
2928
2929 #[cfg(feature = "arbitrary")]
2930 impl arbitrary::Arbitrary<'_> for Horde {
2931 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
2932 arbitrary_position(u.arbitrary_iter()?)
2933 }
2934
2935 fn arbitrary_take_rest(u: arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
2936 arbitrary_position(u.arbitrary_take_rest_iter()?)
2937 }
2938 }
2939
2940 impl Position for Horde {
2941 fn board(&self) -> &Board {
2942 &self.board
2943 }
2944
2945 fn promoted(&self) -> Bitboard {
2946 Bitboard::EMPTY
2947 }
2948
2949 fn pockets(&self) -> Option<&ByColor<ByRole<u8>>> {
2950 None
2951 }
2952
2953 fn turn(&self) -> Color {
2954 self.turn
2955 }
2956
2957 fn castles(&self) -> &Castles {
2958 &self.castles
2959 }
2960
2961 fn maybe_ep_square(&self) -> Option<Square> {
2962 self.ep_square.map(EnPassant::square)
2963 }
2964
2965 fn remaining_checks(&self) -> Option<&ByColor<RemainingChecks>> {
2966 None
2967 }
2968
2969 fn halfmoves(&self) -> u32 {
2970 self.halfmoves
2971 }
2972
2973 fn fullmoves(&self) -> NonZeroU32 {
2974 self.fullmoves
2975 }
2976
2977 fn play_unchecked(&mut self, m: Move) {
2978 do_move(
2979 &mut self.board,
2980 &mut Bitboard(0),
2981 &mut self.turn,
2982 &mut self.castles,
2983 &mut self.ep_square,
2984 &mut self.halfmoves,
2985 &mut self.fullmoves,
2986 m,
2987 );
2988 }
2989
2990 fn legal_moves(&self) -> MoveList {
2991 let mut moves = MoveList::new();
2992
2993 let king = self.board().king_of(self.turn());
2994 let has_ep = gen_en_passant(self.board(), self.turn(), self.ep_square, &mut moves);
2995
2996 let checkers = self.checkers();
2997 if checkers.is_empty() {
2998 let target = !self.us();
2999 gen_non_king(self, target, &mut moves);
3000 if let Some(king) = king {
3001 gen_safe_king(self, king, target, &mut moves);
3002 gen_castling_moves(
3003 self,
3004 &self.castles,
3005 king,
3006 CastlingSide::KingSide,
3007 &mut moves,
3008 );
3009 gen_castling_moves(
3010 self,
3011 &self.castles,
3012 king,
3013 CastlingSide::QueenSide,
3014 &mut moves,
3015 );
3016 }
3017 } else {
3018 evasions(self, king.expect("king in check"), checkers, &mut moves);
3019 }
3020
3021 if let Some(king) = king {
3022 let blockers = slider_blockers(self.board(), self.them(), king);
3023 if blockers.any() || has_ep {
3024 moves.retain(|m| is_safe(self, king, *m, blockers));
3025 }
3026 }
3027
3028 moves
3029 }
3030
3031 fn is_variant_end(&self) -> bool {
3032 self.board().white().is_empty() || self.board().black().is_empty()
3033 }
3034
3035 #[allow(clippy::nonminimal_bool)] fn has_insufficient_material(&self, color: Color) -> bool {
3037 #[derive(Copy, Clone)]
3038 enum SquareColor {
3039 Dark,
3040 Light,
3041 }
3042 impl From<SquareColor> for Bitboard {
3043 fn from(square_color: SquareColor) -> Bitboard {
3044 match square_color {
3045 SquareColor::Light => Bitboard::LIGHT_SQUARES,
3046 SquareColor::Dark => Bitboard::DARK_SQUARES,
3047 }
3048 }
3049 }
3050 impl Not for SquareColor {
3051 type Output = SquareColor;
3052
3053 fn not(self) -> SquareColor {
3054 match self {
3055 SquareColor::Dark => SquareColor::Light,
3056 SquareColor::Light => SquareColor::Dark,
3057 }
3058 }
3059 }
3060
3061 if (self.board.by_color(color) & self.board.kings()).any() {
3063 return false;
3064 }
3065
3066 let has_bishop_pair = |side: Color| -> bool {
3067 let bishops = self.board.bishops() & self.board.by_color(side);
3068 (bishops & Bitboard::DARK_SQUARES).any()
3069 && (bishops & Bitboard::LIGHT_SQUARES).any()
3070 };
3071
3072 let horde = self.board.material_side(color);
3074 let horde_bishops = |square_color: SquareColor| -> u8 {
3075 (Bitboard::from(square_color) & self.board.by_color(color) & self.board.bishops())
3076 .count() as u8
3077 };
3078 let horde_bishop_color = if horde_bishops(SquareColor::Light) >= 1 {
3079 SquareColor::Light
3080 } else {
3081 SquareColor::Dark
3082 };
3083 let horde_num = horde.pawn
3086 + horde.knight
3087 + horde.rook
3088 + horde.queen
3089 + min(horde_bishops(SquareColor::Dark), 2)
3090 + min(horde_bishops(SquareColor::Light), 2);
3091
3092 let pieces = self.board.material_side(!color);
3093 let pieces_bishops = |square_color: SquareColor| -> u8 {
3094 (Bitboard::from(square_color) & self.board.by_color(!color) & self.board.bishops())
3095 .count() as u8
3096 };
3097 let pieces_num = pieces.count() as u8;
3098 let pieces_of_type_not = |piece: u8| -> u8 { pieces_num - piece };
3099
3100 if horde_num == 0 {
3101 return true;
3102 }
3103 if horde_num >= 4 {
3104 return false;
3106 }
3107 if (horde.pawn >= 1 || horde.queen >= 1) && horde_num >= 2 {
3108 return false;
3111 }
3112 if horde.rook >= 1 && horde_num >= 2 {
3113 if !(horde_num == 2
3120 && horde.rook == 1
3121 && horde.bishop == 1
3122 && pieces_of_type_not(pieces_bishops(horde_bishop_color)) == 1)
3123 {
3124 return false;
3125 }
3126 }
3127
3128 if horde_num == 1 {
3129 if pieces_num == 1 {
3130 return true;
3132 } else if horde.queen == 1 {
3133 return !(pieces.pawn >= 1
3141 || pieces.rook >= 1
3142 || pieces_bishops(SquareColor::Light) >= 2
3143 || pieces_bishops(SquareColor::Dark) >= 2);
3144 } else if horde.pawn == 1 {
3145 let pawn_square = (self.board.pawns() & self.board.by_color(color))
3148 .single_square()
3149 .unwrap();
3150 let mut promote_to_queen = self.clone();
3151 promote_to_queen
3152 .board
3153 .set_piece_at(pawn_square, color.queen());
3154 let mut promote_to_knight = self.clone();
3155 promote_to_knight
3156 .board
3157 .set_piece_at(pawn_square, color.knight());
3158 return promote_to_queen.has_insufficient_material(color)
3159 && promote_to_knight.has_insufficient_material(color);
3160 } else if horde.rook == 1 {
3161 return !(pieces.pawn >= 2
3166 || (pieces.rook >= 1 && pieces.pawn >= 1)
3167 || (pieces.rook >= 1 && pieces.knight >= 1)
3168 || (pieces.pawn >= 1 && pieces.knight >= 1));
3169 } else if horde.bishop == 1 {
3170 return !(
3172 pieces_bishops(!horde_bishop_color) >= 2
3184 || (pieces_bishops(!horde_bishop_color) >= 1 && pieces.pawn >= 1)
3185 || pieces.pawn >= 2
3186 );
3187 } else if horde.knight == 1 {
3188 return !(
3190 pieces_num >= 4
3196 && (pieces.knight >= 2
3197 || pieces.pawn >= 2
3198 || (pieces.rook >= 1 && pieces.knight >= 1)
3199 || (pieces.rook >= 1 && pieces.bishop >= 1)
3200 || (pieces.knight >= 1 && pieces.bishop >= 1)
3201 || (pieces.rook >= 1 && pieces.pawn >= 1)
3202 || (pieces.knight >= 1 && pieces.pawn >= 1)
3203 || (pieces.bishop >= 1 && pieces.pawn >= 1)
3204 || (has_bishop_pair(!color) && pieces.pawn >= 1))
3205 && (pieces_bishops(SquareColor::Dark) < 2
3206 || pieces_of_type_not(pieces_bishops(SquareColor::Dark)) >= 3)
3207 && (pieces_bishops(SquareColor::Light) < 2
3208 || pieces_of_type_not(pieces_bishops(SquareColor::Light)) >= 3)
3209 );
3210 }
3211
3212 } else if horde_num == 2 {
3214 if pieces_num == 1 {
3215 return true;
3217 } else if horde.knight == 2 {
3218 return pieces.pawn + pieces.bishop + pieces.knight < 1;
3222 } else if has_bishop_pair(color) {
3223 return !(
3224 pieces.pawn >= 1 || pieces.bishop >= 1 ||
3227 ( pieces.knight >= 1 && pieces.rook + pieces.queen >= 1 )
3231 );
3232 } else if horde.bishop >= 1 && horde.knight >= 1 {
3233 return !(
3235 pieces.pawn >= 1 || pieces_bishops(!horde_bishop_color) >= 1 ||
3238 pieces_of_type_not( pieces_bishops(horde_bishop_color) ) >=3
3242 );
3243 } else {
3244 return !(
3248 (pieces.pawn >= 1 && pieces_bishops(!horde_bishop_color) >= 1)
3254 || (pieces.pawn >= 1 && pieces.knight >= 1)
3255 || (pieces_bishops(!horde_bishop_color) >= 1 && pieces.knight >= 1)
3256 || (pieces_bishops(!horde_bishop_color) >= 2)
3257 || pieces.knight >= 2
3258 || pieces.pawn >= 2
3259 );
3261 }
3262 } else if horde_num == 3 {
3263 if (horde.knight == 2 && horde.bishop == 1)
3266 || horde.knight == 3
3267 || has_bishop_pair(color)
3268 {
3269 return false;
3270 } else {
3271 return pieces_num == 1;
3276 }
3277 }
3278
3279 true
3280 }
3281
3282 fn variant_outcome(&self) -> Outcome {
3283 if self.board().occupied().is_empty() {
3284 Outcome::Known(KnownOutcome::Draw)
3285 } else if self.board().white().is_empty() {
3286 Outcome::Known(KnownOutcome::Decisive { winner: Black })
3287 } else if self.board().black().is_empty() {
3288 Outcome::Known(KnownOutcome::Decisive { winner: White })
3289 } else {
3290 Outcome::Unknown
3291 }
3292 }
3293
3294 fn update_zobrist_hash<V: ZobristValue>(
3295 &self,
3296 current: V,
3297 m: Move,
3298 _mode: EnPassantMode,
3299 ) -> Option<V> {
3300 do_update_zobrist_hash(current, m, self.turn, &self.castles, self.ep_square)
3301 }
3302 }
3303
3304 fn add_king_promotions(moves: &mut MoveList) {
3305 let mut king_promotions = MoveList::new();
3306
3307 for m in &moves[..] {
3308 if let Move::Normal {
3309 role,
3310 from,
3311 capture,
3312 to,
3313 promotion: Some(Role::Queen),
3314 } = *m
3315 {
3316 king_promotions.push(Move::Normal {
3317 role,
3318 from,
3319 capture,
3320 to,
3321 promotion: Some(Role::King),
3322 });
3323 }
3324 }
3325
3326 moves.extend(king_promotions);
3327 }
3328}
3329
3330#[allow(clippy::too_many_arguments)] fn do_move(
3332 board: &mut Board,
3333 promoted: &mut Bitboard,
3334 turn: &mut Color,
3335 castles: &mut Castles,
3336 ep_square: &mut Option<EnPassant>,
3337 halfmoves: &mut u32,
3338 fullmoves: &mut NonZeroU32,
3339 m: Move,
3340) {
3341 let color = *turn;
3342 ep_square.take();
3343
3344 *halfmoves = if m.is_zeroing() {
3345 0
3346 } else {
3347 halfmoves.saturating_add(1)
3348 };
3349
3350 match m {
3351 Move::Normal {
3352 role,
3353 from,
3354 capture,
3355 to,
3356 promotion,
3357 } => {
3358 if role == Role::Pawn && to - from == 16 && from.rank() == Rank::Second {
3359 *ep_square = from.offset(8).map(EnPassant);
3360 } else if role == Role::Pawn && from - to == 16 && from.rank() == Rank::Seventh {
3361 *ep_square = from.offset(-8).map(EnPassant);
3362 }
3363
3364 if role == Role::King {
3365 castles.discard_color(color);
3366 } else if role == Role::Rook {
3367 castles.discard_rook(from);
3368 }
3369
3370 if capture == Some(Role::Rook) {
3371 castles.discard_rook(to);
3372 }
3373
3374 board.discard_piece_at(from);
3375 board.set_piece_at(to, promotion.map_or(role.of(color), |p| p.of(color)));
3376
3377 let is_promoted = promoted.remove(from) || promotion.is_some();
3378 promoted.set(to, is_promoted);
3379 }
3380 Move::Castle { king, rook } => {
3381 let side = CastlingSide::from_queen_side(rook < king);
3382 board.discard_piece_at(king);
3383 board.discard_piece_at(rook);
3384 board.set_new_piece_at(
3385 Square::from_coords(side.rook_to_file(), rook.rank()),
3386 color.rook(),
3387 );
3388 board.set_new_piece_at(
3389 Square::from_coords(side.king_to_file(), king.rank()),
3390 color.king(),
3391 );
3392 castles.discard_color(color);
3393 }
3394 Move::EnPassant { from, to } => {
3395 board.discard_piece_at(Square::from_coords(to.file(), from.rank())); board.discard_piece_at(from);
3397 board.set_new_piece_at(to, color.pawn());
3398 }
3399 Move::Put { role, to } => {
3400 board.set_new_piece_at(to, Piece { color, role });
3401 }
3402 }
3403
3404 if color.is_black() {
3405 *fullmoves = NonZeroU32::new(fullmoves.get().saturating_add(1)).unwrap();
3406 }
3407
3408 *turn = !color;
3409}
3410
3411fn validate<P: Position>(pos: &P, ep_square: Option<EnPassant>) -> PositionErrorKinds {
3412 let mut errors = PositionErrorKinds::empty();
3413
3414 if pos.board().occupied().is_empty() {
3415 errors |= PositionErrorKinds::EMPTY_BOARD;
3416 }
3417
3418 if (pos.board().pawns() & Bitboard::BACKRANKS).any() {
3419 errors |= PositionErrorKinds::PAWNS_ON_BACKRANK;
3420 }
3421
3422 for color in Color::ALL {
3423 let kings = pos.board().kings() & pos.board().by_color(color);
3424 if kings.is_empty() {
3425 errors |= PositionErrorKinds::MISSING_KING;
3426 } else if kings.more_than_one() {
3427 errors |= PositionErrorKinds::TOO_MANY_KINGS;
3428 }
3429
3430 if !is_standard_material(pos.board(), color) {
3431 errors |= PositionErrorKinds::TOO_MUCH_MATERIAL;
3432 }
3433 }
3434
3435 if let Some(their_king) = pos.board().king_of(!pos.turn())
3436 && pos
3437 .king_attackers(their_king, pos.turn(), pos.board().occupied())
3438 .any()
3439 {
3440 errors |= PositionErrorKinds::OPPOSITE_CHECK;
3441 }
3442
3443 let checkers = pos.checkers();
3444 if let (Some(a), Some(b), Some(our_king)) = (
3445 checkers.first(),
3446 checkers.last(),
3447 pos.board().king_of(pos.turn()),
3448 ) {
3449 if let Some(ep_square) = ep_square {
3450 if a != b
3453 || (a != ep_square.pawn_pushed_to()
3454 && pos
3455 .king_attackers(
3456 our_king,
3457 !pos.turn(),
3458 pos.board()
3459 .occupied()
3460 .without(ep_square.pawn_pushed_to())
3461 .with(ep_square.pawn_pushed_from()),
3462 )
3463 .any())
3464 {
3465 errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
3466 }
3467 } else {
3468 if a != b && (checkers.count() > 2 || attacks::aligned(a, our_king, b)) {
3471 errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
3472 }
3473 }
3474 }
3475
3476 if (checkers & pos.board().steppers()).more_than_one() {
3478 errors |= PositionErrorKinds::IMPOSSIBLE_CHECK;
3479 }
3480
3481 errors
3482}
3483
3484fn do_update_zobrist_hash<V: ZobristValue>(
3485 mut current: V,
3486 m: Move,
3487 turn: Color,
3488 castles: &Castles,
3489 ep_square: Option<EnPassant>,
3490) -> Option<V> {
3491 if ep_square.is_some() {
3492 return None;
3493 }
3494
3495 match m {
3496 Move::Normal {
3497 role,
3498 from,
3499 capture,
3500 to,
3501 promotion,
3502 } if (role != Role::Pawn || Square::abs_diff(from, to) != 16)
3503 && role != Role::King
3504 && !castles.castling_rights().contains(from)
3505 && !castles.castling_rights().contains(to) =>
3506 {
3507 current ^= V::zobrist_for_white_turn();
3508 current ^= V::zobrist_for_piece(from, role.of(turn));
3509 current ^= V::zobrist_for_piece(to, promotion.unwrap_or(role).of(turn));
3510 if let Some(capture) = capture {
3511 current ^= V::zobrist_for_piece(to, capture.of(!turn));
3512 }
3513 Some(current)
3514 }
3515 _ => None,
3516 }
3517}
3518
3519const fn is_standard_material(board: &Board, color: Color) -> bool {
3520 let our = board.by_color(color);
3521 let promoted_pieces = board
3522 .queens()
3523 .intersect_const(our)
3524 .count()
3525 .saturating_sub(1)
3526 + board.rooks().intersect_const(our).count().saturating_sub(2)
3527 + board
3528 .knights()
3529 .intersect_const(our)
3530 .count()
3531 .saturating_sub(2)
3532 + board
3533 .bishops()
3534 .intersect_const(our)
3535 .intersect_const(Bitboard::LIGHT_SQUARES)
3536 .count()
3537 .saturating_sub(1)
3538 + board
3539 .bishops()
3540 .intersect_const(our)
3541 .intersect_const(Bitboard::DARK_SQUARES)
3542 .count()
3543 .saturating_sub(1);
3544 board.pawns().intersect_const(our).count() + promoted_pieces <= 8
3545}
3546
3547fn gen_non_king<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
3548 gen_pawn_moves(pos, target, moves);
3549 KnightTag::gen_moves(pos, target, moves);
3550 BishopTag::gen_moves(pos, target, moves);
3551 RookTag::gen_moves(pos, target, moves);
3552 QueenTag::gen_moves(pos, target, moves);
3553}
3554
3555fn gen_safe_king<P: Position>(pos: &P, king: Square, target: Bitboard, moves: &mut MoveList) {
3556 (attacks::king_attacks(king) & target).for_each(|to| {
3557 if pos
3558 .board()
3559 .attacks_to(to, !pos.turn(), pos.board().occupied())
3560 .is_empty()
3561 {
3562 moves.push(Move::Normal {
3563 role: Role::King,
3564 from: king,
3565 capture: pos.board().role_at(to),
3566 to,
3567 promotion: None,
3568 });
3569 }
3570 });
3571}
3572
3573fn evasions<P: Position>(pos: &P, king: Square, checkers: Bitboard, moves: &mut MoveList) {
3574 let sliders = checkers & pos.board().sliders();
3575
3576 let mut attacked = Bitboard(0);
3577 sliders.for_each(|checker| {
3578 attacked |= attacks::ray(checker, king) ^ checker;
3579 });
3580
3581 gen_safe_king(pos, king, !pos.us() & !attacked, moves);
3582
3583 if let Some(checker) = checkers.single_square() {
3584 let target = attacks::between(king, checker).with(checker);
3585 gen_non_king(pos, target, moves);
3586 }
3587}
3588
3589fn gen_castling_moves<P: Position>(
3590 pos: &P,
3591 castles: &Castles,
3592 king: Square,
3593 side: CastlingSide,
3594 moves: &mut MoveList,
3595) {
3596 if let Some(rook) = castles.rook(pos.turn(), side) {
3597 let path = castles.path(pos.turn(), side);
3598 if (path & pos.board().occupied()).any() {
3599 return;
3600 }
3601
3602 let king_to = side.king_to(pos.turn());
3603 if pos
3604 .king_attackers(
3605 king_to,
3606 !pos.turn(),
3607 pos.board().occupied() ^ king ^ rook ^ side.rook_to(pos.turn()),
3608 )
3609 .any()
3610 {
3611 return;
3612 }
3613
3614 let king_path = attacks::between(king, king_to).with(king);
3615 for sq in king_path {
3616 if pos
3617 .king_attackers(sq, !pos.turn(), pos.board().occupied() ^ king)
3618 .any()
3619 {
3620 return;
3621 }
3622 }
3623
3624 moves.push(Move::Castle { king, rook });
3625 }
3626}
3627
3628trait Stepper {
3629 const ROLE: Role;
3630
3631 fn attacks(from: Square) -> Bitboard;
3632
3633 fn gen_moves<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
3634 pos.our(Self::ROLE).for_each(|from| {
3635 (Self::attacks(from) & target).for_each(|to| {
3636 moves.push(Move::Normal {
3637 role: Self::ROLE,
3638 from,
3639 capture: pos.board().role_at(to),
3640 to,
3641 promotion: None,
3642 });
3643 });
3644 });
3645 }
3646}
3647
3648trait Slider {
3649 const ROLE: Role;
3650
3651 fn attacks(from: Square, occupied: Bitboard) -> Bitboard;
3652
3653 fn gen_moves<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
3654 pos.our(Self::ROLE).for_each(|from| {
3655 (Self::attacks(from, pos.board().occupied()) & target).for_each(|to| {
3656 moves.push(Move::Normal {
3657 role: Self::ROLE,
3658 from,
3659 capture: pos.board().role_at(to),
3660 to,
3661 promotion: None,
3662 });
3663 });
3664 });
3665 }
3666}
3667
3668enum KnightTag {}
3669enum BishopTag {}
3670enum RookTag {}
3671enum QueenTag {}
3672
3673impl Stepper for KnightTag {
3674 const ROLE: Role = Role::Knight;
3675
3676 #[inline]
3677 fn attacks(from: Square) -> Bitboard {
3678 attacks::knight_attacks(from)
3679 }
3680}
3681
3682impl Slider for BishopTag {
3683 const ROLE: Role = Role::Bishop;
3684
3685 #[inline]
3686 fn attacks(from: Square, occupied: Bitboard) -> Bitboard {
3687 attacks::bishop_attacks(from, occupied)
3688 }
3689}
3690
3691impl Slider for RookTag {
3692 const ROLE: Role = Role::Rook;
3693
3694 #[inline]
3695 fn attacks(from: Square, occupied: Bitboard) -> Bitboard {
3696 attacks::rook_attacks(from, occupied)
3697 }
3698}
3699
3700impl Slider for QueenTag {
3701 const ROLE: Role = Role::Queen;
3702
3703 #[inline]
3704 fn attacks(from: Square, occupied: Bitboard) -> Bitboard {
3705 attacks::queen_attacks(from, occupied)
3706 }
3707}
3708
3709fn gen_pawn_moves<P: Position>(pos: &P, target: Bitboard, moves: &mut MoveList) {
3710 #[inline(always)]
3716 fn gen_pawn_captures<P: Position>(
3717 pos: &P,
3718 dir: Direction,
3719 target: Bitboard,
3720 moves: &mut MoveList,
3721 ) {
3722 let captures = dir.translate(pos.our(Role::Pawn)) & pos.them() & target;
3723
3724 (captures & !Bitboard::BACKRANKS).for_each(|to| {
3725 let from = unsafe { to.offset_unchecked(-dir.offset()) };
3727 moves.push(Move::Normal {
3728 role: Role::Pawn,
3729 from,
3730 capture: pos.board().role_at(to),
3731 to,
3732 promotion: None,
3733 });
3734 });
3735
3736 (captures & Bitboard::BACKRANKS).for_each(|to| {
3737 let from = unsafe { to.offset_unchecked(-dir.offset()) };
3739 push_promotions(moves, from, to, pos.board().role_at(to));
3740 });
3741 }
3742 gen_pawn_captures(
3743 pos,
3744 pos.turn()
3745 .fold_wb(Direction::NorthWest, Direction::SouthWest),
3746 target,
3747 moves,
3748 );
3749 gen_pawn_captures(
3750 pos,
3751 pos.turn()
3752 .fold_wb(Direction::NorthEast, Direction::SouthEast),
3753 target,
3754 moves,
3755 );
3756
3757 let single_moves =
3759 pos.our(Role::Pawn).shift(pos.turn().fold_wb(8, -8)) & !pos.board().occupied();
3760
3761 (single_moves & target & !Bitboard::BACKRANKS).for_each(|to| {
3762 let from = unsafe { to.offset_unchecked(pos.turn().fold_wb(-8, 8)) };
3764 moves.push(Move::Normal {
3765 role: Role::Pawn,
3766 from,
3767 capture: None,
3768 to,
3769 promotion: None,
3770 });
3771 });
3772
3773 (single_moves & target & Bitboard::BACKRANKS).for_each(|to| {
3774 let from = unsafe { to.offset_unchecked(pos.turn().fold_wb(-8, 8)) };
3776 push_promotions(moves, from, to, None);
3777 });
3778
3779 let double_moves = single_moves.shift(pos.turn().fold_wb(8, -8))
3781 & pos.turn().fold_wb(Bitboard::SOUTH, Bitboard::NORTH)
3782 & !pos.board().occupied();
3783
3784 (double_moves & target).for_each(|to| {
3785 let from = unsafe { to.offset_unchecked(pos.turn().fold_wb(-16, 16)) };
3787 moves.push(Move::Normal {
3788 role: Role::Pawn,
3789 from,
3790 capture: None,
3791 to,
3792 promotion: None,
3793 });
3794 });
3795}
3796
3797fn push_promotions(moves: &mut MoveList, from: Square, to: Square, capture: Option<Role>) {
3798 for promotion in [Role::Queen, Role::Rook, Role::Bishop, Role::Knight] {
3799 moves.push(Move::Normal {
3800 role: Role::Pawn,
3801 from,
3802 capture,
3803 to,
3804 promotion: Some(promotion),
3805 });
3806 }
3807}
3808
3809fn gen_en_passant(
3810 board: &Board,
3811 turn: Color,
3812 ep_square: Option<EnPassant>,
3813 moves: &mut MoveList,
3814) -> bool {
3815 let mut found = false;
3816
3817 if let Some(EnPassant(to)) = ep_square {
3818 (board.pawns() & board.by_color(turn) & attacks::pawn_attacks(!turn, to)).for_each(
3819 |from| {
3820 moves.push(Move::EnPassant { from, to });
3821 found = true;
3822 },
3823 );
3824 }
3825
3826 found
3827}
3828
3829fn slider_blockers(board: &Board, enemy: Bitboard, king: Square) -> Bitboard {
3830 let snipers = (attacks::rook_attacks(king, Bitboard(0)) & board.rooks_and_queens())
3831 | (attacks::bishop_attacks(king, Bitboard(0)) & board.bishops_and_queens());
3832
3833 let mut blockers = Bitboard(0);
3834 (snipers & enemy).for_each(|sniper| {
3835 let b = attacks::between(king, sniper) & board.occupied();
3836 if !b.more_than_one() {
3837 blockers.add(b);
3838 }
3839 });
3840
3841 blockers
3842}
3843
3844fn is_safe<P: Position>(pos: &P, king: Square, m: Move, blockers: Bitboard) -> bool {
3845 match m {
3846 Move::Normal { from, to, .. } => {
3847 !blockers.contains(from) || attacks::aligned(from, to, king)
3848 }
3849 Move::EnPassant { from, to } => {
3850 let capture = Square::from_coords(to.file(), from.rank());
3851 pos.board()
3852 .attacks_to(
3853 king,
3854 !pos.turn(),
3855 pos.board()
3856 .occupied()
3857 .toggled(from)
3858 .toggled(capture)
3859 .with(to),
3860 )
3861 .without(capture)
3862 .is_empty()
3863 }
3864 _ => true,
3865 }
3866}
3867
3868fn filter_san_candidates(role: Role, to: Square, moves: &mut MoveList) {
3869 moves.retain(|m| match *m {
3870 Move::Normal { role: r, to: t, .. } | Move::Put { role: r, to: t } => to == t && role == r,
3871 Move::EnPassant { to: t, .. } => role == Role::Pawn && t == to,
3872 Move::Castle { .. } => false,
3873 });
3874}
3875
3876#[cfg(test)]
3877mod tests {
3878 use super::*;
3879 use crate::fen::{Epd, Fen};
3880
3881 #[cfg(feature = "alloc")]
3882 struct _AssertObjectSafe(alloc::boxed::Box<dyn Position>);
3883
3884 fn setup_fen<T: Position + FromSetup>(fen: &str) -> T {
3885 fen.parse::<Fen>()
3886 .expect("valid fen")
3887 .into_position::<T>(CastlingMode::Chess960)
3888 .expect("legal position")
3889 }
3890
3891 #[test]
3892 fn test_most_known_legals() {
3893 let pos: Chess = setup_fen("R6R/3Q4/1Q4Q1/4Q3/2Q4Q/Q4Q2/pp1Q4/kBNN1KB1 w - - 0 1");
3894 assert_eq!(pos.legal_moves().len(), 218);
3895 }
3896
3897 #[test]
3898 fn test_pinned_san_candidate() {
3899 let pos: Chess = setup_fen("R2r2k1/6pp/1Np2p2/1p2pP2/4p3/4K3/3r2PP/8 b - - 5 37");
3900
3901 let moves = pos.san_candidates(Role::Rook, Square::D3);
3902
3903 assert_eq!(
3904 moves[0],
3905 Move::Normal {
3906 role: Role::Rook,
3907 from: Square::D2,
3908 capture: None,
3909 to: Square::D3,
3910 promotion: None,
3911 }
3912 );
3913
3914 assert_eq!(moves.len(), 1);
3915 }
3916
3917 #[test]
3918 fn test_promotion() {
3919 let pos: Chess = setup_fen("3r3K/6PP/8/8/8/2k5/8/8 w - - 0 1");
3920
3921 let moves = pos.legal_moves();
3922 assert!(moves.iter().all(|m| m.role() == Role::Pawn));
3923 assert!(moves.iter().all(|m| m.is_promotion()));
3924 }
3925
3926 fn assert_insufficient_material<P>(fen: &str, white: bool, black: bool)
3927 where
3928 P: Position + FromSetup,
3929 {
3930 let pos: P = setup_fen(fen);
3931
3932 assert_eq!(
3933 pos.has_insufficient_material(White),
3934 white,
3935 "expected white {}",
3936 if white { "cannot win" } else { "can win " }
3937 );
3938 assert_eq!(
3939 pos.has_insufficient_material(Black),
3940 black,
3941 "expected black {}",
3942 if black { "cannot win" } else { "can win" }
3943 );
3944 }
3945
3946 #[test]
3947 fn test_insufficient_material() {
3948 assert_insufficient_material::<Chess>("8/5k2/8/8/8/8/3K4/8 w - - 0 1", true, true);
3949 assert_insufficient_material::<Chess>("8/3k4/8/8/2N5/8/3K4/8 b - - 0 1", true, true);
3950 assert_insufficient_material::<Chess>("8/4rk2/8/8/8/8/3K4/8 w - - 0 1", true, false);
3951 assert_insufficient_material::<Chess>("8/4qk2/8/8/8/8/3K4/8 w - - 0 1", true, false);
3952 assert_insufficient_material::<Chess>("8/4bk2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
3953 assert_insufficient_material::<Chess>("8/8/3Q4/2bK4/B7/8/1k6/8 w - - 1 68", false, false);
3954 assert_insufficient_material::<Chess>("8/5k2/8/8/8/4B3/3K1B2/8 w - - 0 1", true, true);
3955 assert_insufficient_material::<Chess>("5K2/8/8/1B6/8/k7/6b1/8 w - - 0 39", true, true);
3956 assert_insufficient_material::<Chess>("8/8/8/4k3/5b2/3K4/8/2B5 w - - 0 33", true, true);
3957 assert_insufficient_material::<Chess>("3b4/8/8/6b1/8/8/R7/K1k5 w - - 0 1", false, true);
3958 }
3959
3960 #[test]
3961 fn test_outcome() {
3962 for (fen, outcome) in [
3963 (
3964 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
3965 Outcome::Unknown,
3966 ),
3967 (
3968 "2k5/8/8/8/8/8/8/3KB3 w - - 0 1",
3969 Outcome::Known(KnownOutcome::Draw),
3970 ),
3971 (
3972 "8/8/8/8/8/Q1K5/8/1k6 b - - 0 1",
3973 Outcome::Known(KnownOutcome::Draw),
3974 ),
3975 (
3976 "8/8/8/8/8/Q7/2K5/k7 b - - 0 1",
3977 Outcome::Known(KnownOutcome::Decisive { winner: White }),
3978 ),
3979 ] {
3980 let pos: Chess = setup_fen(fen);
3981 assert_eq!(pos.outcome(), outcome);
3982 }
3983 }
3984
3985 #[test]
3986 #[cfg(feature = "std")]
3987 fn test_eq() {
3988 fn hash<T: Hash>(value: &T) -> u64 {
3989 let mut hasher = std::collections::hash_map::DefaultHasher::new();
3990 value.hash(&mut hasher);
3991 hasher.finish()
3992 }
3993
3994 assert_eq!(Chess::default(), Chess::default());
3995 assert_eq!(hash(&Chess::default()), hash(&Chess::default()));
3996 assert_ne!(
3997 Chess::default(),
3998 Chess::default().swap_turn().expect("swap turn legal")
3999 );
4000
4001 let pos: Chess = setup_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBK1BNR w KQkq - 0 1");
4003 let pos_after_move_played = pos
4004 .play(Move::Normal {
4005 role: Role::King,
4006 from: Square::D1,
4007 to: Square::E1,
4008 capture: None,
4009 promotion: None,
4010 })
4011 .expect("Ke1 is legal");
4012 let pos_after_move =
4013 setup_fen::<Chess>("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR b kq - 1 1");
4014 assert_eq!(pos_after_move, pos_after_move_played);
4015 assert_eq!(hash(&pos_after_move), hash(&pos_after_move_played));
4016
4017 let pos: Chess = setup_fen("rnbqkbn1/pppppppP/8/8/8/8/PPPPPPP1/RNB~QKBNR w KQq - 0 26");
4019 let pos_after_queen_promotion = pos
4020 .clone()
4021 .play(Move::Normal {
4022 role: Role::Pawn,
4023 from: Square::H7,
4024 to: Square::H8,
4025 capture: None,
4026 promotion: Some(Role::Queen),
4027 })
4028 .expect("h8=Q is legal");
4029 let pos_after_knight_promotion = pos
4030 .play(Move::Normal {
4031 role: Role::Pawn,
4032 from: Square::H7,
4033 to: Square::H8,
4034 capture: None,
4035 promotion: Some(Role::Knight),
4036 })
4037 .expect("h8=N is legal");
4038 let final_pos: Chess =
4039 setup_fen("rnbqkbnQ/ppppppp1/8/8/8/8/PPPPPPP1/RNBQKBNR b KQq - 0 26");
4040 assert_eq!(pos_after_queen_promotion, final_pos);
4041 assert_eq!(hash(&pos_after_queen_promotion), hash(&final_pos));
4042 assert_ne!(pos_after_knight_promotion, final_pos);
4043
4044 let pos: Chess = setup_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1");
4046 let pos_with_irrelevant_ep: Chess =
4047 setup_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1");
4048 assert_eq!(pos, pos_with_irrelevant_ep);
4049 assert_eq!(hash(&pos), hash(&pos_with_irrelevant_ep));
4050 }
4051
4052 #[cfg(feature = "variant")]
4053 #[test]
4054 fn test_variant_insufficient_material() {
4055 use super::variant::*;
4056
4057 let false_negative = false;
4058
4059 assert_insufficient_material::<Atomic>("8/3k4/8/8/2N5/8/3K4/8 b - - 0 1", true, true);
4060 assert_insufficient_material::<Atomic>("8/4rk2/8/8/8/8/3K4/8 w - - 0 1", true, true);
4061 assert_insufficient_material::<Atomic>("8/4qk2/8/8/8/8/3K4/8 w - - 0 1", true, false);
4062 assert_insufficient_material::<Atomic>("8/1k6/8/2n5/8/3NK3/8/8 b - - 0 1", false, false);
4063 assert_insufficient_material::<Atomic>("8/4bk2/8/8/8/8/3KB3/8 w - - 0 1", true, true);
4064 assert_insufficient_material::<Atomic>("4b3/5k2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
4065 assert_insufficient_material::<Atomic>("3Q4/5kKB/8/8/8/8/8/8 b - - 0 1", false, true);
4066 assert_insufficient_material::<Atomic>("8/5k2/8/8/8/8/5K2/4bb2 w - - 0 1", true, false);
4067 assert_insufficient_material::<Atomic>("8/5k2/8/8/8/8/5K2/4nb2 w - - 0 1", true, false);
4068
4069 assert_insufficient_material::<Antichess>("8/4bk2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
4070 assert_insufficient_material::<Antichess>("4b3/5k2/8/8/8/8/3KB3/8 w - - 0 1", false, false);
4071 assert_insufficient_material::<Antichess>("8/8/8/6b1/8/3B4/4B3/5B2 w - - 0 1", true, true);
4072 assert_insufficient_material::<Antichess>("8/8/5b2/8/8/3B4/3B4/8 w - - 0 1", true, false);
4073 assert_insufficient_material::<Antichess>(
4074 "8/5p2/5P2/8/3B4/1bB5/8/8 b - - 0 1",
4075 false_negative,
4076 false_negative,
4077 );
4078 assert_insufficient_material::<Antichess>(
4079 "8/8/6b1/8/3P4/8/5B2/8 w - - 0 1",
4080 false_negative,
4081 false,
4082 );
4083
4084 assert_insufficient_material::<KingOfTheHill>(
4085 "8/5k2/8/8/8/8/3K4/8 w - - 0 1",
4086 false,
4087 false,
4088 );
4089
4090 assert_insufficient_material::<RacingKings>("8/5k2/8/8/8/8/3K4/8 w - - 0 1", false, false);
4091
4092 assert_insufficient_material::<ThreeCheck>("8/5k2/8/8/8/8/3K4/8 w - - 0 1", true, true);
4093 assert_insufficient_material::<ThreeCheck>("8/5k2/8/8/8/8/3K2N1/8 w - - 0 1", false, true);
4094
4095 assert_insufficient_material::<Crazyhouse>("8/5k2/8/8/8/8/3K2N1/8 w - - 0 1", true, true);
4096 assert_insufficient_material::<Crazyhouse>(
4097 "8/5k2/8/8/8/5B2/3KB3/8 w - - 0 1",
4098 false,
4099 false,
4100 );
4101 assert_insufficient_material::<Crazyhouse>(
4102 "8/8/8/8/3k4/3N~4/3K4/8 w - - 0 1",
4103 false,
4104 false,
4105 );
4106
4107 assert_insufficient_material::<Horde>("8/5k2/8/8/8/4NN2/8/8 w - - 0 1", true, false);
4108 assert_insufficient_material::<Horde>("8/8/8/7k/7P/7P/8/8 b - - 0 58", false, false);
4109 }
4110
4111 #[cfg(feature = "variant")]
4112 #[test]
4113 fn test_exploded_king_loses_castling_rights() {
4114 use super::variant::Atomic;
4115
4116 let pos: Atomic = setup_fen("rnb1kbnr/pppppppp/8/4q3/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1");
4117
4118 let pos = pos
4119 .play(Move::Normal {
4120 role: Role::Queen,
4121 from: Square::E5,
4122 to: Square::E2,
4123 capture: Some(Role::Pawn),
4124 promotion: None,
4125 })
4126 .expect("Qxe2# is legal");
4127
4128 assert_eq!(
4129 pos.castles().castling_rights(),
4130 Bitboard::from(Square::A8) | Bitboard::from(Square::H8)
4131 );
4132 assert_eq!(
4133 pos.castles().rook(Color::White, CastlingSide::QueenSide),
4134 None
4135 );
4136 assert_eq!(
4137 pos.castles().rook(Color::White, CastlingSide::KingSide),
4138 None
4139 );
4140 assert_eq!(
4141 pos.castles().rook(Color::Black, CastlingSide::QueenSide),
4142 Some(Square::A8)
4143 );
4144 assert_eq!(
4145 pos.castles().rook(Color::Black, CastlingSide::KingSide),
4146 Some(Square::H8)
4147 );
4148 }
4149
4150 #[cfg(feature = "variant")]
4151 #[test]
4152 fn test_atomic_exploded_king() {
4153 use super::variant::Atomic;
4154
4155 let pos: Atomic = setup_fen("rn5r/pp4pp/2p3Nn/5p2/1b2P1PP/8/PPP2P2/R1B1KB1R b KQ - 0 9");
4156
4157 assert_eq!(
4158 pos.outcome(),
4159 Outcome::Known(KnownOutcome::Decisive {
4160 winner: Color::White
4161 })
4162 );
4163 }
4164
4165 #[cfg(feature = "variant")]
4166 #[test]
4167 fn test_racing_kings_end() {
4168 use super::variant::RacingKings;
4169
4170 let pos: RacingKings = setup_fen("kr3NK1/1q2R3/8/8/8/5n2/2N5/1rb2B1R w - - 11 14");
4172 assert!(pos.is_variant_end());
4173 assert_eq!(pos.variant_outcome(), Outcome::Known(KnownOutcome::Draw));
4174
4175 let pos: RacingKings = setup_fen("1k6/6K1/8/8/8/8/8/8 w - - 0 1");
4177 assert!(pos.is_variant_end());
4178 assert_eq!(
4179 pos.variant_outcome(),
4180 Outcome::Known(KnownOutcome::Decisive {
4181 winner: Color::Black
4182 })
4183 );
4184
4185 let pos: RacingKings = setup_fen("1K6/7k/8/8/8/8/8/8 b - - 0 1");
4187 assert_eq!(pos.variant_outcome(), Outcome::Unknown);
4188
4189 let pos: RacingKings = setup_fen("2KR4/k7/2Q5/4q3/8/8/8/2N5 b - - 0 1");
4191 assert!(pos.is_variant_end());
4192 assert_eq!(
4193 pos.variant_outcome(),
4194 Outcome::Known(KnownOutcome::Decisive {
4195 winner: Color::White
4196 })
4197 );
4198 }
4199
4200 #[cfg(feature = "variant")]
4201 #[test]
4202 fn test_antichess_insufficient_material() {
4203 use super::variant::Antichess;
4204
4205 for (fen, possible_winner) in [
4206 ("8/8/8/1n2N3/8/8/8/8 w - - 0 32", Color::Black),
4208 ("8/3N4/8/1n6/8/8/8/8 b - - 1 32", Color::Black),
4209 ("8/8/8/8/2N5/8/8/n7 w - - 0 30", Color::Black),
4211 ("8/8/8/4N3/8/8/8/n7 b - - 1 30", Color::Black),
4212 ("8/8/8/4N3/8/8/2n5/8 w - - 2 31", Color::Black),
4213 ("8/8/6N1/8/8/8/2n5/8 b - - 3 31", Color::Black),
4214 ("8/8/6N1/8/8/4n3/8/8 w - - 4 32", Color::Black),
4215 ("5N2/8/8/8/8/4n3/8/8 b - - 5 32", Color::Black),
4216 ("5N2/8/8/5n2/8/8/8/8 w - - 6 33", Color::Black),
4217 ("6n1/8/8/4N3/8/8/8/8 b - - 0 27", Color::White),
4219 ("8/8/5n2/4N3/8/8/8/8 w - - 1 28", Color::White),
4220 ("8/3N4/5n2/8/8/8/8/8 b - - 2 28", Color::White),
4221 ("8/3n4/8/8/8/8/8/8 w - - 0 29", Color::White),
4222 ] {
4223 let pos = fen
4224 .parse::<Fen>()
4225 .expect("valid fen")
4226 .into_position::<Antichess>(CastlingMode::Standard)
4227 .expect("legal position");
4228 assert!(
4229 !pos.has_insufficient_material(possible_winner),
4230 "{possible_winner} can win {fen}"
4231 );
4232 assert!(
4233 pos.has_insufficient_material(!possible_winner),
4234 "{possible_winner} can not win {fen}"
4235 );
4236 }
4237 }
4238
4239 #[test]
4240 fn test_aligned_checkers() {
4241 let res = "2Nq4/2K5/1b6/8/7R/3k4/7P/8 w - - 0 1"
4242 .parse::<Fen>()
4243 .expect("valid fen")
4244 .into_position::<Chess>(CastlingMode::Chess960);
4245 assert_eq!(
4246 res.expect_err("impossible check").kinds(),
4247 PositionErrorKinds::IMPOSSIBLE_CHECK
4248 );
4249
4250 let _ = "8/8/5k2/p1q5/PP1rp1P1/3P1N2/2RK1r2/5nN1 w - - 0 3"
4251 .parse::<Fen>()
4252 .expect("valid fen")
4253 .into_position::<Chess>(CastlingMode::Standard)
4254 .expect("checkers aligned with opponent king not relevant");
4255
4256 let res = "8/8/8/1k6/3Pp3/8/8/4KQ2 b - d3 0 1"
4257 .parse::<Fen>()
4258 .expect("valid fen")
4259 .into_position::<Chess>(CastlingMode::Standard);
4260 assert_eq!(
4261 res.expect_err("impossible check due to ep square").kinds(),
4262 PositionErrorKinds::IMPOSSIBLE_CHECK
4263 );
4264 }
4265
4266 #[cfg(feature = "alloc")]
4267 #[test]
4268 fn test_swap_turn() {
4269 use alloc::string::ToString as _;
4270 let pos: Chess = "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"
4271 .parse::<Fen>()
4272 .expect("valid fen")
4273 .into_position(CastlingMode::Chess960)
4274 .expect("valid position");
4275 let swapped = pos.swap_turn().expect("swap turn");
4276 assert_eq!(
4277 Fen::from_position(&swapped, EnPassantMode::Always).to_string(),
4278 "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 3"
4279 );
4280 }
4281
4282 #[test]
4283 fn test_invalid_ep_square() {
4284 let fen: Fen = "4k3/8/8/8/8/8/8/4K3 w - e3 0 1".parse().expect("valid fen");
4285 let err = fen
4286 .into_position::<Chess>(CastlingMode::Standard)
4287 .expect_err("invalid ep square");
4288 assert_eq!(err.kinds(), PositionErrorKinds::INVALID_EP_SQUARE);
4289 assert_eq!(
4290 err.ignore_invalid_ep_square()
4291 .expect("now valid")
4292 .maybe_ep_square(),
4293 None
4294 );
4295 }
4296
4297 #[test]
4298 fn test_check_with_unrelated_ep_square() {
4299 let fen: Fen = "rnbqk1nr/bb3p1p/1q2r3/2pPp3/3P4/7P/1PP1NpPP/R1BQKBNR w KQkq c6 0 1"
4300 .parse()
4301 .expect("valid fen");
4302 let pos = fen
4303 .into_position::<Chess>(CastlingMode::Standard)
4304 .expect_err("impossible check")
4305 .ignore_impossible_check()
4306 .expect("legal otherwise");
4307 assert!(pos.san_candidates(Role::Pawn, Square::C6).is_empty());
4308 assert!(pos.en_passant_moves().is_empty());
4309 assert_eq!(pos.legal_moves().len(), 2);
4310 }
4311
4312 #[test]
4313 fn test_put_in_standard() {
4314 let pos = Chess::default();
4315 assert!(!pos.is_legal(Move::Put {
4316 role: Role::Pawn,
4317 to: Square::D4
4318 }));
4319 }
4320
4321 #[test]
4322 fn test_from_setup() {
4323 for (epd, expected_error_kinds) in [
4324 (
4325 "1rrrrrk1/1PPPPPPP/8/8/8/8/8/6K1 b - -",
4326 PositionErrorKinds::IMPOSSIBLE_CHECK,
4327 ),
4328 (
4329 "1rrrrrk1/PPPPPPPP/8/8/8/8/8/6K1 b - -",
4330 PositionErrorKinds::IMPOSSIBLE_CHECK,
4331 ),
4332 (
4333 "1rrrrrkr/PPPPPPPP/8/8/8/8/8/6K1 b - -",
4334 PositionErrorKinds::IMPOSSIBLE_CHECK,
4335 ),
4336 (
4337 "1q4k1/3r1Ppp/5NP1/pP6/8/1Q6/3B4/2K2R2 b - -",
4338 PositionErrorKinds::IMPOSSIBLE_CHECK,
4339 ),
4340 (
4341 "2b5/1nbn4/n3n3/1kn5/n3n3/1n1n4/5RQ1/2KQ1R2 w K -",
4342 PositionErrorKinds::IMPOSSIBLE_CHECK,
4343 ),
4344 (
4345 "1n1b1Q2/1b4rp/1q5P/2Pppr2/p2kP1pR/2NP4/P2Bq1K1/RQ3Br1 w - -",
4346 PositionErrorKinds::default(),
4347 ),
4348 (
4349 "1n1b1Q2/1b4rp/1q5P/2Pppr2/p2kP1pR/2NP4/P2Bq1K1/RQ3Bq1 w - -",
4350 PositionErrorKinds::default(),
4351 ),
4352 (
4353 "1n1b1Q2/qb4rp/7P/2Pppr2/p2kP1p1/2NP4/P2Bq1KR/RQ3Br1 w - -",
4354 PositionErrorKinds::default(),
4355 ),
4356 (
4357 "1n1b1Q2/qb4rp/7P/2Pppr2/p2kP1p1/2NP4/P2Bq1KR/RQ3Bq1 w - -",
4358 PositionErrorKinds::default(),
4359 ),
4360 ] {
4361 let error_kinds = epd
4362 .parse::<Epd>()
4363 .expect("valid epd")
4364 .into_position::<Chess>(CastlingMode::Chess960)
4365 .map_err(|err| err.kinds())
4366 .err()
4367 .unwrap_or_default();
4368 assert_eq!(error_kinds, expected_error_kinds, "epd: {epd}");
4369 }
4370 }
4371}