candidate/
game.rs

1use crate::board::{Board, BoardStatus};
2use crate::chess_move::ChessMove;
3use crate::color::Color;
4use crate::error::Error;
5use crate::movegen::MoveGen;
6use crate::piece::Piece;
7use std::borrow::Borrow;
8use std::str::FromStr;
9#[cfg(any(feature = "instrument_game", feature = "instrument_all"))]
10use tracing::instrument;
11
12/// Contains all actions supported within the game
13#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq)]
14pub enum Action {
15    MakeMove(ChessMove),
16    OfferDraw(Color),
17    AcceptDraw,
18    DeclareDraw,
19    Resign(Color),
20}
21
22/// What was the result of this game?
23#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
24pub enum GameResult {
25    WhiteCheckmates,
26    WhiteResigns,
27    BlackCheckmates,
28    BlackResigns,
29    Stalemate,
30    DrawAccepted,
31    DrawDeclared,
32}
33
34/// For UI/UCI Servers, store a game object which allows you to determine
35/// draw by 3 fold repitition, draw offers, resignations, and moves.
36///
37/// This structure is slow compared to using `Board` directly, so it is
38/// not recommended for engines.
39#[derive(Clone, Debug)]
40pub struct Game {
41    start_pos: Board,
42    moves: Vec<Action>,
43    #[cfg(feature = "cache_game_state")]
44    boards: Vec<Board>,
45}
46
47impl Game {
48    /// Create a new `Game` with the initial position.
49    ///
50    /// ```
51    /// use candidate::{Game, Board};
52    ///
53    /// let game = Game::new();
54    /// assert_eq!(game.current_position(), Board::default());
55    /// ```
56    #[cfg_attr(
57        any(feature = "instrument_game", feature = "instrument_all"),
58        instrument
59    )]
60    pub fn new() -> Game {
61        Game {
62            start_pos: Board::default(),
63            moves: vec![],
64            #[cfg(feature = "cache_game_state")]
65            boards: vec![Board::default()],
66        }
67    }
68
69    pub fn get_boards(&self) -> &Vec<Board> {
70        &self.boards
71    }
72
73    /// Create a new `Game` with a specific starting position.
74    ///
75    /// ```
76    /// use candidate::{Game, Board};
77    ///
78    /// let game = Game::new_with_board(Board::default());
79    /// assert_eq!(game.current_position(), Board::default());
80    /// ```
81    #[cfg_attr(
82        any(feature = "instrument_game", feature = "instrument_all"),
83        instrument
84    )]
85    pub fn new_with_board(board: Board) -> Game {
86        Game {
87            start_pos: board,
88            moves: vec![],
89            #[cfg(feature = "cache_game_state")]
90            boards: vec![board],
91        }
92    }
93
94    /// Get all actions made in this game (moves, draw offers, resignations, etc.)
95    ///
96    /// ```
97    /// use candidate::{Game, MoveGen, Color};
98    ///
99    /// let mut game = Game::new();
100    /// let mut movegen = MoveGen::new_legal(&game.current_position());
101    ///
102    /// game.make_move(movegen.next().expect("At least one valid move"));
103    /// game.resign(Color::Black);
104    /// assert_eq!(game.actions().len(), 2);
105    /// ```
106    #[cfg_attr(
107        any(feature = "instrument_game", feature = "instrument_all"),
108        instrument
109    )]
110    pub fn actions(&self) -> &Vec<Action> {
111        &self.moves
112    }
113
114    /// What is the status of this game?
115    ///
116    /// ```
117    /// use candidate::Game;
118    ///
119    /// let game = Game::new();
120    /// assert!(game.result().is_none());
121    /// ```
122    #[cfg_attr(
123        any(feature = "instrument_game", feature = "instrument_all"),
124        instrument
125    )]
126    pub fn result(&self) -> Option<GameResult> {
127        match self.current_position().status() {
128            BoardStatus::Checkmate => {
129                if self.side_to_move() == Color::White {
130                    Some(GameResult::BlackCheckmates)
131                } else {
132                    Some(GameResult::WhiteCheckmates)
133                }
134            }
135            BoardStatus::Stalemate => Some(GameResult::Stalemate),
136            BoardStatus::Ongoing => {
137                if self.moves.len() == 0 {
138                    None
139                } else if self.moves[self.moves.len() - 1] == Action::AcceptDraw {
140                    Some(GameResult::DrawAccepted)
141                } else if self.moves[self.moves.len() - 1] == Action::DeclareDraw {
142                    Some(GameResult::DrawDeclared)
143                } else if self.moves[self.moves.len() - 1] == Action::Resign(Color::White) {
144                    Some(GameResult::WhiteResigns)
145                } else if self.moves[self.moves.len() - 1] == Action::Resign(Color::Black) {
146                    Some(GameResult::BlackResigns)
147                } else {
148                    None
149                }
150            }
151        }
152    }
153
154    /// Create a new `Game` object from an FEN string.
155    ///
156    /// ```
157    /// use candidate::{Game, Board};
158    ///
159    /// // This is the better way:
160    /// # {
161    /// use std::str::FromStr;
162    /// let game: Game = Game::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Valid FEN");
163    /// let game2: Result<Game, _> = Game::from_str("Invalid FEN");
164    /// assert!(game2.is_err());
165    /// # }
166    ///
167    /// // This still works
168    /// # {
169    /// let game = Game::new_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Valid FEN");
170    /// let game2 = Game::new_from_fen("Invalid FEN");
171    /// assert!(game2.is_none());
172    /// # }
173    /// ```
174    #[cfg_attr(
175        any(feature = "instrument_game", feature = "instrument_all"),
176        instrument
177    )]
178    #[deprecated(since = "3.1.0", note = "Please use Game::from_str(fen)? instead.")]
179    pub fn new_from_fen(fen: &str) -> Option<Game> {
180        Game::from_str(fen).ok()
181    }
182
183    /// Get the current position on the board from the `Game` object.
184    ///
185    /// ```
186    /// use candidate::{Game, Board};
187    ///
188    /// let game = Game::new();
189    /// assert_eq!(game.current_position(), Board::default());
190    /// ```
191    #[cfg_attr(
192        any(feature = "instrument_game", feature = "instrument_all"),
193        instrument
194    )]
195    pub fn current_position(&self) -> Board {
196        #[cfg(feature = "cache_game_state")]
197        {
198            self.boards.last().expect("At least one board").clone()
199        }
200        #[cfg(not(feature = "cache_game_state"))]
201        {
202            let mut copy = self.start_pos;
203
204            for x in self.moves.iter() {
205                if let Action::MakeMove(m) = *x {
206                    copy = copy.make_move_new(m);
207                }
208            }
209
210            copy
211        }
212    }
213
214    /// Determine if a player can legally declare a draw by 3-fold repetition or 50-move rule.
215    ///
216    /// ```
217    /// use candidate::{Game, Square, ChessMove};
218    ///
219    /// let b1c3 = ChessMove::new(Square::B1, Square::C3, None);
220    /// let c3b1 = ChessMove::new(Square::C3, Square::B1, None);
221    ///
222    /// let b8c6 = ChessMove::new(Square::B8, Square::C6, None);
223    /// let c6b8 = ChessMove::new(Square::C6, Square::B8, None);
224    ///
225    /// let mut game = Game::new();
226    /// assert_eq!(game.can_declare_draw(), false);
227    ///
228    /// game.make_move(b1c3);
229    /// game.make_move(b8c6);
230    /// game.make_move(c3b1);
231    /// game.make_move(c6b8);
232    ///
233    /// assert_eq!(game.can_declare_draw(), false); // position has shown up twice
234    ///
235    /// game.make_move(b1c3);
236    /// game.make_move(b8c6);
237    /// game.make_move(c3b1);
238    /// game.make_move(c6b8);
239    /// assert_eq!(game.can_declare_draw(), true); // position has shown up three times
240    /// ```
241    #[cfg_attr(
242        any(feature = "instrument_game", feature = "instrument_all"),
243        instrument
244    )]
245    pub fn can_declare_draw(&self) -> bool {
246        if self.result().is_some() {
247            return false;
248        }
249
250        let mut legal_moves_per_turn: Vec<(u64, Vec<ChessMove>)> = vec![];
251
252        let mut board = self.start_pos;
253        let mut reversible_moves = 0;
254
255        // Loop over each move, counting the reversible_moves for draw by 50 move rule,
256        // and filling a list of legal_moves_per_turn list for 3-fold repitition
257        legal_moves_per_turn.push((board.get_hash(), MoveGen::new_legal(&board).collect()));
258        for x in self.moves.iter() {
259            if let Action::MakeMove(m) = *x {
260                let white_castle_rights = board.castle_rights(Color::White);
261                let black_castle_rights = board.castle_rights(Color::Black);
262                if board.piece_on(m.get_source()) == Some(Piece::Pawn) {
263                    reversible_moves = 0;
264                    legal_moves_per_turn.clear();
265                } else if board.piece_on(m.get_dest()).is_some() {
266                    reversible_moves = 0;
267                    legal_moves_per_turn.clear();
268                } else {
269                    reversible_moves += 1;
270                }
271                board = board.make_move_new(m);
272
273                if board.castle_rights(Color::White) != white_castle_rights
274                    || board.castle_rights(Color::Black) != black_castle_rights
275                {
276                    reversible_moves = 0;
277                    legal_moves_per_turn.clear();
278                }
279                legal_moves_per_turn.push((board.get_hash(), MoveGen::new_legal(&board).collect()));
280            }
281        }
282
283        if reversible_moves >= 100 {
284            return true;
285        }
286
287        // Detect possible draw by 3 fold repitition
288        let last_moves = legal_moves_per_turn[legal_moves_per_turn.len() - 1].clone();
289
290        for i in 1..(legal_moves_per_turn.len() - 1) {
291            for j in 0..i {
292                if legal_moves_per_turn[i] == last_moves && legal_moves_per_turn[j] == last_moves {
293                    return true;
294                }
295            }
296        }
297
298        return false;
299    }
300
301    /// Declare a draw by 3-fold repitition or 50-move rule.
302    ///
303    /// ```
304    /// use candidate::{Game, Square, ChessMove};
305    ///
306    /// let b1c3 = ChessMove::new(Square::B1, Square::C3, None);
307    /// let c3b1 = ChessMove::new(Square::C3, Square::B1, None);
308    ///
309    /// let b8c6 = ChessMove::new(Square::B8, Square::C6, None);
310    /// let c6b8 = ChessMove::new(Square::C6, Square::B8, None);
311    ///
312    /// let mut game = Game::new();
313    /// assert_eq!(game.can_declare_draw(), false);
314    ///
315    /// game.make_move(b1c3);
316    /// game.make_move(b8c6);
317    /// game.make_move(c3b1);
318    /// game.make_move(c6b8);
319    ///
320    /// assert_eq!(game.can_declare_draw(), false); // position has shown up twice
321    ///
322    /// game.make_move(b1c3);
323    /// game.make_move(b8c6);
324    /// game.make_move(c3b1);
325    /// game.make_move(c6b8);
326    /// assert_eq!(game.can_declare_draw(), true); // position has shown up three times
327    /// game.declare_draw();
328    /// ```
329    #[cfg_attr(
330        any(feature = "instrument_game", feature = "instrument_all"),
331        instrument
332    )]
333    pub fn declare_draw(&mut self) -> bool {
334        if self.can_declare_draw() {
335            self.moves.push(Action::DeclareDraw);
336            true
337        } else {
338            false
339        }
340    }
341
342    /// Make a chess move on the board
343    ///
344    /// ```
345    /// use candidate::{Game, MoveGen};
346    ///
347    /// let mut game = Game::new();
348    ///
349    /// let mut movegen = MoveGen::new_legal(&game.current_position());
350    ///
351    /// game.make_move(movegen.next().expect("At least one legal move"));
352    /// ```
353    #[cfg_attr(
354        any(feature = "instrument_game", feature = "instrument_all"),
355        instrument
356    )]
357    pub fn make_move(&mut self, chess_move: ChessMove) -> Option<String> {
358        if self.result().is_some() {
359            return None;
360        }
361
362        let initial_position = self.current_position();
363        if !initial_position.legal(chess_move) {
364            return None;
365        }
366
367        #[cfg(feature = "cache_game_state")]
368        {
369            self.boards.push(initial_position.make_move_new(chess_move));
370        }
371
372        self.moves.push(Action::MakeMove(chess_move));
373        Some(Self::generate_san(
374            &initial_position,
375            &self.current_position(),
376            chess_move,
377        ))
378    }
379
380    // Generate SAN for a given board and move.
381    // Move must be legal.
382    #[cfg_attr(
383        any(feature = "instrument_game", feature = "instrument_all"),
384        instrument
385    )]
386    fn generate_san(initial_board: &Board, final_board: &Board, chess_move: ChessMove) -> String {
387        let mut san = String::new();
388
389        // Add piece type if not pawn
390        let piece = initial_board
391            .piece_on(chess_move.get_source())
392            .expect("the move is valid");
393
394        // if the move is a castle, return the appropriate string
395        if piece == Piece::King
396            && chess_move.get_source().get_file() == crate::file::File::E
397            && chess_move.get_dest().get_rank() == chess_move.get_source().get_rank()
398        {
399            if chess_move.get_dest().get_file() == crate::file::File::G {
400                return "O-O".to_string();
401            } else if chess_move.get_dest().get_file() == crate::file::File::C {
402                return "O-O-O".to_string();
403            }
404        }
405        if piece != Piece::Pawn {
406            san.push(
407                // white is uppercase??
408                piece
409                    .to_string(Color::White)
410                    .to_uppercase()
411                    .chars()
412                    .next()
413                    .expect("a piece has a valid string notation"),
414            );
415        }
416        let is_capture = initial_board.piece_on(chess_move.get_dest()).is_some()
417            || initial_board.en_passant_target() == Some(chess_move.get_dest());
418
419        if is_capture {
420            if piece == Piece::Pawn {
421                san.push(
422                    chess_move
423                        .get_source()
424                        .to_string()
425                        .chars()
426                        .next()
427                        .expect("a square has a valid string notation"),
428                );
429            }
430
431            san.push('x');
432        }
433
434        san.push_str(&chess_move.get_dest().to_string());
435
436        let promotion = chess_move.get_promotion();
437        if let Some(promotion) = promotion {
438            san.push_str(&format!("={promotion}").to_uppercase());
439        }
440
441        if final_board.has_checkers() {
442            if MoveGen::has_legal_moves(final_board) {
443                san.push('+');
444            } else {
445                san.push('#');
446            }
447        }
448
449        san
450    }
451
452    /// Who's turn is it to move?
453    ///
454    /// ```
455    /// use candidate::{Game, Color};
456    ///
457    /// let game = Game::new();
458    /// assert_eq!(game.side_to_move(), Color::White);
459    /// ```
460    #[cfg_attr(
461        any(feature = "instrument_game", feature = "instrument_all"),
462        instrument
463    )]
464    pub fn side_to_move(&self) -> Color {
465        #[cfg(feature = "cache_game_state")]
466        {
467            self.current_position().side_to_move()
468        }
469        #[cfg(not(feature = "cache_game_state"))]
470        {
471            let move_count = self
472                .moves
473                .iter()
474                .filter(|m| match *m {
475                    Action::MakeMove(_) => true,
476                    _ => false,
477                })
478                .count()
479                + if self.start_pos.side_to_move() == Color::White {
480                    0
481                } else {
482                    1
483                };
484
485            if move_count % 2 == 0 {
486                Color::White
487            } else {
488                Color::Black
489            }
490        }
491    }
492
493    /// Offer a draw to my opponent.  `color` is the player who offered the draw.  The draw must be
494    /// accepted before my opponent moves.
495    ///
496    /// ```
497    /// use candidate::{Game, Color};
498    ///
499    /// let mut game = Game::new();
500    /// game.offer_draw(Color::White);
501    /// ```
502    #[cfg_attr(
503        any(feature = "instrument_game", feature = "instrument_all"),
504        instrument
505    )]
506    pub fn offer_draw(&mut self, color: Color) -> bool {
507        if self.result().is_some() {
508            return false;
509        }
510        self.moves.push(Action::OfferDraw(color));
511        return true;
512    }
513
514    /// Accept a draw offer from my opponent.
515    ///
516    /// ```
517    /// use candidate::{Game, MoveGen, Color};
518    ///
519    /// let mut game = Game::new();
520    /// game.offer_draw(Color::Black);
521    /// assert_eq!(game.accept_draw(), true);
522    ///
523    /// let mut game2 = Game::new();
524    /// let mut movegen = MoveGen::new_legal(&game2.current_position());
525    /// game2.offer_draw(Color::Black);
526    /// game2.make_move(movegen.next().expect("At least one legal move"));
527    /// assert_eq!(game2.accept_draw(), false);
528    /// ```
529    #[cfg_attr(
530        any(feature = "instrument_game", feature = "instrument_all"),
531        instrument
532    )]
533    pub fn accept_draw(&mut self) -> bool {
534        if self.result().is_some() {
535            return false;
536        }
537        if self.moves.len() > 0 {
538            if self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::White)
539                || self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::Black)
540            {
541                self.moves.push(Action::AcceptDraw);
542                return true;
543            }
544        }
545
546        if self.moves.len() > 1 {
547            if self.moves[self.moves.len() - 2] == Action::OfferDraw(!self.side_to_move()) {
548                self.moves.push(Action::AcceptDraw);
549                return true;
550            }
551        }
552
553        false
554    }
555
556    /// `color` resigns the game
557    ///
558    /// ```
559    /// use candidate::{Game, Color};
560    ///
561    /// let mut game = Game::new();
562    /// game.resign(Color::White);
563    /// ```
564    #[cfg_attr(
565        any(feature = "instrument_game", feature = "instrument_all"),
566        instrument
567    )]
568    pub fn resign(&mut self, color: Color) -> bool {
569        if self.result().is_some() {
570            return false;
571        }
572        self.moves.push(Action::Resign(color));
573        return true;
574    }
575}
576
577impl FromStr for Game {
578    type Err = Error;
579    #[cfg_attr(
580        any(feature = "instrument_game", feature = "instrument_all"),
581        instrument
582    )]
583    fn from_str(fen: &str) -> Result<Self, Self::Err> {
584        Ok(Game::new_with_board(Board::from_str(fen)?))
585    }
586}
587
588#[cfg(test)]
589pub fn fake_pgn_parser(moves: &str) -> Game {
590    moves
591        .split_whitespace()
592        .filter(|s| !s.ends_with("."))
593        .fold(Game::new(), |mut g, m| {
594            g.make_move(ChessMove::from_san(&g.current_position(), m).expect("Valid SAN Move"));
595            g
596        })
597}
598
599#[test]
600pub fn test_can_declare_draw() {
601    let game = fake_pgn_parser(
602        "1. Nc3 d5 2. e3 Nc6 3. Nf3 Nf6 4. Bb5 a6 5. Bxc6+ bxc6 6. Ne5 Qd6 7. d4 Nd7
603                8. f4 Nxe5 9. dxe5 Qg6 10. O-O Bf5 11. e4 Bxe4 12. Nxe4 Qxe4 13. Re1 Qb4
604                14. e6 f6 15. Be3 g6 16. Qd4 Qxd4 17. Bxd4 Bh6 18. g3 g5 19. f5 g4 20. Rad1
605                Rg8 21. b3 Rb8 22. c4 dxc4 23. bxc4 Rd8 24. Kg2 Rc8 25. Bc5 Rg5 26. Rd7 Bf8
606                27. Rf1 a5 28. Kg1 a4 29. Bb4 Rh5 30. Rf4 Rg5 31. Rf1 Rh5 32. Rf4 Rg5 33.
607                Ba5",
608    );
609    assert!(!game.can_declare_draw());
610
611    // three fold
612    let game = fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1 Ng8");
613    assert!(game.can_declare_draw());
614
615    // three fold (again)
616    let game = fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1");
617    assert!(game.can_declare_draw());
618
619    // three fold, but with a move at the end that breaks the draw cycle
620    let game =
621        fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1 Ng8 7. e4");
622    assert!(!game.can_declare_draw());
623
624    // three fold, but with a move at the end that breaks the draw cycle
625    let game =
626        fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1 Ng8 7. e4");
627    assert!(!game.can_declare_draw());
628
629    // fifty move rule
630    let game = fake_pgn_parser("1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. e4 d6 5. Nf3 O-O 6. Be2 e5 7. O-O Nc6 8. d5 Ne7 9. Nd2 a5 10. Rb1 Nd7 11. a3 f5 12. b4 Kh8 13. f3 Ng8 14. Qc2 Ngf6 15. Nb5 axb4 16. axb4 Nh5 17. g3 Ndf6 18. c5 Bd7 19. Rb3 Nxg3 20. hxg3 Nh5 21. f4 exf4 22. c6 bxc6 23. dxc6 Nxg3 24. Rxg3 fxg3 25. cxd7 g2 26. Rf3 Qxd7 27. Bb2 fxe4 28. Rxf8+ Rxf8 29. Bxg7+ Qxg7 30. Qxe4 Qf6 31. Nf3 Qf4 32. Qe7 Rf7 33. Qe6 Rf6 34. Qe8+ Rf8 35. Qe7 Rf7 36. Qe6 Rf6 37. Qb3 g5 38. Nxc7 g4 39. Nd5 Qc1+ 40. Qd1 Qxd1+ 41. Bxd1 Rf5 42. Ne3 Rf4 43. Ne1 Rxb4 44. Bxg4 h5 45. Bf3 d5 46. N3xg2 h4 47. Nd3 Ra4 48. Ngf4 Kg7 49. Kg2 Kf6 50. Bxd5 Ra5 51. Bc6 Ra6 52. Bb7 Ra3 53. Be4 Ra4 54. Bd5 Ra5 55. Bc6 Ra6 56. Bf3 Kg5 57. Bb7 Ra1 58. Bc8 Ra4 59. Kf3 Rc4 60. Bd7 Kf6 61. Kg4 Rd4 62. Bc6 Rd8 63. Kxh4 Rg8 64. Be4 Rg1 65. Nh5+ Ke6 66. Ng3 Kf6 67. Kg4 Ra1 68. Bd5 Ra5 69. Bf3 Ra1 70. Kf4 Ke6 71. Nc5+ Kd6 72. Nge4+ Ke7 73. Ke5 Rf1 74. Bg4 Rg1 75. Be6 Re1 76. Bc8 Rc1 77. Kd4 Rd1+ 78. Nd3 Kf7 79. Ke3 Ra1 80. Kf4 Ke7 81. Nb4 Rc1 82. Nd5+ Kf7 83. Bd7 Rf1+ 84. Ke5 Ra1 85. Ng5+ Kg6 86. Nf3 Kg7 87. Bg4 Kg6 88. Nf4+ Kg7 89. Nd4 Re1+ 90. Kf5 Rc1 91. Be2 Re1 92. Bh5 Ra1 93. Nfe6+ Kh6 94. Be8 Ra8 95. Bc6 Ra1 96. Kf6 Kh7 97. Ng5+ Kh8 98. Nde6 Ra6 99. Be8 Ra8 100. Bh5 Ra1 101. Bg6 Rf1+ 102. Ke7 Ra1 103. Nf7+ Kg8 104. Nh6+ Kh8 105. Nf5 Ra7+ 106. Kf6 Ra1 107. Ne3 Re1 108. Nd5 Rg1 109. Bf5 Rf1 110. Ndf4 Ra1 111. Ng6+ Kg8 112. Ne7+ Kh8 113. Ng5");
631    assert!(game.can_declare_draw());
632
633    let game = fake_pgn_parser("1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. e4 d6 5. Nf3 O-O 6. Be2 e5 7. O-O Nc6 8. d5 Ne7 9. Nd2 a5 10. Rb1 Nd7 11. a3 f5 12. b4 Kh8 13. f3 Ng8 14. Qc2 Ngf6 15. Nb5 axb4 16. axb4 Nh5 17. g3 Ndf6 18. c5 Bd7 19. Rb3 Nxg3 20. hxg3 Nh5 21. f4 exf4 22. c6 bxc6 23. dxc6 Nxg3 24. Rxg3 fxg3 25. cxd7 g2 26. Rf3 Qxd7 27. Bb2 fxe4 28. Rxf8+ Rxf8 29. Bxg7+ Qxg7 30. Qxe4 Qf6 31. Nf3 Qf4 32. Qe7 Rf7 33. Qe6 Rf6 34. Qe8+ Rf8 35. Qe7 Rf7 36. Qe6 Rf6 37. Qb3 g5 38. Nxc7 g4 39. Nd5 Qc1+ 40. Qd1 Qxd1+ 41. Bxd1 Rf5 42. Ne3 Rf4 43. Ne1 Rxb4 44. Bxg4 h5 45. Bf3 d5 46. N3xg2 h4 47. Nd3 Ra4 48. Ngf4 Kg7 49. Kg2 Kf6 50. Bxd5 Ra5 51. Bc6 Ra6 52. Bb7 Ra3 53. Be4 Ra4 54. Bd5 Ra5 55. Bc6 Ra6 56. Bf3 Kg5 57. Bb7 Ra1 58. Bc8 Ra4 59. Kf3 Rc4 60. Bd7 Kf6 61. Kg4 Rd4 62. Bc6 Rd8 63. Kxh4 Rg8 64. Be4 Rg1 65. Nh5+ Ke6 66. Ng3 Kf6 67. Kg4 Ra1 68. Bd5 Ra5 69. Bf3 Ra1 70. Kf4 Ke6 71. Nc5+ Kd6 72. Nge4+ Ke7 73. Ke5 Rf1 74. Bg4 Rg1 75. Be6 Re1 76. Bc8 Rc1 77. Kd4 Rd1+ 78. Nd3 Kf7 79. Ke3 Ra1 80. Kf4 Ke7 81. Nb4 Rc1 82. Nd5+ Kf7 83. Bd7 Rf1+ 84. Ke5 Ra1 85. Ng5+ Kg6 86. Nf3 Kg7 87. Bg4 Kg6 88. Nf4+ Kg7 89. Nd4 Re1+ 90. Kf5 Rc1 91. Be2 Re1 92. Bh5 Ra1 93. Nfe6+ Kh6 94. Be8 Ra8 95. Bc6 Ra1 96. Kf6 Kh7 97. Ng5+ Kh8 98. Nde6 Ra6 99. Be8 Ra8 100. Bh5 Ra1 101. Bg6 Rf1+ 102. Ke7 Ra1 103. Nf7+ Kg8 104. Nh6+ Kh8 105. Nf5 Ra7+ 106. Kf6 Ra1 107. Ne3 Re1 108. Nd5 Rg1 109. Bf5 Rf1 110. Ndf4 Ra1 111. Ng6+ Kg8 112. Ne7+ Kh8");
634    assert!(!game.can_declare_draw());
635}
636
637#[test]
638pub fn test_make_move() {
639    use crate::square::Square;
640
641    let mut game = Game::new();
642
643    #[rustfmt::skip]
644    let move_list = [
645        (ChessMove::new(Square::D2, Square::D4, None), "d4"),
646        (ChessMove::new(Square::D2, Square::D4, None), ""),
647        (ChessMove::new(Square::D7, Square::D5, None), "d5"),
648        (ChessMove::new(Square::C2, Square::C4, None), "c4"),
649        (ChessMove::new(Square::D5, Square::C4, None), "dxc4"),
650        (ChessMove::new(Square::D1, Square::A4, None), "Qa4+"),
651        (ChessMove::new(Square::C8, Square::D7, None), "Bd7"),
652        (ChessMove::new(Square::A4, Square::C4, None), "Qxc4"),
653        (ChessMove::new(Square::G8, Square::F6, None), "Nf6"),
654        (ChessMove::new(Square::E2, Square::E4, None), "e4"),
655        (ChessMove::new(Square::F6, Square::D5, None), "Nd5"),
656        (ChessMove::new(Square::E4, Square::D5, None), "exd5"),
657        (ChessMove::new(Square::E7, Square::E5, None), "e5"),
658        (ChessMove::new(Square::D5, Square::E6, None), "dxe6"), // en passant
659        (ChessMove::new(Square::D8, Square::H4, None), "Qh4"),
660        (ChessMove::new(Square::C4, Square::D5, None), "Qd5"),
661        (ChessMove::new(Square::F8, Square::B4, None), "Bb4+"),
662        (ChessMove::new(Square::C1, Square::D2, None), "Bd2"),
663        (ChessMove::new(Square::E8, Square::G8, None), "O-O"),
664        (ChessMove::new(Square::B1, Square::C3, None), "Nc3"),
665        (ChessMove::new(Square::H4, Square::H6, None), "Qh6"),
666        (ChessMove::new(Square::E1, Square::C1, None), "O-O-O"),
667        (ChessMove::new(Square::H6, Square::H4, None), "Qh4"),
668        (ChessMove::new(Square::E6, Square::D7, None), "exd7"),
669        (ChessMove::new(Square::H4, Square::H3, None), "Qh3"),
670        (ChessMove::new(Square::D7, Square::D8, Some(Piece::Queen)), "d8=Q"),
671        (ChessMove::new(Square::F8, Square::D8, None), "Rxd8"),
672        (ChessMove::new(Square::C3, Square::B1, None), "Nb1"),
673        (ChessMove::new(Square::B4, Square::D2, None), "Bxd2+"),
674        (ChessMove::new(Square::B1, Square::D2, None), "Nxd2"),
675        (ChessMove::new(Square::H3, Square::D3, None), "Qd3"),
676        (ChessMove::new(Square::D5, Square::D8, None), "Qxd8#"),
677    ];
678
679    for (mv, expected_san) in move_list.iter() {
680        let san = game.make_move(*mv);
681        if (*expected_san).len() == 0 {
682            assert_eq!(san, None);
683        } else {
684            assert_eq!(
685                san,
686                Some(expected_san.to_string()),
687                "\n'{}' for move: '{}' didn't match the expected san ({}). position: \n{}",
688                san.clone().unwrap_or_default(),
689                mv,
690                expected_san,
691                game.current_position().color_combined(Color::White)
692                    | game.current_position().color_combined(Color::Black)
693            );
694        }
695    }
696}