1use std::{collections::HashMap, fmt};
2
3use regex::Regex;
4
5use crate::{
6 core::{
7 piece_movement, CastleType, Color, DrawReason, GameStatus, Move, MoveType, PGNTree, Piece,
8 PieceType, Position, WinReason,
9 },
10 errors::{FenError, MoveError},
11 parsing::fen::parse_fen,
12};
13
14use super::board::Board;
15
16#[derive(Debug, Clone)]
30pub struct Game {
31 capture_king: bool,
32 pub board: Board,
34 pub is_white_turn: bool,
36 pub halfmove_clock: u32,
38 pub fullmove_number: u32,
40 pub en_passant: Option<Position>,
42 pub castling_rights: u8,
44 pub starting_fen: String,
46 pub history: PGNTree<Move>,
48 pub prev_positions: HashMap<String, u32>,
50 pub status: GameStatus,
52}
53
54impl Default for Game {
55 fn default() -> Game {
65 let fen = String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
66 let mut map = HashMap::new();
67 map.insert(
68 String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -"),
69 1,
70 );
71
72 Game {
73 board: Board::default(),
74 is_white_turn: true,
75 castling_rights: 0b1111,
76 en_passant: None,
77 halfmove_clock: 0,
78 fullmove_number: 1,
79 starting_fen: fen,
80 history: PGNTree::default(),
81 capture_king: false,
82 prev_positions: map,
83 status: GameStatus::InProgress,
84 }
85 }
86}
87
88impl Game {
89 pub fn new(fen: &str, capture_king: bool) -> Result<Game, FenError> {
107 let mut game = Game::from_fen(fen)?;
108
109 game.capture_king = capture_king;
110
111 Ok(game)
112 }
113
114 pub fn from_fen(fen: &str) -> Result<Game, FenError> {
131 let mut game = parse_fen(fen)?;
132 game.history.add_metadata("FEN", fen).unwrap();
133 game.history
134 .add_metadata("Variant", "From Position")
135 .unwrap();
136 Ok(game)
137 }
138
139 pub fn get_variant(&self) -> String {
153 match self.history.variant.clone() {
154 Some(variant) => variant,
155 None => String::from("Standard"),
156 }
157 }
158
159 pub fn move_piece(&mut self, move_str: &str) -> Result<GameStatus, MoveError> {
177 if self.status != GameStatus::InProgress {
178 return Ok(self.status);
179 }
180
181 let (piece_type, start_pos_info, end_pos, move_type) = self.parse_move(move_str)?;
182 let color = if self.is_white_turn {
183 Color::White
184 } else {
185 Color::Black
186 };
187
188 let ambiguity =
189 self.move_ambiguity(piece_type, color, start_pos_info, &end_pos, &move_type);
190
191 let positions = self.find_pieces(piece_type, color, start_pos_info, &end_pos, &move_type);
192
193 let start_pos = match positions.len() {
194 0 => return Err(MoveError::Illegal(move_str.to_string())),
195 1 => positions[0],
196 _ => {
197 return Err(MoveError::Ambiguous(move_str.to_string()));
198 }
199 };
200
201 let mut rook_start: Option<Position> = None;
202 let mut captured_piece: Option<PieceType> =
203 self.board.get_piece(&end_pos).map(|p| p.piece_type);
204
205 match self.board.move_piece(&start_pos, &end_pos) {
206 Ok(_) => {
207 match &move_type {
208 MoveType::Castle { side } => {
209 let rook_end = match side {
210 CastleType::KingSide => Position {
211 col: 5,
212 row: start_pos.row,
213 },
214 CastleType::QueenSide => Position {
215 col: 3,
216 row: start_pos.row,
217 },
218 };
219
220 let rooks = self.board.find(PieceType::Rook, color);
221
222 for rook in rooks {
223 match side {
224 CastleType::KingSide => {
225 if rook.col > start_pos.col && rook.row == start_pos.row {
226 rook_start = Some(rook);
227 self.board.move_piece(&rook, &rook_end).unwrap();
228 break;
229 }
230 }
231 CastleType::QueenSide => {
232 if rook.col < start_pos.col && rook.row == start_pos.row {
233 rook_start = Some(rook);
234 self.board.move_piece(&rook, &rook_end).unwrap();
235 break;
236 }
237 }
238 }
239 }
240 }
241 MoveType::EnPassant => {
242 let captured_pos = Position {
243 col: end_pos.col,
244 row: start_pos.row,
245 };
246 captured_piece =
247 Some(self.board.delete_piece(&captured_pos).unwrap().piece_type);
248 }
249 MoveType::Normal {
250 capture: _,
251 promotion: Some(piece_type),
252 } => {
253 self.board.delete_piece(&end_pos).unwrap();
254 self.board
255 .set_piece(Piece::new(color, piece_type.to_owned()), &end_pos)
256 .unwrap();
257 }
258 _ => {}
259 }
260
261 self.update_rules(
262 Move::new(
263 Piece::new(color, piece_type),
264 start_pos,
265 end_pos,
266 move_type,
267 captured_piece,
268 rook_start,
269 ambiguity,
270 false,
271 false,
272 )
273 .map_err(|e| MoveError::Invalid(e.error))?,
274 );
275
276 Ok(self.status)
277 }
278 Err(_) => unreachable!(),
279 }
280 }
281
282 pub fn fen(&self) -> String {
296 let mut fen = String::new();
297 fen.push_str(&self.board.to_string());
298 fen.push(' ');
299 fen.push(if self.is_white_turn { 'w' } else { 'b' });
300 fen.push(' ');
301 if self.castling_rights == 0 {
302 fen.push('-');
303 } else {
304 if self.castling_rights & 0b1000 != 0 {
305 fen.push('K');
306 }
307 if self.castling_rights & 0b0100 != 0 {
308 fen.push('Q');
309 }
310 if self.castling_rights & 0b0010 != 0 {
311 fen.push('k');
312 }
313 if self.castling_rights & 0b0001 != 0 {
314 fen.push('q');
315 }
316 }
317 fen.push(' ');
318 fen.push_str(
319 &self
320 .en_passant
321 .as_ref()
322 .map_or(String::from("-"), |pos| pos.to_string()),
323 );
324 fen.push(' ');
325 fen.push_str(&self.halfmove_clock.to_string());
326 fen.push(' ');
327 fen.push_str(&self.fullmove_number.to_string());
328
329 fen
330 }
331
332 pub fn undo(&mut self) {
345 let mov = self.history.get_move();
346
347 if let None = mov {
348 return;
349 }
350
351 let mov = mov.unwrap();
352 let info = self.history.get_move_info().unwrap();
353
354 self.board.move_piece(&mov.to, &mov.from).unwrap();
355
356 match mov.move_type {
357 MoveType::Normal {
358 capture: true,
359 promotion,
360 } => {
361 self.board
362 .set_piece(
363 Piece::new(mov.piece.color.opposite(), mov.captured_piece.unwrap()),
364 &mov.to,
365 )
366 .unwrap();
367 if let Some(_) = promotion {
368 self.board.delete_piece(&mov.from).unwrap();
369 self.board
370 .set_piece(Piece::new(mov.piece.color, PieceType::Pawn), &mov.from)
371 .unwrap();
372 }
373 }
374 MoveType::EnPassant => {
375 let captured_pos = Position {
376 col: mov.to.col,
377 row: mov.from.row,
378 };
379 self.board
380 .set_piece(
381 Piece::new(mov.piece.color.opposite(), mov.captured_piece.unwrap()),
382 &captured_pos,
383 )
384 .unwrap();
385 }
386 MoveType::Castle { side } => {
387 let rook_from = mov.rook_from.unwrap();
388 let rook_to = match side {
389 CastleType::KingSide => Position {
390 col: 5,
391 row: mov.to.row,
392 },
393 CastleType::QueenSide => Position {
394 col: 3,
395 row: mov.to.row,
396 },
397 };
398 self.board.move_piece(&rook_to, &rook_from).unwrap();
399 }
400 _ => {}
401 }
402
403 self.is_white_turn = !self.is_white_turn;
404
405 self.halfmove_clock = info.halfmove_clock;
406 self.fullmove_number = info.fullmove_number;
407 self.en_passant = info.en_passant;
408 self.castling_rights = info.castling_rights;
409 self.status = info.game_status;
410 self.prev_positions = info.prev_positions;
411
412 self.history.prev_move();
413 }
414
415 pub fn redo(&mut self) {
430 let mov = self.history.next_move();
431
432 if let None = mov {
433 return;
434 }
435
436 let mov = mov.unwrap();
437 self.history.prev_move();
438
439 self.move_piece(mov.to_string().as_str()).unwrap();
440 }
441
442 pub fn redo_nth(&mut self, n: u32) {
464 let mov = self.history.next_move_variant(n);
465
466 if let None = mov {
467 return;
468 }
469
470 let mov = mov.unwrap();
471
472 self.move_piece(mov.to_string().as_str()).unwrap();
473 }
474
475 pub fn get_last_move(&self) -> Option<Move> {
492 self.history.get_move()
493 }
494
495 pub fn get_piece_at(&self, pos: Position) -> Option<Piece> {
514 self.board.get_piece(&pos)
515 }
516
517 pub fn start(&mut self) {
532 while self.history.has_prev_move() {
533 self.undo();
534 }
535 }
536
537 pub fn end(&mut self) {
554 while self.history.has_next_move() {
555 self.redo();
556 }
557 }
558
559 pub fn pgn(&self) -> String {
575 self.history.pgn()
576 }
577
578 pub fn parse_move(
588 &self,
589 move_str: &str,
590 ) -> Result<(PieceType, (Option<u8>, Option<u8>), Position, MoveType), MoveError> {
591 let mut move_str = move_str.to_string();
592 let re =
593 Regex::new(r"^([PNBRQK]?[a-h]?[1-8]?x?[a-h][1-8](=[NBRQ])?|O(-O){1,2})[+#]?").unwrap();
594 if !re.is_match(move_str.as_str()) || move_str.starts_with('x') {
595 return Err(MoveError::Invalid(move_str));
596 }
597
598 if move_str.chars().last().unwrap() == '+' || move_str.chars().last().unwrap() == '#' {
599 move_str.remove(move_str.len() - 1);
600 }
601
602 if move_str.starts_with('O') {
603 let castle_side;
604 let end_pos;
605 if move_str == "O-O" {
606 if (self.castling_rights & 0b1000 == 0 || !self.is_white_turn)
607 && (self.castling_rights & 0b0010 == 0 || self.is_white_turn)
608 {
609 return Err(MoveError::Invalid(move_str));
610 }
611 castle_side = CastleType::KingSide;
612 end_pos = if self.is_white_turn {
613 Position::from_string("g1").unwrap()
614 } else {
615 Position::from_string("g8").unwrap()
616 };
617 } else if move_str == "O-O-O" {
618 if (self.castling_rights & 0b0100 == 0 || !self.is_white_turn)
619 && (self.castling_rights & 0b0001 == 0 || self.is_white_turn)
620 {
621 return Err(MoveError::Invalid(move_str));
622 }
623 castle_side = CastleType::QueenSide;
624
625 end_pos = if self.is_white_turn {
626 Position::from_string("c1").unwrap()
627 } else {
628 Position::from_string("c8").unwrap()
629 };
630 } else {
631 unreachable!()
632 }
633 return Ok((
634 PieceType::King,
635 (None, None),
636 end_pos,
637 MoveType::Castle { side: castle_side },
638 ));
639 } else {
640 let start_col;
641 let start_row;
642 let end_pos;
643 let promotion;
644 let end_pos_index;
645 let piece = match move_str.chars().next().unwrap() {
646 'N' => PieceType::Knight,
647 'B' => PieceType::Bishop,
648 'R' => PieceType::Rook,
649 'Q' => PieceType::Queen,
650 'K' => PieceType::King,
651 'P' => PieceType::Pawn,
652 _ => {
653 move_str = format!("P{}", move_str);
654 PieceType::Pawn
655 }
656 };
657
658 if move_str.contains('=') {
659 if "NBRQK".contains(move_str.chars().next().unwrap()) {
660 return Err(MoveError::Invalid(move_str));
661 }
662
663 promotion = Some(PieceType::from_char(move_str.chars().last().unwrap()).unwrap());
664 end_pos = Position::from_string(&move_str[move_str.len() - 4..move_str.len() - 2])
665 .unwrap();
666 end_pos_index = move_str.len() - 4;
667
668 if end_pos.row != 0 && end_pos.row != 7 {
669 return Err(MoveError::Invalid(move_str));
670 }
671 } else {
672 end_pos = Position::from_string(&move_str[move_str.len() - 2..]).unwrap();
673 end_pos_index = move_str.len() - 2;
674 promotion = None;
675 }
676
677 let capture = move_str.contains("x")
678 || self.board.is_ocupied(&end_pos)
679 || self.en_passant.map_or_else(|| false, |pos| pos == end_pos);
680
681 if end_pos_index > 1 {
682 if "abcdefgh".contains(move_str.chars().nth(1).unwrap()) {
683 start_col = Some(move_str.chars().nth(1).unwrap() as u8 - 'a' as u8);
684 if "12345678".contains(move_str.chars().nth(2).unwrap()) {
685 start_row = Some(move_str.chars().nth(2).unwrap() as u8 - '1' as u8);
686 } else {
687 start_row = None;
688 }
689 } else if "12345678".contains(move_str.chars().nth(1).unwrap()) {
690 start_col = None;
691 start_row = Some(move_str.chars().nth(1).unwrap() as u8 - '1' as u8);
692 } else {
693 start_col = None;
694 start_row = None;
695 }
696 } else {
697 start_col = None;
698 start_row = None;
699 }
700
701 if capture
702 && piece == PieceType::Pawn
703 && self.en_passant.map_or_else(|| false, |pos| pos == end_pos)
704 {
705 return Ok((
706 PieceType::Pawn,
707 (start_col, start_row),
708 end_pos,
709 MoveType::EnPassant,
710 ));
711 }
712
713 return Ok((
714 piece,
715 (start_col, start_row),
716 end_pos,
717 MoveType::Normal { capture, promotion },
718 ));
719 }
720 }
721
722 pub fn is_legal(
734 &self,
735 piece: &Piece,
736 start_pos: &Position,
737 end_pos: &Position,
738 move_type: &MoveType,
739 ) -> bool {
740 if piece.piece_type != PieceType::Knight && piece.piece_type != PieceType::King {
741 match self.board.piece_between(start_pos, end_pos) {
742 Ok(true) | Err(_) => return false,
743 Ok(false) => (),
744 }
745 }
746 if move_type == &MoveType::EnPassant && piece.piece_type != PieceType::Pawn {
747 return false;
748 }
749
750 if let MoveType::Castle { side } = move_type {
751 return self.is_castle_legal(piece, start_pos, end_pos, side);
752 }
753 if !piece_movement(piece, start_pos, end_pos) {
754 return false;
755 }
756 if let MoveType::Normal {
757 capture: true,
758 promotion: _,
759 } = move_type
760 {
761 if !self.board.is_ocupied(end_pos)
762 || self.board.get_piece(end_pos).unwrap().color == piece.color
763 || (piece.piece_type == PieceType::Pawn && start_pos.col == end_pos.col)
764 {
765 return false;
766 }
767 }
768 if let MoveType::Normal {
769 capture: false,
770 promotion: _,
771 } = move_type
772 {
773 if self.board.is_ocupied(end_pos) {
774 return false;
775 }
776 }
777 if piece.piece_type == PieceType::Pawn {
778 if matches!(
779 move_type,
780 MoveType::Normal {
781 capture: false,
782 promotion: _
783 }
784 ) && (self.board.get_piece(end_pos).is_some() || start_pos.col != end_pos.col)
785 {
786 return false;
787 }
788
789 if let MoveType::Normal {
790 capture: _,
791 promotion: Some(_),
792 } = move_type
793 {
794 if (piece.color == Color::White && end_pos.row != 7)
795 || (piece.color == Color::Black && end_pos.row != 0)
796 {
797 return false;
798 }
799 }
800 if let MoveType::Normal {
801 capture: _,
802 promotion: None,
803 } = move_type
804 {
805 if (piece.color == Color::White && end_pos.row == 7)
806 || (piece.color == Color::Black && end_pos.row == 0)
807 {
808 return false;
809 }
810 }
811
812 if let MoveType::EnPassant = move_type {
813 if self.en_passant.map_or_else(|| false, |pos| pos != *end_pos)
814 || ((piece.color == Color::White && end_pos.row != 5)
815 || (piece.color == Color::Black && end_pos.row != 2))
816 {
817 return false;
818 }
819 }
820 }
821
822 if self.capture_king {
823 return true;
824 }
825
826 let mut board = self.board.clone();
827 board.move_piece(start_pos, end_pos).unwrap();
828
829 let king = board.find(PieceType::King, piece.color)[0];
830
831 return !board.is_attacked(king, piece.color.opposite());
832 }
833
834 pub fn get_legal_moves(&self, pos: Position) -> Vec<Move> {
855 if self.board.get_piece(&pos).is_none() {
856 return vec![];
857 }
858
859 let piece = self.board.get_piece(&pos).unwrap();
860 if piece.color
861 != if self.is_white_turn {
862 Color::White
863 } else {
864 Color::Black
865 }
866 {
867 return vec![];
868 }
869
870 let mut moves = Vec::new();
871
872 for col in 0..8 {
873 for row in 0..8 {
874 let end_pos = Position { col, row };
875 let move_type = MoveType::Normal {
876 capture: self
877 .board
878 .get_piece(&end_pos)
879 .map_or_else(|| false, |p| p.color != piece.color),
880 promotion: None,
881 };
882 let en_passant_move_type = MoveType::EnPassant;
883 let promotion_move_type = MoveType::Normal {
884 capture: self
885 .board
886 .get_piece(&end_pos)
887 .map_or_else(|| false, |p| p.color != piece.color),
888 promotion: Some(PieceType::Queen),
889 };
890
891 if self.is_legal(&piece, &pos, &end_pos, &move_type) {
892 moves.push(
893 Move::new(
894 piece.clone(),
895 pos,
896 end_pos,
897 move_type,
898 self.board.get_piece(&end_pos).map(|p| p.piece_type),
899 None,
900 (false, false),
901 false,
902 false,
903 )
904 .unwrap(),
905 );
906 } else if self.is_legal(&piece, &pos, &end_pos, &en_passant_move_type) {
907 moves.push(
908 Move::new(
909 piece.clone(),
910 pos,
911 end_pos,
912 en_passant_move_type,
913 self.board.get_piece(&end_pos).map(|p| p.piece_type),
914 None,
915 (false, false),
916 false,
917 false,
918 )
919 .unwrap(),
920 );
921 } else if self.is_legal(&piece, &pos, &end_pos, &promotion_move_type) {
922 moves.push(
923 Move::new(
924 piece.clone(),
925 pos,
926 end_pos,
927 promotion_move_type,
928 self.board.get_piece(&end_pos).map(|p| p.piece_type),
929 None,
930 (false, false),
931 false,
932 false,
933 )
934 .unwrap(),
935 );
936 } else if piece.piece_type == PieceType::King {
937 if self.is_legal(
938 &piece,
939 &pos,
940 &end_pos,
941 &MoveType::Castle {
942 side: CastleType::KingSide,
943 },
944 ) {
945 moves.push(
946 Move::new(
947 piece.clone(),
948 pos,
949 end_pos,
950 MoveType::Castle {
951 side: CastleType::KingSide,
952 },
953 self.board.get_piece(&end_pos).map(|p| p.piece_type),
954 self.get_castle_rook_pos(CastleType::KingSide),
955 (false, false),
956 false,
957 false,
958 )
959 .unwrap(),
960 );
961 } else if self.is_legal(
962 &piece,
963 &pos,
964 &end_pos,
965 &MoveType::Castle {
966 side: CastleType::QueenSide,
967 },
968 ) {
969 moves.push(
970 Move::new(
971 piece.clone(),
972 pos,
973 end_pos,
974 MoveType::Castle {
975 side: CastleType::QueenSide,
976 },
977 self.board.get_piece(&end_pos).map(|p| p.piece_type),
978 self.get_castle_rook_pos(CastleType::QueenSide),
979 (false, false),
980 false,
981 false,
982 )
983 .unwrap(),
984 );
985 }
986 }
987 }
988 }
989
990 moves
991 }
992
993 pub fn get_castle_rook_pos(&self, side: CastleType) -> Option<Position> {
1012 match side {
1013 CastleType::KingSide => {
1014 if self.is_white_turn && self.castling_rights & 0b1000 == 0 {
1015 return None;
1016 }
1017 if !self.is_white_turn && self.castling_rights & 0b0010 == 0 {
1018 return None;
1019 }
1020 }
1021 CastleType::QueenSide => {
1022 if self.is_white_turn && self.castling_rights & 0b0100 == 0 {
1023 return None;
1024 }
1025 if !self.is_white_turn && self.castling_rights & 0b0001 == 0 {
1026 return None;
1027 }
1028 }
1029 }
1030
1031 let color = if self.is_white_turn {
1032 Color::White
1033 } else {
1034 Color::Black
1035 };
1036
1037 let step = match side {
1038 CastleType::KingSide => 1,
1039 CastleType::QueenSide => -1,
1040 };
1041 let king_pos = self.board.find(PieceType::King, color)[0];
1042
1043 let mut col = king_pos.col as i8 + step;
1044 while col < 8 && col >= 0 {
1045 if let Some(piece) = self.board.get_piece(&Position {
1046 col: col as u8,
1047 row: king_pos.row,
1048 }) {
1049 if piece.piece_type == PieceType::Rook && piece.color == color {
1050 return Some(Position {
1051 col: col as u8,
1052 row: king_pos.row,
1053 });
1054 }
1055 }
1056 if step > 0 {
1057 col += step;
1058 } else {
1059 col -= -step;
1060 }
1061 }
1062 unreachable!()
1064 }
1065
1066 pub fn check(&self) -> bool {
1085 if self.capture_king {
1086 return false;
1087 }
1088 let color = if self.is_white_turn {
1089 Color::White
1090 } else {
1091 Color::Black
1092 };
1093
1094 if self
1095 .board
1096 .is_attacked(self.board.find(PieceType::King, color)[0], color.opposite())
1097 {
1098 return true;
1099 }
1100 false
1101 }
1102
1103 pub fn checkmate(&self) -> bool {
1126 if self.capture_king {
1127 let color = if self.is_white_turn {
1128 Color::White
1129 } else {
1130 Color::Black
1131 };
1132 let kings = self.board.find(PieceType::King, color);
1133 return kings.is_empty();
1134 }
1135 if !self.check() {
1136 return false;
1137 }
1138
1139 !self.has_legal_moves()
1140 }
1141
1142 pub fn stalemate(&self) -> bool {
1157 if self.capture_king {
1158 return false;
1159 }
1160 if self.check() {
1161 return false;
1162 }
1163
1164 !self.has_legal_moves()
1165 }
1166
1167 pub fn insufficient_material(&self) -> bool {
1181 let white_pieces = self.board.find_all(Color::White);
1182 let black_pieces = self.board.find_all(Color::Black);
1183
1184 if white_pieces.len() > 2 || black_pieces.len() > 2 {
1185 return false;
1186 }
1187
1188 let pieces = [white_pieces, black_pieces].concat();
1189 for pos in pieces {
1190 let piece = self.board.get_piece(&pos).unwrap();
1191 match piece.piece_type {
1192 PieceType::Pawn | PieceType::Rook | PieceType::Queen => return false,
1193 _ => {}
1194 }
1195 }
1196
1197 true
1198 }
1199
1200 pub fn resign(&mut self, color: Color) {
1216 if self.status != GameStatus::InProgress {
1217 return;
1218 }
1219 self.status = if color == Color::White {
1220 GameStatus::BlackWins(WinReason::Resignation)
1221 } else {
1222 GameStatus::WhiteWins(WinReason::Resignation)
1223 };
1224 }
1225
1226 pub fn lost_on_time(&mut self, color: Color) {
1243 if self.status != GameStatus::InProgress {
1244 return;
1245 }
1246 self.status = if color == Color::White {
1247 GameStatus::BlackWins(WinReason::Time)
1248 } else {
1249 GameStatus::WhiteWins(WinReason::Time)
1250 };
1251 }
1252
1253 pub fn draw_by_agreement(&mut self) {
1266 if self.status != GameStatus::InProgress {
1267 return;
1268 }
1269 self.status = GameStatus::Draw(DrawReason::Agreement);
1270 }
1271
1272 fn update_rules(&mut self, mut mov: Move) {
1278 self.is_white_turn = !self.is_white_turn;
1279
1280 mov.check = self.check();
1281 mov.checkmate = self.checkmate();
1282
1283 self.history.add_move(
1284 mov.clone(),
1285 self.halfmove_clock,
1286 self.fullmove_number,
1287 self.en_passant,
1288 self.castling_rights,
1289 self.status,
1290 self.prev_positions.clone(),
1291 );
1292
1293 if matches!(mov.move_type, MoveType::Castle { .. })
1294 || mov.piece.piece_type == PieceType::King
1295 {
1296 self.castling_rights &= match mov.piece.color {
1297 Color::White => 0b0011,
1298 Color::Black => 0b1100,
1299 };
1300 }
1301 if mov.piece.piece_type == PieceType::Rook {
1302 let king = self.board.find(PieceType::King, mov.piece.color)[0];
1303 match mov.piece.color {
1304 Color::White => {
1305 if mov.from.col < king.col {
1306 self.castling_rights &= 0b1011;
1307 } else if mov.from.col > king.col {
1308 self.castling_rights &= 0b0111;
1309 }
1310 }
1311 Color::Black => {
1312 if mov.from.col < king.col {
1313 self.castling_rights &= 0b1110;
1314 } else if mov.from.col > king.col {
1315 self.castling_rights &= 0b1101;
1316 }
1317 }
1318 }
1319 }
1320 if let Some(PieceType::Rook) = mov.captured_piece {
1321 let king = self.board.find(PieceType::King, mov.piece.color)[0];
1322 match mov.piece.color.opposite() {
1323 Color::White => {
1324 if mov.to.col < king.col {
1325 self.castling_rights &= 0b1011;
1326 } else if mov.to.col > king.col {
1327 self.castling_rights &= 0b0111;
1328 }
1329 }
1330 Color::Black => {
1331 if mov.to.col < king.col {
1332 self.castling_rights &= 0b1110;
1333 } else if mov.to.col > king.col {
1334 self.castling_rights &= 0b1101;
1335 }
1336 }
1337 }
1338 }
1339 if matches!(mov.move_type, MoveType::Normal { capture: true, .. })
1340 || mov.piece.piece_type == PieceType::Pawn
1341 {
1342 self.halfmove_clock = 0;
1343 } else {
1344 self.halfmove_clock += 1;
1345 }
1346 if mov.piece.piece_type == PieceType::Pawn && (&mov.from - &mov.to).1.abs() == 2 {
1347 let positions = self.board.find(PieceType::Pawn, mov.piece.color.opposite());
1348 let en_passant_pos = Position {
1349 col: mov.from.col,
1350 row: (mov.from.row + mov.to.row) / 2,
1351 };
1352
1353 let can_en_passant = positions.iter().any(|pos| {
1354 let piece = self.board.get_piece(&pos).unwrap();
1355 piece_movement(&piece, &pos, &en_passant_pos)
1356 });
1357
1358 if can_en_passant {
1359 self.en_passant = Some(en_passant_pos);
1360 } else {
1361 self.en_passant = None;
1362 }
1363 } else {
1364 self.en_passant = None;
1365 }
1366 if self.is_white_turn {
1367 self.fullmove_number += 1;
1368 }
1369
1370 let current_pos = self.get_fen_reduced();
1371 let posistions = *self.prev_positions.get(¤t_pos).unwrap_or(&0);
1372
1373 self.prev_positions.insert(current_pos, posistions + 1);
1374
1375 if mov.checkmate {
1376 self.status = if self.is_white_turn {
1377 GameStatus::BlackWins(WinReason::Checkmate)
1378 } else {
1379 GameStatus::WhiteWins(WinReason::Checkmate)
1380 };
1381 } else if self.stalemate() {
1382 self.status = GameStatus::Draw(DrawReason::Stalemate);
1383 } else if self.insufficient_material() {
1384 self.status = GameStatus::Draw(DrawReason::InsufficientMaterial);
1385 } else if posistions == 2 {
1386 self.status = GameStatus::Draw(DrawReason::ThreefoldRepetition);
1387 } else if self.halfmove_clock >= 100 {
1388 self.status = GameStatus::Draw(DrawReason::FiftyMoveRule);
1389 } else {
1390 self.status = GameStatus::InProgress;
1391 };
1392
1393 self.history.game_over(self.status);
1394 }
1395
1396 fn find_pieces(
1409 &self,
1410 piece: PieceType,
1411 color: Color,
1412 start_pos: (Option<u8>, Option<u8>),
1413 end_pos: &Position,
1414 move_type: &MoveType,
1415 ) -> Vec<Position> {
1416 let mut positions = match move_type {
1417 MoveType::Normal {
1418 capture: _,
1419 promotion: _,
1420 } => self.board.find(piece, color),
1421 MoveType::EnPassant => self.board.find(PieceType::Pawn, color),
1422 MoveType::Castle { side: _ } => self.board.find(PieceType::King, color),
1423 };
1424
1425 if start_pos != (None, None) {
1426 positions = positions
1427 .iter()
1428 .filter(|pos| -> bool {
1429 let has_col = match start_pos {
1430 (Some(col), _) => pos.col == col,
1431 _ => true,
1432 };
1433 let has_row = match start_pos {
1434 (_, Some(row)) => pos.row == row,
1435 _ => true,
1436 };
1437 has_col && has_row
1438 })
1439 .cloned()
1440 .collect();
1441 }
1442
1443 let mut valid_positions = Vec::new();
1444 for pos in positions {
1445 if self.is_legal(
1446 &Piece {
1447 color,
1448 piece_type: piece,
1449 },
1450 &pos,
1451 &end_pos,
1452 &move_type,
1453 ) {
1454 valid_positions.push(pos);
1455 }
1456 }
1457
1458 return valid_positions;
1459 }
1460
1461 fn move_ambiguity(
1477 &self,
1478 piece: PieceType,
1479 color: Color,
1480 start_pos: (Option<u8>, Option<u8>),
1481 end_pos: &Position,
1482 move_type: &MoveType,
1483 ) -> (bool, bool) {
1484 match start_pos {
1485 (None, None) => (false, false),
1486 (Some(_), None) => {
1487 let positions = self.board.find(piece, color);
1488 let valid_positions = positions
1489 .iter()
1490 .filter(|pos| {
1491 self.is_legal(
1492 &Piece {
1493 color,
1494 piece_type: piece,
1495 },
1496 &pos,
1497 &end_pos,
1498 &move_type,
1499 )
1500 })
1501 .count();
1502 (valid_positions > 1, false)
1503 }
1504 (None, Some(_)) => {
1505 let positions = self.board.find(piece, color);
1506 let valid_positions = positions
1507 .iter()
1508 .filter(|pos| {
1509 self.is_legal(
1510 &Piece {
1511 color,
1512 piece_type: piece,
1513 },
1514 &pos,
1515 &end_pos,
1516 &move_type,
1517 )
1518 })
1519 .count();
1520 (false, valid_positions > 1)
1521 }
1522 (Some(col), Some(row)) => {
1523 let positions = self.board.find(piece, color);
1524 let col_ambiguity = positions
1525 .iter()
1526 .filter(|pos| pos.row == row)
1527 .filter(|pos| {
1528 self.is_legal(
1529 &Piece {
1530 color,
1531 piece_type: piece,
1532 },
1533 &pos,
1534 &end_pos,
1535 &move_type,
1536 )
1537 })
1538 .count()
1539 > 1;
1540 let row_ambiguity = positions
1541 .iter()
1542 .filter(|pos| pos.col == col)
1543 .filter(|pos| {
1544 self.is_legal(
1545 &Piece {
1546 color,
1547 piece_type: piece,
1548 },
1549 &pos,
1550 &end_pos,
1551 &move_type,
1552 )
1553 })
1554 .count()
1555 > 1;
1556 (col_ambiguity, row_ambiguity)
1557 }
1558 }
1559 }
1560
1561 fn is_castle_legal(
1573 &self,
1574 piece: &Piece,
1575 start_pos: &Position,
1576 end_pos: &Position,
1577 side: &CastleType,
1578 ) -> bool {
1579 if piece.piece_type != PieceType::King || start_pos.row != end_pos.row {
1580 return false;
1581 }
1582
1583 match side {
1584 CastleType::KingSide => {
1585 if end_pos.col != 6 {
1586 return false;
1587 }
1588 if piece.color == Color::White && self.castling_rights & 0b1000 == 0 {
1589 return false;
1590 } else if piece.color == Color::Black && self.castling_rights & 0b0010 == 0 {
1591 return false;
1592 }
1593 }
1594 CastleType::QueenSide => {
1595 if end_pos.col != 2 {
1596 return false;
1597 }
1598 if piece.color == Color::White && self.castling_rights & 0b0100 == 0 {
1599 return false;
1600 } else if piece.color == Color::Black && self.castling_rights & 0b0001 == 0 {
1601 return false;
1602 }
1603 }
1604 }
1605 let col_range = if end_pos.col < start_pos.col {
1606 end_pos.col..=start_pos.col
1607 } else {
1608 start_pos.col..=end_pos.col
1609 };
1610 for col in col_range {
1611 let new_pos = Position::new(col, start_pos.row).unwrap();
1612 if (&new_pos != start_pos && self.board.is_ocupied(&new_pos))
1613 || self.board.is_attacked(
1614 Position::new(col, start_pos.row).unwrap(),
1615 piece.color.opposite(),
1616 )
1617 {
1618 return false;
1619 }
1620 }
1621 return true;
1622 }
1623
1624 fn has_legal_moves(&self) -> bool {
1630 let color = if self.is_white_turn {
1631 Color::White
1632 } else {
1633 Color::Black
1634 };
1635
1636 for piece_pos in self.board.find_all(color) {
1637 let piece = self.board.get_piece(&piece_pos).unwrap();
1638 for col in 0..8 {
1639 for row in 0..8 {
1640 let end_pos = Position::new(col, row).unwrap();
1641
1642 let mut board = self.board.clone();
1643 if !board.can_move(&piece_pos, &end_pos).unwrap() {
1644 continue;
1645 }
1646
1647 board.move_piece(&piece_pos, &end_pos).unwrap();
1648
1649 let king = board.find(PieceType::King, piece.color)[0];
1650 if !board.is_attacked(king, piece.color.opposite()) {
1651 return true;
1652 }
1653 }
1654 }
1655 }
1656 false
1657 }
1658
1659 fn get_fen_reduced(&self) -> String {
1666 let fen = self.fen();
1667 let mut fen_parts: Vec<&str> = fen.split_whitespace().collect();
1668 fen_parts.pop();
1669 fen_parts.pop();
1670 fen_parts.join(" ")
1671 }
1672}
1673
1674impl fmt::Display for Game {
1675 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1676 write!(f, "{}", self.fen())
1677 }
1678}
1679
1680#[cfg(test)]
1681mod tests {
1682
1683 use crate::core::{Color, MoveType, PieceType, Position};
1684
1685 use super::*;
1686
1687 #[test]
1688 fn test_fen() {
1689 let game = Game::default();
1690 assert_eq!(
1691 game.fen(),
1692 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
1693 );
1694 }
1695
1696 #[test]
1697 fn test_fmt() {
1698 let game = Game::default();
1699 assert_eq!(
1700 format!("{}", game),
1701 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
1702 );
1703 }
1704
1705 #[test]
1706 fn test_new_game() {
1707 let game = Game::new(
1708 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
1709 true,
1710 )
1711 .unwrap();
1712 assert_eq!(
1713 game.fen(),
1714 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
1715 );
1716 }
1717
1718 #[test]
1719 fn test_from_fen() {
1720 let game =
1721 Game::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
1722 assert_eq!(
1723 game.fen(),
1724 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
1725 );
1726 }
1727
1728 #[test]
1729 fn test_move_piece() {
1730 let mut game = Game::default();
1731 game.move_piece("e4").unwrap();
1732 assert_eq!(
1733 game.fen(),
1734 "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
1735 );
1736 }
1737
1738 #[test]
1739 fn test_redundant_move() {
1740 let mut game = Game::default();
1741 assert!(game.move_piece("Pe4").is_ok());
1742 game.move_piece("d5").unwrap();
1743 assert!(game.move_piece("e4d5").is_ok());
1744 game.undo();
1745 game.move_piece("e5").unwrap();
1746 game.move_piece("f5").unwrap();
1747 assert!(game.move_piece("Pe5f6").is_ok());
1748 }
1749
1750 #[test]
1751 fn test_invalid_move() {
1752 let mut game = Game::default();
1753 assert!(game.move_piece("abc").is_err());
1754 }
1755
1756 #[test]
1757 fn test_castle_kingside() {
1758 let mut game = Game::default();
1759 game.move_piece("e4").unwrap();
1760 game.move_piece("e5").unwrap();
1761 game.move_piece("Nf3").unwrap();
1762 game.move_piece("Nc6").unwrap();
1763 game.move_piece("Bb5").unwrap();
1764 game.move_piece("a6").unwrap();
1765 game.move_piece("O-O").unwrap();
1766 assert_eq!(
1767 game.fen(),
1768 "r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 1 4"
1769 );
1770 }
1771
1772 #[test]
1773 fn test_castle_queenside() {
1774 let mut game = Game::default();
1775 game.move_piece("d4").unwrap();
1776 game.move_piece("d5").unwrap();
1777 game.move_piece("Nc3").unwrap();
1778 game.move_piece("Nc6").unwrap();
1779 game.move_piece("Bf4").unwrap();
1780 game.move_piece("Bf5").unwrap();
1781 game.move_piece("Qd2").unwrap();
1782 game.move_piece("Qd7").unwrap();
1783 game.move_piece("O-O-O").unwrap();
1784 game.move_piece("O-O-O").unwrap();
1785 assert_eq!(
1786 game.fen(),
1787 "2kr1bnr/pppqpppp/2n5/3p1b2/3P1B2/2N5/PPPQPPPP/2KR1BNR w - - 8 6"
1788 );
1789 }
1790
1791 #[test]
1792 fn test_castle_through_check() {
1793 let mut game = Game::default();
1794 game.move_piece("e4").unwrap();
1795 game.move_piece("e5").unwrap();
1796 game.move_piece("Nf3").unwrap();
1797 game.move_piece("Nc6").unwrap();
1798 game.move_piece("Bb5").unwrap();
1799 game.move_piece("a6").unwrap();
1800 game.move_piece("Bxc6").unwrap();
1801 game.move_piece("dxc6").unwrap();
1802 game.move_piece("c3").unwrap();
1803 game.move_piece("Be6").unwrap();
1804 game.move_piece("d4").unwrap();
1805 game.move_piece("Bc4").unwrap();
1806 assert!(game.move_piece("O-O").is_err());
1807
1808 game = Game::default();
1809 game.move_piece("e4").unwrap();
1810 game.move_piece("c5").unwrap();
1811 game.move_piece("Nf3").unwrap();
1812 game.move_piece("d6").unwrap();
1813 game.move_piece("d4").unwrap();
1814 game.move_piece("cxd4").unwrap();
1815 game.move_piece("Nxd4").unwrap();
1816 game.move_piece("Nf6").unwrap();
1817 game.move_piece("Nc3").unwrap();
1818 game.move_piece("a6").unwrap();
1819 game.move_piece("Be3").unwrap();
1820 game.move_piece("e5").unwrap();
1821 game.move_piece("Nb3").unwrap();
1822 game.move_piece("Bg4").unwrap();
1823 game.move_piece("Qd2").unwrap();
1824 game.move_piece("Be7").unwrap();
1825 println!("{}", game);
1826 assert!(game.move_piece("O-O-O").is_err());
1827 }
1828
1829 #[test]
1830 fn test_castle_with_no_rights() {
1831 let mut game = Game::default();
1832 game.move_piece("e4").unwrap();
1833 game.move_piece("e5").unwrap();
1834 game.move_piece("Nf3").unwrap();
1835 game.move_piece("Nc6").unwrap();
1836 game.move_piece("Bb5").unwrap();
1837 game.move_piece("a6").unwrap();
1838 game.move_piece("Ke2").unwrap();
1839 game.move_piece("Nf6").unwrap();
1840 game.move_piece("Ke1").unwrap();
1841 game.move_piece("d6").unwrap();
1842 assert!(game.move_piece("O-O").is_err());
1843
1844 game = Game::default();
1845 game.move_piece("d4").unwrap();
1846 game.move_piece("d5").unwrap();
1847 game.move_piece("Nc3").unwrap();
1848 game.move_piece("Nc6").unwrap();
1849 game.move_piece("Bf4").unwrap();
1850 game.move_piece("Bf5").unwrap();
1851 game.move_piece("Qd2").unwrap();
1852 game.move_piece("Qd7").unwrap();
1853 game.move_piece("Kd1").unwrap();
1854 game.move_piece("Kd8").unwrap();
1855 game.move_piece("Ke1").unwrap();
1856 game.move_piece("Ke8").unwrap();
1857 assert!(game.move_piece("O-O-O").is_err());
1858 }
1859
1860 #[test]
1861 fn test_is_castle_legal_no_rights_kingside() {
1862 let mut game = Game::default();
1863 game.castling_rights = 0;
1864 let white_king = Piece::new(Color::White, PieceType::King);
1865 let black_king = Piece::new(Color::Black, PieceType::King);
1866
1867 let white_start = Position::from_string("e1").unwrap();
1868 let black_start = Position::from_string("e8").unwrap();
1869
1870 let white_end = Position::from_string("g1").unwrap();
1871 let black_end = Position::from_string("g8").unwrap();
1872
1873 assert!(!game.is_castle_legal(
1874 &white_king,
1875 &white_start,
1876 &white_end,
1877 &CastleType::KingSide
1878 ));
1879 assert!(!game.is_castle_legal(
1880 &black_king,
1881 &black_start,
1882 &black_end,
1883 &CastleType::KingSide
1884 ));
1885 }
1886
1887 #[test]
1888 fn test_is_castle_legal_no_rights_queenside() {
1889 let mut game = Game::default();
1890 game.castling_rights = 0;
1891 let white_king = Piece::new(Color::White, PieceType::King);
1892 let black_king = Piece::new(Color::Black, PieceType::King);
1893
1894 let white_start = Position::from_string("e1").unwrap();
1895 let black_start = Position::from_string("e8").unwrap();
1896
1897 let white_end = Position::from_string("c1").unwrap();
1898 let black_end = Position::from_string("c8").unwrap();
1899
1900 assert!(!game.is_castle_legal(
1901 &white_king,
1902 &white_start,
1903 &white_end,
1904 &CastleType::QueenSide
1905 ));
1906 assert!(!game.is_castle_legal(
1907 &black_king,
1908 &black_start,
1909 &black_end,
1910 &CastleType::QueenSide
1911 ));
1912 }
1913
1914 #[test]
1915 fn test_is_legal_invalid_promotion() {
1916 let game = Game::default();
1917
1918 assert!(!game.is_legal(
1919 &Piece::new(Color::White, PieceType::Pawn),
1920 &Position::from_string("e2").unwrap(),
1921 &Position::from_string("e4").unwrap(),
1922 &MoveType::Normal {
1923 capture: false,
1924 promotion: Some(PieceType::Queen)
1925 },
1926 ));
1927 }
1928
1929 #[test]
1930 fn test_is_legal_nonsense_move() {
1931 let game = Game::default();
1932 assert!(!game.is_legal(
1933 &Piece::new(Color::White, PieceType::Knight),
1934 &Position::from_string("e1").unwrap(),
1935 &Position::from_string("e1").unwrap(),
1936 &MoveType::Castle {
1937 side: CastleType::KingSide,
1938 },
1939 ))
1940 }
1941
1942 #[test]
1943 fn test_en_passant() {
1944 let mut game = Game::default();
1945 game.move_piece("e4").unwrap();
1946 game.move_piece("d5").unwrap();
1947 game.move_piece("e5").unwrap();
1948 game.move_piece("f5").unwrap();
1949 assert_eq!(
1950 game.fen(),
1951 "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"
1952 );
1953 game.move_piece("exf6").unwrap();
1954 assert_eq!(
1955 game.fen(),
1956 "rnbqkbnr/ppp1p1pp/5P2/3p4/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 3"
1957 );
1958 }
1959
1960 #[test]
1961 fn test_castle_rights() {
1962 let mut game = Game::default();
1963 game.move_piece("a3").unwrap();
1964 game.move_piece("a6").unwrap();
1965 game.move_piece("Ra2").unwrap();
1966 assert_eq!(
1967 game.fen(),
1968 "rnbqkbnr/1ppppppp/p7/8/8/P7/RPPPPPPP/1NBQKBNR b Kkq - 1 2"
1969 );
1970 game.move_piece("Ra7").unwrap();
1971 assert_eq!(
1972 game.fen(),
1973 "1nbqkbnr/rppppppp/p7/8/8/P7/RPPPPPPP/1NBQKBNR w Kk - 2 3"
1974 );
1975
1976 game.move_piece("h3").unwrap();
1977 game.move_piece("h6").unwrap();
1978 game.move_piece("Rh2").unwrap();
1979 assert_eq!(
1980 game.fen(),
1981 "1nbqkbnr/rpppppp1/p6p/8/8/P6P/RPPPPPPR/1NBQKBN1 b k - 1 4"
1982 );
1983 game.move_piece("Rh7").unwrap();
1984 assert_eq!(
1985 game.fen(),
1986 "1nbqkbn1/rppppppr/p6p/8/8/P6P/RPPPPPPR/1NBQKBN1 w - - 2 5"
1987 );
1988 }
1989
1990 #[test]
1991 fn test_promotion() {
1992 let mut game = Game::default();
1993 game.move_piece("e4").unwrap();
1994 game.move_piece("d5").unwrap();
1995 game.move_piece("exd5").unwrap();
1996 game.move_piece("c6").unwrap();
1997 game.move_piece("dxc6").unwrap();
1998 game.move_piece("a6").unwrap();
1999 game.move_piece("cxb7").unwrap();
2000 game.move_piece("a5").unwrap();
2001 game.move_piece("bxa8=Q").unwrap();
2002 assert_eq!(
2003 game.fen(),
2004 "Qnbqkbnr/4pppp/8/p7/8/8/PPPP1PPP/RNBQKBNR b KQk - 0 5"
2005 );
2006 }
2007
2008 #[test]
2009 fn test_promotion_invalid() {
2010 let mut game = Game::default();
2011 assert!(game.move_piece("e4=Q").is_err());
2012 assert!(game.move_piece("Ke4=Q").is_err());
2013 }
2014
2015 #[test]
2016 fn test_illegal_move() {
2017 let mut game = Game::default();
2018 assert!(game.move_piece("e5").is_err());
2019 game.move_piece("e4").unwrap();
2020 assert!(game.move_piece("e4").is_err());
2021 game.move_piece("e5").unwrap();
2022 assert!(game.move_piece("Nf6").is_err());
2023 assert!(game.move_piece("d4=Q").is_err());
2024 }
2025
2026 #[test]
2027 fn test_get_piece_at() {
2028 let game = Game::default();
2029 let piece = game
2030 .get_piece_at(Position::from_string("e2").unwrap())
2031 .unwrap();
2032 assert_eq!(piece.piece_type, PieceType::Pawn);
2033 assert_eq!(piece.color, Color::White);
2034
2035 assert!(game
2036 .get_piece_at(Position::from_string("e4").unwrap())
2037 .is_none());
2038 }
2039
2040 #[test]
2041 fn test_get_legal_moves() {
2042 let mut game = Game::default();
2043 let legal_moves = game.get_legal_moves(Position::from_string("e2").unwrap());
2044
2045 assert_eq!(legal_moves.len(), 2);
2046 assert!(legal_moves
2047 .iter()
2048 .any(|m| m.to == Position::from_string("e3").unwrap()));
2049 assert!(legal_moves
2050 .iter()
2051 .any(|m| m.to == Position::from_string("e4").unwrap()));
2052
2053 let legal_moves = game.get_legal_moves(Position::from_string("e1").unwrap());
2054 assert_eq!(legal_moves.len(), 0);
2055
2056 game.move_piece("e4").unwrap();
2057
2058 let legal_moves = game.get_legal_moves(Position::from_string("e7").unwrap());
2059
2060 assert_eq!(legal_moves.len(), 2);
2061 assert!(legal_moves
2062 .iter()
2063 .any(|m| m.to == Position::from_string("e6").unwrap()));
2064 assert!(legal_moves
2065 .iter()
2066 .any(|m| m.to == Position::from_string("e5").unwrap()));
2067 }
2068
2069 #[test]
2070 fn test_get_legal_moves_no_piece() {
2071 let game = Game::default();
2072 let legal_moves = game.get_legal_moves(Position::from_string("e4").unwrap());
2073 assert_eq!(legal_moves.len(), 0);
2074 }
2075
2076 #[test]
2077 fn test_get_legal_moves_oposite_color() {
2078 let game = Game::default();
2079 let legal_moves = game.get_legal_moves(Position::from_string("e7").unwrap());
2080 assert_eq!(legal_moves.len(), 0);
2081 }
2082
2083 #[test]
2084 fn test_get_legal_moves_castle() {
2085 let mut game = Game::default();
2086 game.move_piece("e4").unwrap();
2087 game.move_piece("e5").unwrap();
2088 game.move_piece("Nf3").unwrap();
2089 game.move_piece("Nc6").unwrap();
2090 game.move_piece("Bb5").unwrap();
2091 game.move_piece("a6").unwrap();
2092
2093 let legal_moves = game.get_legal_moves(Position::from_string("e1").unwrap());
2094 assert_eq!(legal_moves.len(), 3);
2095 assert!(legal_moves.iter().any(|mov| matches!(
2096 mov.move_type,
2097 MoveType::Castle {
2098 side: CastleType::KingSide
2099 }
2100 )));
2101 assert!(legal_moves
2102 .iter()
2103 .any(|mov| matches!(mov.rook_from, Some(Position { col: 7, row: 0 }))));
2104
2105 let mut game = Game::default();
2106 game.move_piece("d4").unwrap();
2107 game.move_piece("d5").unwrap();
2108 game.move_piece("Nc3").unwrap();
2109 game.move_piece("Nc6").unwrap();
2110 game.move_piece("Bf4").unwrap();
2111 game.move_piece("Bf5").unwrap();
2112 game.move_piece("Qd2").unwrap();
2113 game.move_piece("Qd7").unwrap();
2114
2115 let legal_moves = game.get_legal_moves(Position::from_string("e1").unwrap());
2116 assert_eq!(legal_moves.len(), 2);
2117 assert!(legal_moves.iter().any(|mov| matches!(
2118 mov.move_type,
2119 MoveType::Castle {
2120 side: CastleType::QueenSide
2121 }
2122 )));
2123 assert!(legal_moves
2124 .iter()
2125 .any(|mov| matches!(mov.rook_from, Some(Position { col: 0, row: 0 }))));
2126 }
2127
2128 #[test]
2129 fn test_get_legal_moves_en_passant() {
2130 let mut game = Game::default();
2131 game.move_piece("e4").unwrap();
2132 game.move_piece("d5").unwrap();
2133 game.move_piece("e5").unwrap();
2134 game.move_piece("f5").unwrap();
2135
2136 let legal_moves = game.get_legal_moves(Position::from_string("e5").unwrap());
2137 assert_eq!(legal_moves.len(), 2);
2138 assert!(legal_moves
2139 .iter()
2140 .any(|m| m.to == Position::from_string("e6").unwrap()));
2141 assert!(legal_moves
2142 .iter()
2143 .any(|m| m.to == Position::from_string("f6").unwrap()));
2144 }
2145
2146 #[test]
2147 fn test_get_legal_moves_promotion() {
2148 let mut game = Game::default();
2149 game.move_piece("e4").unwrap();
2150 game.move_piece("d5").unwrap();
2151 game.move_piece("exd5").unwrap();
2152 game.move_piece("c6").unwrap();
2153 game.move_piece("dxc6").unwrap();
2154 game.move_piece("a6").unwrap();
2155 game.move_piece("cxb7").unwrap();
2156 game.move_piece("a5").unwrap();
2157
2158 let legal_moves = game.get_legal_moves(Position::from_string("b7").unwrap());
2159 assert_eq!(legal_moves.len(), 2);
2160 assert_eq!(
2161 legal_moves.iter().map(|v| v.to).collect::<Vec<Position>>(),
2162 vec![
2163 Position::from_string("a8").unwrap(),
2164 Position::from_string("c8").unwrap()
2165 ]
2166 );
2167 assert!(matches!(
2168 legal_moves[0].move_type,
2169 MoveType::Normal {
2170 capture: true,
2171 promotion: Some(PieceType::Queen) }
2173 ));
2174 }
2175
2176 #[test]
2177 fn test_ambiguity() {
2178 let mut game = Game::default();
2179 game.move_piece("Nf3").unwrap();
2180 game.move_piece("Nc6").unwrap();
2181 game.move_piece("Na3").unwrap();
2182 game.move_piece("Nb8").unwrap();
2183 game.move_piece("Nc4").unwrap();
2184 game.move_piece("Nc6").unwrap();
2185
2186 assert!(game.move_piece("Ne5").is_err());
2187 }
2188
2189 #[test]
2190 fn test_undo() {
2191 let mut game = Game::default();
2192 game.move_piece("e4").unwrap();
2193 game.undo();
2194 assert_eq!(
2195 game.fen(),
2196 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
2197 );
2198 }
2199
2200 #[test]
2201 fn test_undo_no_moves() {
2202 let mut game = Game::default();
2203 game.undo();
2204 assert_eq!(
2205 game.fen(),
2206 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
2207 );
2208 }
2209
2210 #[test]
2211 fn test_undo_castle_kingside() {
2212 let mut game = Game::default();
2213 game.move_piece("e4").unwrap();
2214 game.move_piece("e5").unwrap();
2215 game.move_piece("Nf3").unwrap();
2216 game.move_piece("Nc6").unwrap();
2217 game.move_piece("Bb5").unwrap();
2218 game.move_piece("a6").unwrap();
2219 game.move_piece("O-O").unwrap();
2220 game.undo();
2221 assert_eq!(
2222 game.fen(),
2223 "r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 4"
2224 );
2225 }
2226
2227 #[test]
2228 fn test_undo_castle_queenside() {
2229 let mut game = Game::default();
2230 game.move_piece("d4").unwrap();
2231 game.move_piece("d5").unwrap();
2232 game.move_piece("Nc3").unwrap();
2233 game.move_piece("Nc6").unwrap();
2234 game.move_piece("Bf4").unwrap();
2235 game.move_piece("Bf5").unwrap();
2236 game.move_piece("Qd2").unwrap();
2237 game.move_piece("Qd7").unwrap();
2238 game.move_piece("O-O-O").unwrap();
2239 game.undo();
2240 assert_eq!(
2241 game.fen(),
2242 "r3kbnr/pppqpppp/2n5/3p1b2/3P1B2/2N5/PPPQPPPP/R3KBNR w KQkq - 6 5"
2243 );
2244 }
2245
2246 #[test]
2247 fn test_undo_en_passant() {
2248 let mut game = Game::default();
2249 game.move_piece("e4").unwrap();
2250 game.move_piece("d5").unwrap();
2251 game.move_piece("e5").unwrap();
2252 game.move_piece("f5").unwrap();
2253 game.move_piece("exf6").unwrap();
2254 game.undo();
2255 assert_eq!(
2256 game.fen(),
2257 "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"
2258 );
2259 }
2260
2261 #[test]
2262 fn test_undo_promotion() {
2263 let mut game = Game::default();
2264 game.move_piece("e4").unwrap();
2265 game.move_piece("d5").unwrap();
2266 game.move_piece("exd5").unwrap();
2267 game.move_piece("c6").unwrap();
2268 game.move_piece("dxc6").unwrap();
2269 game.move_piece("a6").unwrap();
2270 game.move_piece("cxb7").unwrap();
2271 game.move_piece("a5").unwrap();
2272 game.move_piece("bxa8=Q").unwrap();
2273 game.undo();
2274 assert_eq!(
2275 game.fen(),
2276 "rnbqkbnr/1P2pppp/8/p7/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 5"
2277 );
2278 }
2279
2280 #[test]
2281 fn test_undo_castle_rights() {
2282 let mut game = Game::default();
2283 game.move_piece("a3").unwrap();
2284 game.move_piece("a6").unwrap();
2285 game.move_piece("Ra2").unwrap();
2286 game.move_piece("Ra7").unwrap();
2287 game.move_piece("h3").unwrap();
2288 game.move_piece("h6").unwrap();
2289 game.move_piece("Rh2").unwrap();
2290 game.move_piece("Rh7").unwrap();
2291 game.undo();
2292 assert_eq!(
2293 game.fen(),
2294 "1nbqkbnr/rpppppp1/p6p/8/8/P6P/RPPPPPPR/1NBQKBN1 b k - 1 4"
2295 );
2296 }
2297
2298 #[test]
2299 fn test_redo() {
2300 let mut game = Game::default();
2301 game.move_piece("e4").unwrap();
2302 game.undo();
2303 game.redo();
2304 assert_eq!(
2305 game.fen(),
2306 "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
2307 );
2308 game.undo();
2309 }
2310
2311 #[test]
2312 fn test_redo_no_moves() {
2313 let mut game = Game::default();
2314 game.redo();
2315 assert_eq!(
2316 game.fen(),
2317 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
2318 );
2319 }
2320
2321 #[test]
2322 fn test_redo_castle() {
2323 let mut game = Game::default();
2324 game.move_piece("e4").unwrap();
2325 game.move_piece("e5").unwrap();
2326 game.move_piece("Nf3").unwrap();
2327 game.move_piece("Nc6").unwrap();
2328 game.move_piece("Bb5").unwrap();
2329 game.move_piece("a6").unwrap();
2330 game.move_piece("O-O").unwrap();
2331 game.undo();
2332 game.redo();
2333 assert_eq!(
2334 game.fen(),
2335 "r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 1 4"
2336 );
2337 }
2338
2339 #[test]
2340 fn test_redo_en_passant() {
2341 let mut game = Game::default();
2342 game.move_piece("e4").unwrap();
2343 game.move_piece("d5").unwrap();
2344 game.move_piece("e5").unwrap();
2345 game.move_piece("f5").unwrap();
2346 game.move_piece("exf6").unwrap();
2347 game.undo();
2348 game.redo();
2349 assert_eq!(
2350 game.fen(),
2351 "rnbqkbnr/ppp1p1pp/5P2/3p4/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 3"
2352 );
2353 }
2354
2355 #[test]
2356 fn test_redo_promotion() {
2357 let mut game = Game::default();
2358 game.move_piece("e4").unwrap();
2359 game.move_piece("d5").unwrap();
2360 game.move_piece("exd5").unwrap();
2361 game.move_piece("c6").unwrap();
2362 game.move_piece("dxc6").unwrap();
2363 game.move_piece("a6").unwrap();
2364 game.move_piece("cxb7").unwrap();
2365 game.move_piece("a5").unwrap();
2366 game.move_piece("bxa8=Q").unwrap();
2367 game.undo();
2368 game.redo();
2369 assert_eq!(
2370 game.fen(),
2371 "Qnbqkbnr/4pppp/8/p7/8/8/PPPP1PPP/RNBQKBNR b KQk - 0 5"
2372 );
2373 }
2374
2375 #[test]
2376 fn test_redo_castle_rights() {
2377 let mut game = Game::default();
2378 game.move_piece("a3").unwrap();
2379 game.move_piece("a6").unwrap();
2380 game.move_piece("Ra2").unwrap();
2381 game.move_piece("Ra7").unwrap();
2382 game.move_piece("h3").unwrap();
2383 game.move_piece("h6").unwrap();
2384 game.move_piece("Rh2").unwrap();
2385 game.move_piece("Rh7").unwrap();
2386 game.undo();
2387 game.redo();
2388 assert_eq!(
2389 game.fen(),
2390 "1nbqkbn1/rppppppr/p6p/8/8/P6P/RPPPPPPR/1NBQKBN1 w - - 2 5"
2391 );
2392 }
2393
2394 #[test]
2395 fn test_redo_principal_line() {
2396 let mut game = Game::default();
2397 game.move_piece("e4").unwrap();
2398 game.undo();
2399 game.move_piece("d4").unwrap();
2400 game.undo();
2401
2402 assert_eq!(game.history.all_next_moves().len(), 2);
2403
2404 game.redo();
2405 assert_eq!(
2406 game.fen(),
2407 "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
2408 );
2409 }
2410
2411 #[test]
2412 fn test_multiple_redos() {
2413 let mut game = Game::default();
2414 game.move_piece("e4").unwrap();
2415 game.move_piece("e5").unwrap();
2416 game.move_piece("Nf3").unwrap();
2417 game.move_piece("Nc6").unwrap();
2418 game.move_piece("Bb5").unwrap();
2419 game.move_piece("a6").unwrap();
2420 game.move_piece("O-O").unwrap();
2421
2422 game.start();
2423
2424 game.redo();
2425 game.redo();
2426 game.redo();
2427 game.redo();
2428 game.redo();
2429 game.redo();
2430 game.redo();
2431
2432 assert_eq!(
2433 game.fen(),
2434 "r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 1 4"
2435 );
2436 }
2437
2438 #[test]
2439 fn test_auto_redo() {
2440 let mut game = Game::default();
2441 game.move_piece("e4").unwrap();
2442 game.move_piece("e5").unwrap();
2443 game.undo();
2444 game.undo();
2445 game.move_piece("e4").unwrap();
2446 game.redo();
2447
2448 assert_eq!(
2449 game.fen(),
2450 "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"
2451 );
2452 }
2453
2454 #[test]
2455 fn test_play_other_move() {
2456 let mut game = Game::default();
2457 game.move_piece("e4").unwrap();
2458 game.undo();
2459 game.move_piece("d4").unwrap();
2460
2461 assert_eq!(
2462 game.fen(),
2463 "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1"
2464 );
2465
2466 let mut game = Game::default();
2467 game.move_piece("e4").unwrap();
2468 game.move_piece("e5").unwrap();
2469 game.move_piece("Nf3").unwrap();
2470 game.undo();
2471 game.move_piece("Nc3").unwrap();
2472
2473 assert_eq!(
2474 game.fen(),
2475 "rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2"
2476 );
2477 }
2478
2479 #[test]
2480 fn test_redo_nth() {
2481 let mut game = Game::default();
2482 game.move_piece("e4").unwrap();
2483 game.undo();
2484 game.move_piece("d4").unwrap();
2485 game.undo();
2486 game.redo_nth(1);
2487 assert_eq!(
2488 game.fen(),
2489 "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1"
2490 );
2491 }
2492
2493 #[test]
2494 fn test_redo_nth_no_moves() {
2495 let mut game = Game::default();
2496 game.redo_nth(0);
2497 assert_eq!(
2498 game.fen(),
2499 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
2500 );
2501 }
2502
2503 #[test]
2504 fn test_start() {
2505 let mut game = Game::default();
2506 game.move_piece("e4").unwrap();
2507 game.move_piece("e5").unwrap();
2508 game.move_piece("Nf3").unwrap();
2509 game.move_piece("Nc6").unwrap();
2510 game.move_piece("Bb5").unwrap();
2511 game.move_piece("a6").unwrap();
2512 game.move_piece("O-O").unwrap();
2513
2514 game.start();
2515
2516 assert_eq!(
2517 game.fen(),
2518 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
2519 );
2520 }
2521
2522 #[test]
2523 fn test_end() {
2524 let mut game = Game::default();
2525 game.move_piece("e4").unwrap();
2526 game.move_piece("e5").unwrap();
2527 game.move_piece("Nf3").unwrap();
2528 game.move_piece("Nc6").unwrap();
2529 game.move_piece("Bb5").unwrap();
2530 game.move_piece("a6").unwrap();
2531 game.move_piece("O-O").unwrap();
2532
2533 game.start();
2534 game.end();
2535
2536 assert_eq!(
2537 game.fen(),
2538 "r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 1 4"
2539 );
2540 }
2541
2542 #[test]
2543 fn test_pgn() {
2544 let mut game = Game::default();
2545 game.move_piece("e4").unwrap();
2546 game.move_piece("e5").unwrap();
2547 game.move_piece("Nf3").unwrap();
2548 game.move_piece("Nc6").unwrap();
2549 game.move_piece("Bb5").unwrap();
2550 game.move_piece("a6").unwrap();
2551 game.move_piece("Ba4").unwrap();
2552 game.move_piece("Nf6").unwrap();
2553 game.move_piece("O-O").unwrap();
2554 game.move_piece("Be7").unwrap();
2555 game.move_piece("Re1").unwrap();
2556 game.move_piece("b5").unwrap();
2557 game.move_piece("Bb3").unwrap();
2558 game.move_piece("O-O").unwrap();
2559 game.move_piece("c3").unwrap();
2560 game.move_piece("d5").unwrap();
2561 game.undo();
2562 game.undo();
2563 game.undo();
2564 game.undo();
2565 game.undo();
2566 game.move_piece("O-O").unwrap();
2567 game.move_piece("c3").unwrap();
2568 game.move_piece("b5").unwrap();
2569 game.move_piece("Bc2").unwrap();
2570
2571 let pgn = game.pgn();
2572 assert!(
2573 pgn.contains(
2574 "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 (6... O-O 7. c3 b5 8. Bc2) 7. Bb3 O-O 8. c3 d5"
2575 )
2576 );
2577 }
2578
2579 #[test]
2580 fn test_get_castle_rook_pos() {
2581 let mut game = Game::default();
2582 assert_eq!(
2583 game.get_castle_rook_pos(CastleType::KingSide),
2584 Some(Position::from_string("h1").unwrap())
2585 );
2586 assert_eq!(
2587 game.get_castle_rook_pos(CastleType::QueenSide),
2588 Some(Position::from_string("a1").unwrap())
2589 );
2590
2591 game.move_piece("e4").unwrap();
2592
2593 assert_eq!(
2594 game.get_castle_rook_pos(CastleType::KingSide),
2595 Some(Position::from_string("h8").unwrap())
2596 );
2597 assert_eq!(
2598 game.get_castle_rook_pos(CastleType::QueenSide),
2599 Some(Position::from_string("a8").unwrap())
2600 );
2601 }
2602
2603 #[test]
2604 fn test_get_castle_rook_pos_after_castle() {
2605 let mut game = Game::default();
2606 game.move_piece("e4").unwrap();
2607 game.move_piece("e5").unwrap();
2608 game.move_piece("Nf3").unwrap();
2609 game.move_piece("Nf6").unwrap();
2610 game.move_piece("Bb5").unwrap();
2611 game.move_piece("Bb4").unwrap();
2612 game.move_piece("O-O").unwrap();
2613
2614 assert_eq!(
2615 game.get_castle_rook_pos(CastleType::KingSide),
2616 Some(Position::from_string("h8").unwrap())
2617 );
2618 assert_eq!(
2619 game.get_castle_rook_pos(CastleType::QueenSide),
2620 Some(Position::from_string("a8").unwrap())
2621 );
2622
2623 game.move_piece("O-O").unwrap();
2624
2625 assert_eq!(game.get_castle_rook_pos(CastleType::KingSide), None);
2626 assert_eq!(game.get_castle_rook_pos(CastleType::QueenSide), None);
2627
2628 game.move_piece("a3").unwrap();
2629
2630 assert_eq!(game.get_castle_rook_pos(CastleType::KingSide), None);
2631 assert_eq!(game.get_castle_rook_pos(CastleType::QueenSide), None);
2632 }
2633
2634 #[test]
2635 fn test_check() {
2636 let mut game = Game::default();
2637 game.move_piece("c4").unwrap();
2638 game.move_piece("d6").unwrap();
2639 game.move_piece("Qa4+").unwrap();
2640 assert!(game.check());
2641 }
2642
2643 #[test]
2644 fn test_check_pgn() {
2645 let mut game = Game::default();
2646 game.move_piece("c4").unwrap();
2647 game.move_piece("d6").unwrap();
2648 game.move_piece("Qa4+").unwrap();
2649 assert!(game.pgn().contains("1. c4 d6 2. Qa4+"));
2650 }
2651
2652 #[test]
2653 fn test_checkmate() {
2654 let mut game = Game::default();
2655 game.move_piece("e4").unwrap();
2656 game.move_piece("e5").unwrap();
2657 game.move_piece("Qh5").unwrap();
2658 game.move_piece("Nc6").unwrap();
2659 game.move_piece("Bc4").unwrap();
2660 game.move_piece("Nf6").unwrap();
2661 game.move_piece("Qxf7#").unwrap();
2662 assert_eq!(game.status, GameStatus::WhiteWins(WinReason::Checkmate));
2663 assert!(game.checkmate());
2664
2665 game = Game::default();
2666 game.move_piece("f3").unwrap();
2667 game.move_piece("e5").unwrap();
2668 game.move_piece("g4").unwrap();
2669 game.move_piece("Qh4#").unwrap();
2670 assert_eq!(game.status, GameStatus::BlackWins(WinReason::Checkmate));
2671 assert!(game.checkmate());
2672 }
2673
2674 #[test]
2675 fn test_stalemate_pgn() {
2676 let mut game = Game::from_fen("6k1/8/6K1/5Q2/8/8/8/8 w - - 0 1").unwrap();
2677 game.move_piece("Qf6").unwrap();
2678 println!("{}", game.pgn());
2679 assert!(game.pgn().contains("[Result \"1/2-1/2\"]"));
2680 assert!(game.pgn().contains("1. Qf6 1/2-1/2"));
2681 }
2682
2683 #[test]
2684 fn test_checkmate_pgn() {
2685 let mut game = Game::default();
2686 game.move_piece("e4").unwrap();
2687 game.move_piece("e5").unwrap();
2688 game.move_piece("Qh5").unwrap();
2689 game.move_piece("Nc6").unwrap();
2690 game.move_piece("Bc4").unwrap();
2691 game.move_piece("Nf6").unwrap();
2692 game.move_piece("Qxf7#").unwrap();
2693 assert!(game
2694 .pgn()
2695 .contains("[Result \"1-0\"]\n1. e4 e5 2. Qh5 Nc6 3. Bc4 Nf6 4. Qxf7# 1-0\n"));
2696 }
2697
2698 #[test]
2699 fn test_fifty_move_rule() {
2700 let mut game = Game::from_fen("8/4P3/8/8/8/4K3/8/4k3 w - - 99 50").unwrap();
2701 game.move_piece("Kd3").unwrap();
2702 assert_eq!(game.status, GameStatus::Draw(DrawReason::FiftyMoveRule));
2703 }
2704
2705 #[test]
2706 fn test_stalemate() {
2707 let mut game = Game::from_fen("8/8/8/8/4Q3/4K3/8/4k3 w - - 0 1").unwrap();
2708 game.move_piece("Qd3").unwrap();
2709 assert!(game.stalemate());
2710 assert_eq!(game.status, GameStatus::Draw(DrawReason::Stalemate));
2711 }
2712
2713 #[test]
2714 fn test_insufficient_material() {
2715 let mut game = Game::from_fen("8/8/8/8/8/3pK3/8/4kB2 w - - 0 1").unwrap();
2716 game.move_piece("Bxd3").unwrap();
2717 assert!(game.insufficient_material());
2718 assert_eq!(
2719 game.status,
2720 GameStatus::Draw(DrawReason::InsufficientMaterial)
2721 );
2722 }
2723
2724 #[test]
2725 fn test_resign() {
2726 let mut game = Game::default();
2727 game.resign(Color::White);
2728 assert_eq!(game.status, GameStatus::BlackWins(WinReason::Resignation));
2729
2730 game = Game::default();
2731 game.resign(Color::Black);
2732 assert_eq!(game.status, GameStatus::WhiteWins(WinReason::Resignation));
2733 game.resign(Color::White);
2734 assert_eq!(game.status, GameStatus::WhiteWins(WinReason::Resignation));
2736 }
2737
2738 #[test]
2739 fn test_lose_on_time() {
2740 let mut game = Game::default();
2741 game.lost_on_time(Color::White);
2742 assert_eq!(game.status, GameStatus::BlackWins(WinReason::Time));
2743
2744 game = Game::default();
2745 game.lost_on_time(Color::Black);
2746 assert_eq!(game.status, GameStatus::WhiteWins(WinReason::Time));
2747 game.lost_on_time(Color::White);
2748 assert_eq!(game.status, GameStatus::WhiteWins(WinReason::Time));
2750 }
2751
2752 #[test]
2753 fn test_draw_by_agreement() {
2754 let mut game = Game::default();
2755 game.draw_by_agreement();
2756 assert_eq!(game.status, GameStatus::Draw(DrawReason::Agreement));
2757
2758 game = Game::default();
2759 game.resign(Color::White);
2760 game.draw_by_agreement();
2762 assert_eq!(game.status, GameStatus::BlackWins(WinReason::Resignation));
2763 }
2764
2765 #[test]
2766 fn test_move_ambiguity() {
2767 let game =
2768 Game::from_fen("r3k1nr/ppq2ppp/2nbp3/3p3b/3P4/2PB1N1P/PP3PP1/RNBQR1K1 w kq - 3 10")
2769 .unwrap();
2770 assert_eq!(
2771 game.move_ambiguity(
2772 PieceType::Knight,
2773 Color::White,
2774 (Some(1), None),
2775 &Position::from_string("d2").unwrap(),
2776 &MoveType::Normal {
2777 capture: false,
2778 promotion: None
2779 }
2780 ),
2781 (true, false)
2782 );
2783 }
2784
2785 #[test]
2786 fn test_capture_king_game() {
2787 let mut game = Game::new(
2788 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
2789 true,
2790 )
2791 .unwrap();
2792
2793 game.move_piece("f3").unwrap();
2794 game.move_piece("e5").unwrap();
2795 game.move_piece("g4").unwrap();
2796 game.move_piece("Qh4").unwrap();
2797 assert!(!game.checkmate());
2798 game.move_piece("Kf2").unwrap();
2799 game.move_piece("Qxf2").unwrap();
2800 assert!(game.checkmate());
2801 }
2802}