1use std::fmt::{self, Display};
7
8use crate::bitboard::Bitboard;
9use crate::boardrepr::PieceSets;
10use crate::coretypes::{
11 Castling, Color, Move, MoveCount, MoveInfo, MoveKind, Piece, PieceKind, Square,
12};
13use crate::coretypes::{Color::*, PieceKind::*, Square::*};
14use crate::error::{self, ErrorKind};
15use crate::fen::Fen;
16use crate::movegen as mg;
17use crate::movelist::{MoveHistory, MoveList};
18
19#[derive(Debug, Clone, Eq, PartialEq)]
23pub struct Game {
24 pub base_position: Position,
25 pub moves: MoveHistory,
26 pub position: Position,
27}
28
29impl Game {
30 pub fn new(base_position: Position, moves: MoveHistory) -> error::Result<Self> {
34 let mut position = base_position.clone();
35
36 for move_ in &moves {
37 let maybe_move_info = position.do_legal_move(*move_);
38 if maybe_move_info.is_none() {
39 return Err(ErrorKind::GameIllegalMove.into());
40 }
41 }
42
43 Ok(Self {
44 base_position,
45 moves,
46 position,
47 })
48 }
49
50 pub fn start_position() -> Self {
52 Self::from(Position::start_position())
53 }
54}
55
56impl From<Position> for Game {
58 fn from(position: Position) -> Self {
59 Self::new(position, MoveHistory::new()).unwrap()
60 }
61}
62
63#[derive(Debug, Copy, Clone, Eq, PartialEq)]
68pub struct Cache {
69 pub(crate) castling: Castling,
70 pub(crate) en_passant: Option<Square>,
71 pub(crate) halfmoves: MoveCount,
72 }
76
77impl Cache {
78 pub fn illegal() -> Self {
80 Self {
81 castling: Castling::NONE,
82 en_passant: None,
83 halfmoves: 1,
84 }
85 }
86}
87
88impl From<&Position> for Cache {
89 fn from(position: &Position) -> Self {
90 Self {
91 castling: position.castling,
92 en_passant: position.en_passant,
93 halfmoves: position.halfmoves,
94 }
95 }
96}
97
98#[derive(Debug, Copy, Clone, Eq, PartialEq)]
108pub struct Position {
109 pub(crate) pieces: PieceSets,
110 pub(crate) player: Color,
111 pub(crate) castling: Castling,
112 pub(crate) en_passant: Option<Square>,
113 pub(crate) halfmoves: MoveCount,
114 pub(crate) fullmoves: MoveCount,
115}
116
117impl Position {
118 pub fn start_position() -> Self {
120 Self {
121 pieces: PieceSets::start_position(),
122 player: Color::White,
123 castling: Castling::start_position(),
124 en_passant: None,
125 halfmoves: 0,
126 fullmoves: 1,
127 }
128 }
129
130 pub fn pieces(&self) -> &PieceSets {
132 &self.pieces
133 }
134 pub fn player(&self) -> &Color {
135 &self.player
136 }
137 pub fn castling(&self) -> &Castling {
138 &self.castling
139 }
140 pub fn en_passant(&self) -> &Option<Square> {
141 &self.en_passant
142 }
143 pub fn halfmoves(&self) -> &MoveCount {
144 &self.halfmoves
145 }
146 pub fn fullmoves(&self) -> &MoveCount {
147 &self.fullmoves
148 }
149
150 pub fn moves_played(&self) -> MoveCount {
152 self.fullmoves * 2
153 - 1
154 - match self.player {
155 Color::White => 1,
156 Color::Black => 0,
157 }
158 }
159
160 pub(crate) fn age(&self) -> u8 {
163 (self.moves_played() % u8::MAX as MoveCount) as u8
164 }
165
166 pub fn color_flip(&self) -> Self {
174 let mut flipped = self.clone();
175
176 let mut pieces = PieceSets::new();
178 for color in Color::iter() {
179 for piece_kind in PieceKind::iter() {
180 for sq in self.pieces[(color, piece_kind)] {
181 let flipped_sq = Square::from((sq.file(), sq.rank().flip()));
182 pieces[(!color, piece_kind)].set_square(flipped_sq);
183 }
184 }
185 }
186 flipped.pieces = pieces;
187
188 flipped.player = !self.player;
190
191 let mut cr = Castling::NONE;
193 self.castling
194 .has(Castling::W_KING)
195 .then(|| cr.set(Castling::B_KING));
196 self.castling
197 .has(Castling::W_QUEEN)
198 .then(|| cr.set(Castling::B_QUEEN));
199 self.castling
200 .has(Castling::B_KING)
201 .then(|| cr.set(Castling::W_KING));
202 self.castling
203 .has(Castling::B_QUEEN)
204 .then(|| cr.set(Castling::W_QUEEN));
205 flipped.castling = cr;
206
207 flipped.en_passant = self
209 .en_passant
210 .map(|sq| Square::from((sq.file(), sq.rank().flip())));
211
212 debug_assert!(flipped.pieces().is_valid());
213 debug_assert!(flipped.castling().is_mask_valid());
214 flipped
215 }
216
217 pub fn is_same_as(&self, other: &Self) -> bool {
221 self.player == other.player
222 && self.castling == other.castling
223 && self.en_passant == other.en_passant
224 && self.pieces == other.pieces
225 }
226
227 pub fn fifty_move_rule(&self, num_legal_moves: usize) -> bool {
230 self.halfmoves >= 100 && num_legal_moves != 0
231 }
232
233 pub fn move_info(&self, move_: Move) -> MoveInfo {
235 let moved_piece_kind = self
236 .pieces
237 .on_player_square(self.player, move_.from)
238 .expect("no piece on `from` square");
239
240 let mut move_kind = MoveKind::Quiet;
241
242 if let Some(pk) = self.pieces.on_player_square(!self.player, move_.to) {
244 move_kind = MoveKind::Capture(pk);
245 }
246 else if moved_piece_kind == Pawn {
248 if let Some(ep_square) = self.en_passant {
249 if move_.to == ep_square {
250 move_kind = MoveKind::EnPassant;
251 }
252 }
253 }
254 else if moved_piece_kind == King {
256 match (move_.from, move_.to) {
257 (E1, C1) | (E1, G1) | (E8, C8) | (E8, G8) => move_kind = MoveKind::Castle,
258 _ => (),
259 };
260 }
261
262 MoveInfo::new(move_, moved_piece_kind, move_kind)
263 }
264
265 pub fn cache(&self) -> Cache {
267 Cache::from(self)
268 }
269
270 fn step_halfmoves(&mut self, move_info: &MoveInfo) {
273 let is_pawn_move = move_info.piece_kind == Pawn;
274 let is_capture = matches!(move_info.move_kind, MoveKind::Capture(_));
275
276 match is_pawn_move || is_capture {
277 true => self.halfmoves = 0,
278 false => self.halfmoves += 1,
279 }
280 }
281
282 fn step_fullmoves(&mut self) {
284 if *self.player() == Black {
285 self.fullmoves += 1;
286 }
287 }
288
289 fn unstep_fullmoves(&mut self) {
292 if *self.player() == White && *self.fullmoves() > 1 {
293 self.fullmoves -= 1;
294 }
295 }
296
297 pub fn do_move(&mut self, move_: Move) -> MoveInfo {
310 let move_info = self.move_info(move_);
311 self.do_move_info(move_info);
312 move_info
313 }
314
315 pub fn do_move_info(&mut self, move_info: MoveInfo) {
318 let player = *self.player();
319 let active_piece = Piece::new(player, move_info.piece_kind);
320
321 self.step_halfmoves(&move_info);
323 self.step_fullmoves();
324 self.en_passant = None;
325 self.pieces[active_piece].clear_square(move_info.from);
326 self.player = !self.player;
327
328 if let Some(promoting_piece_kind) = move_info.promotion {
330 let promoting_piece = Piece::new(player, promoting_piece_kind);
331 self.pieces[promoting_piece].set_square(move_info.to);
332 } else {
333 self.pieces[active_piece].set_square(move_info.to);
334 }
335
336 match move_info.move_kind {
338 MoveKind::Capture(piece_kind) => {
340 let captured_piece = Piece::new(!player, piece_kind);
341 self.pieces[captured_piece].clear_square(move_info.to);
342 }
343 MoveKind::EnPassant => {
345 let to = Bitboard::from(move_info.to);
346 let captured_pawn = mg::pawn_single_pushes(to, !player);
347 self.pieces[(!player, Pawn)].remove(&captured_pawn);
348 }
349 MoveKind::Castle => {
351 let castling_rook_squares = match (move_info.from, move_info.to) {
352 (E1, G1) => (H1, F1), (E1, C1) => (A1, D1), (E8, G8) => (H8, F8), (E8, C8) => (A8, D8), _ => panic!("move_kind is Castle however squares are illegal"),
357 };
358 let (clear, set) = castling_rook_squares;
359 let active_rook = (active_piece.color, Rook);
360 self.pieces[active_rook].clear_square(clear);
361 self.pieces[active_rook].set_square(set);
362
363 self.castling.clear_color(player);
364 }
365
366 MoveKind::Quiet => {
368 if move_info.piece_kind == Pawn {
369 let from = Bitboard::from(move_info.from);
370 let to = Bitboard::from(move_info.to);
371 let from_start_row = from & (Bitboard::RANK_2 | Bitboard::RANK_7);
372 let to_jump_row = to & (Bitboard::RANK_4 | Bitboard::RANK_5);
373
374 if !from_start_row.is_empty() && !to_jump_row.is_empty() {
375 let pawn = Bitboard::from(move_info.from);
376 self.en_passant = mg::pawn_single_pushes(pawn, player).get_lowest_square();
377 }
378 }
379 }
380 };
381
382 let moved_rights = match move_info.from {
385 A1 => Castling::W_QUEEN,
386 A8 => Castling::B_QUEEN,
387 H1 => Castling::W_KING,
388 H8 => Castling::B_KING,
389 _ => Castling::NONE,
390 };
391 let captured_rights = match move_info.to {
392 A1 => Castling::W_QUEEN,
393 A8 => Castling::B_QUEEN,
394 H1 => Castling::W_KING,
395 H8 => Castling::B_KING,
396 _ => Castling::NONE,
397 };
398 self.castling.clear(moved_rights | captured_rights);
399
400 if move_info.piece_kind == King {
402 self.castling.clear_color(player);
403 }
404
405 debug_assert!(self.pieces().is_valid());
406 }
407
408 pub fn undo_move(&mut self, move_info: MoveInfo, cache: Cache) {
410 self.unstep_fullmoves();
411 self.player = !self.player;
412 self.castling = cache.castling;
413 self.en_passant = cache.en_passant;
414 self.halfmoves = cache.halfmoves;
415
416 let player = *self.player();
422
423 let moved_piece = Piece::new(player, move_info.piece_kind);
425 self.pieces[moved_piece].set_square(move_info.from);
426 self.pieces[moved_piece].clear_square(move_info.to);
427 if let Some(promoted) = move_info.promotion {
428 self.pieces[(player, promoted)].clear_square(move_info.to);
429 }
430
431 match move_info.move_kind {
433 MoveKind::Capture(piece_kind) => {
434 self.pieces[(!player, piece_kind)].set_square(move_info.to);
435 }
436
437 MoveKind::Castle => {
438 let (rook_from, rook_to) = match move_info.to {
440 C1 => (A1, D1), G1 => (H1, F1), C8 => (A8, D8), G8 => (H8, F8), _ => panic!("MoveKind is Castle but Move is not a castling move."),
445 };
446 self.pieces[(player, Rook)].set_square(rook_from);
448 self.pieces[(player, Rook)].clear_square(rook_to);
449 }
450
451 MoveKind::EnPassant => {
452 let ep_square = cache
453 .en_passant
454 .expect("MoveKind is EnPassant, but en_passant square is not set.");
455 let ep_bb = Bitboard::from(ep_square);
456 let original_bb = mg::pawn_single_pushes(ep_bb, !player);
457 self.pieces[(!player, Pawn)] |= original_bb;
458 }
459
460 _ => (),
461 }
462 debug_assert!(self.pieces().is_valid());
463 }
464
465 pub fn do_legal_move(&mut self, move_: Move) -> Option<MoveInfo> {
470 if self.is_legal_move(move_) {
471 Some(self.do_move(move_))
472 } else {
473 None
474 }
475 }
476
477 pub fn is_checkmate(&self) -> bool {
480 let legal_moves = self.get_legal_moves();
481 if legal_moves.len() == 0 && self.is_in_check() {
482 true
483 } else {
484 false
485 }
486 }
487
488 pub fn is_stalemate(&self) -> bool {
491 let legal_moves = self.get_legal_moves();
492 if legal_moves.len() == 0 && !self.is_in_check() {
493 true
494 } else {
495 false
496 }
497 }
498
499 pub fn make_move(&self, move_: Move) -> Self {
501 let mut position_clone: Position = self.clone();
502 position_clone.do_move(move_);
503 position_clone
504 }
505
506 pub fn is_legal_move(&self, move_: Move) -> bool {
508 let legal_moves = self.get_legal_moves();
509 legal_moves.contains(&move_)
510 }
511
512 pub fn is_in_check(&self) -> bool {
514 self.num_active_king_checks() > 0
515 }
516
517 pub fn active_king_checks(&self) -> (bool, bool) {
520 let num_checks = self.num_active_king_checks();
521 let single_check = num_checks >= 1;
522 let double_check = num_checks >= 2;
523 (single_check, double_check)
524 }
525
526 pub fn num_active_king_checks(&self) -> u32 {
527 let king_bb = self.pieces[(self.player, King)];
528 let king_square = king_bb.get_lowest_square().unwrap();
529 let king_attackers = self.attackers_to(king_square, !self.player);
530 king_attackers.count_squares()
531 }
532
533 pub fn attackers_to(&self, target: Square, attacking: Color) -> Bitboard {
536 let pawns = self.pieces[(attacking, Pawn)];
537 let knights = self.pieces[(attacking, Knight)];
538 let king = self.pieces[(attacking, King)];
539 let bishops = self.pieces[(attacking, Bishop)];
540 let rooks = self.pieces[(attacking, Rook)];
541 let queens = self.pieces[(attacking, Queen)];
542
543 let occupied = self.pieces().occupied();
544
545 mg::pawn_attackers_to(target, pawns, attacking)
546 | mg::knight_attackers_to(target, knights)
547 | mg::king_attackers_to(target, king)
548 | mg::bishop_attackers_to(target, bishops, occupied)
549 | mg::rook_attackers_to(target, rooks, occupied)
550 | mg::queen_attackers_to(target, queens, occupied)
551 }
552
553 pub fn is_attacked_by(&self, target: Square, attacking: Color) -> bool {
555 self.attackers_to(target, attacking).count_squares() > 0
556 }
557
558 pub fn attacks(&self, attacking: Color, occupied: Bitboard) -> Bitboard {
560 let pawns = self.pieces[(attacking, Pawn)];
561 let knights = self.pieces[(attacking, Knight)];
562 let king = self.pieces[(attacking, King)];
563 let rooks = self.pieces[(attacking, Rook)];
564 let bishops = self.pieces[(attacking, Bishop)];
565 let queens = self.pieces[(attacking, Queen)];
566
567 mg::pawn_attacks(pawns, attacking)
568 | mg::knight_attacks(knights)
569 | mg::king_attacks(king)
570 | mg::slide_attacks(queens, rooks, bishops, occupied)
571 }
572
573 pub fn get_legal_moves(&self) -> MoveList {
580 let (single_check, double_check) = self.active_king_checks();
581
582 if double_check {
583 self.generate_legal_double_check_moves()
584 } else if single_check {
585 self.generate_legal_single_check_moves()
586 } else {
587 self.generate_legal_no_check_moves()
588 }
589 }
590
591 fn generate_legal_double_check_moves(&self) -> MoveList {
608 let king = self.pieces[(self.player, King)];
609
610 let passive_player = !self.player;
613 let occupied_without_king = self.pieces.occupied() & !king;
614 let attacked = self.attacks(passive_player, occupied_without_king);
615
616 let mut possible_moves = mg::king_attacks(king);
619 possible_moves.remove(&attacked);
620 possible_moves.remove(&self.pieces.color_occupied(self.player));
621
622 let mut legal_moves = MoveList::new(); let from = king.get_lowest_square().unwrap();
625 for to in possible_moves {
626 legal_moves.push(Move::new(from, to, None));
627 }
628
629 legal_moves
630 }
631
632 fn generate_legal_single_check_moves(&self) -> MoveList {
634 let mut legal_moves: MoveList = MoveList::new();
638
639 let king = self.pieces[(self.player, King)];
640 let king_square = king.get_lowest_square().unwrap();
641 let passive_player = !self.player;
642 let us = self.pieces.color_occupied(self.player);
643 let them = self.pieces.color_occupied(passive_player);
644 let occupied = self.pieces.occupied();
645
646 let occupied_without_king = occupied & !king;
648 let attacked_xray_king = self.attacks(passive_player, occupied_without_king);
649 let mut possible_moves = mg::king_attacks(king);
650 possible_moves.remove(&attacked_xray_king);
651 possible_moves.remove(&us);
652 for to in possible_moves {
653 legal_moves.push(Move::new(king_square, to, None));
654 }
655
656 let queens = self.pieces[(self.player, Queen)];
669 let rooks = self.pieces[(self.player, Rook)];
670 let bishops = self.pieces[(self.player, Bishop)];
671 let knights = self.pieces[(self.player, Knight)];
672 let pawns = self.pieces[(self.player, Pawn)];
673
674 let mut pseudo_moves = MoveList::new();
675 mg::queen_pseudo_moves(&mut pseudo_moves, queens, occupied, us);
676 mg::rook_pseudo_moves(&mut pseudo_moves, rooks, occupied, us);
677 mg::bishop_pseudo_moves(&mut pseudo_moves, bishops, occupied, us);
678 mg::knight_pseudo_moves(&mut pseudo_moves, knights, us);
679 mg::pawn_pseudo_moves(
680 &mut pseudo_moves,
681 pawns,
682 self.player,
683 occupied,
684 them,
685 self.en_passant,
686 );
687
688 let mut position = self.clone();
689 let cache = position.cache();
690 pseudo_moves
691 .into_iter()
692 .filter(|pseudo_move| {
693 let move_info = position.do_move(*pseudo_move);
694 let is_legal = !position.is_attacked_by(king_square, passive_player);
695 position.undo_move(move_info, cache);
696 is_legal
697 })
698 .for_each(|legal_move| legal_moves.push(legal_move));
699
700 legal_moves
701 }
702
703 fn generate_legal_no_check_moves(&self) -> MoveList {
705 let mut legal_moves = MoveList::new();
714
715 let king = self.pieces[(self.player, King)];
716 let king_square = king.get_lowest_square().unwrap();
717 let passive_player = !self.player;
718 let us = self.pieces.color_occupied(self.player);
719 let them = self.pieces.color_occupied(passive_player);
720 let occupied = us | them;
721 let attacked = self.attacks(passive_player, occupied);
722
723 let (absolute_pins, _pinned_moves) = {
724 let queens = self.pieces[(passive_player, Queen)];
725 let rooks = self.pieces[(passive_player, Rook)];
726 let bishops = self.pieces[(passive_player, Bishop)];
727
728 mg::absolute_pins(king_square, us, them, queens | rooks, queens | bishops)
729 };
730
731 let queens = self.pieces[(self.player, Queen)];
734 let rooks = self.pieces[(self.player, Rook)];
735 let bishops = self.pieces[(self.player, Bishop)];
736 let knights = self.pieces[(self.player, Knight)];
737 let pawns = self.pieces[(self.player, Pawn)];
738
739 let knights_free = knights & !absolute_pins;
741 let bishops_free = bishops & !absolute_pins;
742 let queens_free = queens & !absolute_pins;
743 let rooks_free = rooks & !absolute_pins;
744 mg::knight_pseudo_moves(&mut legal_moves, knights_free, us);
745 mg::bishop_pseudo_moves(&mut legal_moves, bishops_free, occupied, us);
746 mg::queen_pseudo_moves(&mut legal_moves, queens_free, occupied, us);
747 mg::rook_pseudo_moves(&mut legal_moves, rooks_free, occupied, us);
748
749 let mut king_tos = mg::king_attacks(king);
751 king_tos.remove(&us);
752 king_tos.remove(&attacked);
753 for to in king_tos {
754 legal_moves.push(Move::new(king_square, to, None));
755 }
756
757 let mut pseudo_moves = MoveList::new();
759 let bishops_pinned = bishops & absolute_pins;
760 let rooks_pinned = rooks & absolute_pins;
761 let queens_pinned = queens & absolute_pins;
762
763 mg::queen_pseudo_moves(&mut pseudo_moves, queens_pinned, occupied, us);
764 mg::rook_pseudo_moves(&mut pseudo_moves, rooks_pinned, occupied, us);
765 mg::bishop_pseudo_moves(&mut pseudo_moves, bishops_pinned, occupied, us);
766 mg::pawn_pseudo_moves(
767 &mut pseudo_moves,
768 pawns,
769 self.player,
770 occupied,
771 them,
772 self.en_passant,
773 );
774
775 let mut position = self.clone();
776 let cache = position.cache();
777 pseudo_moves
778 .into_iter()
779 .filter(|pseudo_move| {
780 let move_info = position.do_move(*pseudo_move);
781 let is_legal = !position.is_attacked_by(king_square, passive_player);
782 position.undo_move(move_info, cache);
783 is_legal
784 })
785 .for_each(|legal_move| legal_moves.push(legal_move));
786
787 mg::legal_castling_moves(
792 &mut legal_moves,
793 self.player,
794 self.castling,
795 occupied,
796 attacked,
797 );
798
799 legal_moves
800 }
801
802 }
838
839impl Default for Position {
841 fn default() -> Self {
842 Self::start_position()
843 }
844}
845
846impl Display for Position {
848 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
849 write!(f, "{}\n Fen: {}\n", self.pieces, self.to_fen())
851 }
852}
853
854#[cfg(test)]
855mod tests {
856 use super::*;
857
858 #[test]
859 fn pretty_print_position() {
860 let start_pos = Position::start_position();
861 println!("{}", start_pos);
862 }
863
864 #[test]
865 fn do_move_with_legal_move() {
866 let move1 = Move::new(E2, E4, None);
867 let move1_piece = Piece::new(White, Pawn);
868 let mut position = Position::start_position();
869 position.do_move(move1);
870 assert!(position.pieces[&move1_piece].has_square(E4));
871 assert!(!position.pieces[&move1_piece].has_square(E2));
872 }
873
874 #[test]
875 fn undo_move_with_legal_move() {
876 {
877 let pos = Position::start_position();
879 let cache = pos.cache();
880 let mut pos_moved = pos.clone();
881 let move_ = Move::new(E2, E4, None);
882 let move_info = pos_moved.do_move(move_);
883 pos_moved.undo_move(move_info, cache);
884 assert_eq!(pos, pos_moved);
885 assert_eq!(Move::from(move_info), move_);
886 assert_eq!(move_info.piece_kind, Pawn);
887 assert_eq!(move_info.move_kind, MoveKind::Quiet);
888 assert_eq!(cache.castling, Castling::ALL);
889 assert_eq!(cache.en_passant, None);
890 }
891 {
892 let pos = Position::parse_fen(
894 "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3",
895 )
896 .unwrap();
897 let mut pos_moved = pos.clone();
898 let cache = pos_moved.cache();
899 let move_ = Move::new(E5, F6, None);
900 let move_info = pos_moved.do_move(move_);
901 pos_moved.undo_move(move_info, cache);
902 assert_eq!(pos, pos_moved);
903 assert_eq!(Move::from(move_info), move_);
904 assert_eq!(move_info.piece_kind, Pawn);
905 assert_eq!(move_info.move_kind, MoveKind::EnPassant);
906 assert_eq!(cache.castling, Castling::ALL);
907 assert_eq!(cache.en_passant, Some(F6));
908 }
909 {
910 let pos = Position::parse_fen(
912 "rn1qkbnr/ppp3pp/5p2/8/2Bp2b1/5N2/PPPP1PPP/RNBQK2R w KQkq - 2 6",
913 )
914 .unwrap();
915 let mut pos_moved = pos.clone();
916 let cache = pos_moved.cache();
917 let move_ = Move::new(E1, G1, None);
918 let move_info = pos_moved.do_move(move_);
919 pos_moved.undo_move(move_info, cache);
920 assert_eq!(pos, pos_moved);
921 assert_eq!(Move::from(move_info), move_);
922 assert_eq!(move_info.piece_kind, King);
923 assert_eq!(move_info.move_kind, MoveKind::Castle);
924 assert_eq!(cache.castling, Castling::ALL);
925 assert_eq!(cache.en_passant, None);
926 }
927 }
928
929 #[test]
930 fn king_checks() {
931 let check1_1 = Position::parse_fen("8/8/8/8/3K3r/8/8/8 w - - 0 1").unwrap();
932 let check1_2 =
933 Position::parse_fen("rnb1kbnr/ppp1pppp/8/3p4/1qPPP3/8/PP3PPP/RNBQKBNR w KQkq - 1 4")
934 .unwrap();
935 let check2_1 = Position::parse_fen("3q4/8/4b3/3k4/4P1n1/8/3Q4/2R5 b - - 0 1").unwrap();
936 let check4_1 =
937 Position::parse_fen("6b1/2r1r3/pp4n1/4K2r/2p5/7p/1p1q2q1/4r2r w - - 0 1").unwrap();
938 let check5_1 = Position::parse_fen("4r3/8/2b2n2/5p2/4K3/5q2/8/8 w - - 0 1").unwrap();
939 let check5_2 = Position::parse_fen("8/8/5n2/3brp2/Q3K2q/5P2/3N4/1B2R3 w - - 0 1").unwrap();
940
941 assert_eq!(check1_1.num_active_king_checks(), 1);
942 assert_eq!(check1_2.num_active_king_checks(), 1);
943 assert_eq!(check2_1.num_active_king_checks(), 2);
944 assert_eq!(check4_1.num_active_king_checks(), 4);
945 assert_eq!(check5_1.num_active_king_checks(), 5);
946 assert_eq!(check5_2.num_active_king_checks(), 5);
947 }
948
949 #[test]
950 fn legal_double_check_moves() {
951 let pos0_1 = Position::parse_fen("4R2k/7p/6p1/8/8/2B5/8/1K6 b - - 0 1").unwrap();
952 let pos1_1 = Position::parse_fen("8/5K2/8/3Qk3/4R3/8/8/8 b - - 0 1").unwrap();
953 let pos3_1 = Position::parse_fen("8/2k5/8/8/4Kr2/4r3/8/8 w - - 0 1").unwrap();
954
955 let moves0_1 = pos0_1.generate_legal_double_check_moves();
956 let moves1_1 = pos1_1.generate_legal_double_check_moves();
957 let moves3_1 = pos3_1.generate_legal_double_check_moves();
958 assert_eq!(moves0_1.len(), 0);
959 assert_eq!(moves1_1.len(), 1);
960 assert_eq!(moves3_1.len(), 3);
961
962 assert!(moves1_1.contains(&Move::new(E5, D5, None)));
963
964 assert!(moves3_1.contains(&Move::new(E4, D5, None)));
965 assert!(moves3_1.contains(&Move::new(E4, E3, None)));
966 assert!(moves3_1.contains(&Move::new(E4, F4, None)));
967 }
968
969 #[test]
970 fn checkmated() {
971 {
972 let pos1 =
973 Position::parse_fen("rnb1k1nr/ppp2ppp/4p3/8/P7/1Pb3BQ/3qPPPP/4KBNR w Kkq - 0 14")
974 .unwrap();
975 let moves1 = pos1.get_legal_moves();
976 assert_eq!(moves1.len(), 0);
977 }
978
979 {
980 let pos2 =
981 Position::parse_fen("r4r1k/1b3p1p/pp2pQ2/2p5/P1B3R1/3P3P/2q3P1/7K b - - 0 26")
982 .unwrap();
983 let moves2 = pos2.get_legal_moves();
984 assert_eq!(moves2.len(), 0);
985 }
986 }
987
988 #[test]
989 fn stalemated() {
990 let pos1 = Position::parse_fen("8/8/8/8/p7/P3k3/4p3/4K3 w - - 1 2").unwrap();
991
992 let moves1 = pos1.get_legal_moves();
993 assert_eq!(moves1.len(), 0);
994 }
995
996 #[test]
997 fn color_flipped_eq() {
998 let w_giuoco = Position::parse_fen(
1001 "r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 6 4",
1002 )
1003 .unwrap();
1004 let b_giuoco = Position::parse_fen(
1005 "rnbqk2r/pppp1ppp/5n2/2b1p3/2B1P3/2N5/PPPP1PPP/R1BQK1NR b KQkq - 6 4",
1006 )
1007 .unwrap();
1008
1009 assert_eq!(w_giuoco.color_flip(), b_giuoco);
1010 assert_eq!(b_giuoco.color_flip(), w_giuoco);
1011 }
1012
1013 #[test]
1014 fn moves_played() {
1015 let mut pos = Position::start_position();
1016 let line = vec![
1017 Move::new(D2, D4, None),
1018 Move::new(D7, D5, None),
1019 Move::new(E2, E4, None),
1020 Move::new(D5, E4, None),
1021 Move::new(B1, C3, None),
1022 Move::new(F7, F5, None),
1023 Move::new(C1, F4, None),
1024 ];
1025
1026 assert_eq!(pos.moves_played(), 0);
1027
1028 for (moves_played, move_) in line.into_iter().enumerate() {
1029 let res = pos.do_legal_move(move_);
1030 assert!(res.is_some());
1031 assert_eq!(pos.moves_played(), moves_played as MoveCount + 1);
1032 }
1033 }
1034}