Skip to main content

chess/
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::str::FromStr;
8
9/// Contains all actions supported within the game
10#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq)]
11pub enum Action {
12	MakeMove(ChessMove),
13	OfferDraw(Color),
14	AcceptDraw,
15	DeclareDraw,
16	Resign(Color),
17}
18
19/// What was the result of this game?
20#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
21pub enum GameResult {
22	WhiteCheckmates,
23	WhiteResigns,
24	BlackCheckmates,
25	BlackResigns,
26	Stalemate,
27	DrawAccepted,
28	DrawDeclared,
29}
30
31/// For UI/UCI Servers, store a game object which allows you to determine
32/// draw by 3 fold repitition, draw offers, resignations, and moves.
33///
34/// This structure is slow compared to using `Board` directly, so it is
35/// not recommended for engines.
36#[derive(Clone, Debug)]
37pub struct Game {
38	start_pos: Board,
39	moves: Vec<Action>,
40}
41
42impl Default for Game {
43	fn default() -> Self {
44		Self::new()
45	}
46}
47
48impl Game {
49	/// Create a new `Game` with the initial position.
50	///
51	/// ```
52	/// use chess::{Game, Board};
53	///
54	/// let game = Game::new();
55	/// assert_eq!(game.current_position(), Board::default());
56	/// ```
57	pub fn new() -> Game {
58		Game { start_pos: Board::default(), moves: vec![] }
59	}
60
61	/// Create a new `Game` with a specific starting position.
62	///
63	/// ```
64	/// use chess::{Game, Board};
65	///
66	/// let game = Game::new_with_board(Board::default());
67	/// assert_eq!(game.current_position(), Board::default());
68	/// ```
69	pub fn new_with_board(board: Board) -> Game {
70		Game { start_pos: board, moves: vec![] }
71	}
72
73	/// Get all actions made in this game (moves, draw offers, resignations, etc.)
74	///
75	/// ```
76	/// use chess::{Game, MoveGen, Color};
77	///
78	/// let mut game = Game::new();
79	/// let mut movegen = MoveGen::new_legal(&game.current_position());
80	///
81	/// game.make_move(movegen.next().expect("At least one valid move"));
82	/// game.resign(Color::Black);
83	/// assert_eq!(game.actions().len(), 2);
84	/// ```
85	pub fn actions(&self) -> &Vec<Action> {
86		&self.moves
87	}
88
89	/// What is the status of this game?
90	///
91	/// ```
92	/// use chess::Game;
93	///
94	/// let game = Game::new();
95	/// assert!(game.result().is_none());
96	/// ```
97	pub fn result(&self) -> Option<GameResult> {
98		match self.current_position().status() {
99			BoardStatus::Checkmate => {
100				if self.side_to_move() == Color::White {
101					Some(GameResult::BlackCheckmates)
102				} else {
103					Some(GameResult::WhiteCheckmates)
104				}
105			}
106			BoardStatus::Stalemate => Some(GameResult::Stalemate),
107			BoardStatus::Ongoing => {
108				if self.moves.is_empty() {
109					None
110				} else if self.moves[self.moves.len() - 1] == Action::AcceptDraw {
111					Some(GameResult::DrawAccepted)
112				} else if self.moves[self.moves.len() - 1] == Action::DeclareDraw {
113					Some(GameResult::DrawDeclared)
114				} else if self.moves[self.moves.len() - 1] == Action::Resign(Color::White) {
115					Some(GameResult::WhiteResigns)
116				} else if self.moves[self.moves.len() - 1] == Action::Resign(Color::Black) {
117					Some(GameResult::BlackResigns)
118				} else {
119					None
120				}
121			}
122		}
123	}
124
125	/// Create a new `Game` object from an FEN string.
126	///
127	/// ```
128	/// use chess::{Game, Board};
129	///
130	/// // This is the better way:
131	/// # {
132	/// use std::str::FromStr;
133	/// let game: Game = Game::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Valid FEN");
134	/// let game2: Result<Game, _> = Game::from_str("Invalid FEN");
135	/// assert!(game2.is_err());
136	/// # }
137	///
138	/// // This still works
139	/// # {
140	/// let game = Game::new_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Valid FEN");
141	/// let game2 = Game::new_from_fen("Invalid FEN");
142	/// assert!(game2.is_none());
143	/// # }
144	/// ```
145	#[deprecated(since = "3.1.0", note = "Please use Game::from_str(fen)? instead.")]
146	pub fn new_from_fen(fen: &str) -> Option<Game> {
147		Game::from_str(fen).ok()
148	}
149
150	/// Get the current position on the board from the `Game` object.
151	///
152	/// ```
153	/// use chess::{Game, Board};
154	///
155	/// let game = Game::new();
156	/// assert_eq!(game.current_position(), Board::default());
157	/// ```
158	pub fn current_position(&self) -> Board {
159		let mut copy = self.start_pos;
160
161		for x in self.moves.iter() {
162			if let Action::MakeMove(m) = *x {
163				copy = copy.make_move_new(m);
164			}
165		}
166
167		copy
168	}
169
170	/// Determine if a player can legally declare a draw by 3-fold repetition or 50-move rule.
171	///
172	/// ```
173	/// use chess::{Game, Square, ChessMove};
174	///
175	/// let b1c3 = ChessMove::new(Square::B1, Square::C3, None);
176	/// let c3b1 = ChessMove::new(Square::C3, Square::B1, None);
177	///
178	/// let b8c6 = ChessMove::new(Square::B8, Square::C6, None);
179	/// let c6b8 = ChessMove::new(Square::C6, Square::B8, None);
180	///
181	/// let mut game = Game::new();
182	/// assert_eq!(game.can_declare_draw(), false);
183	///
184	/// game.make_move(b1c3);
185	/// game.make_move(b8c6);
186	/// game.make_move(c3b1);
187	/// game.make_move(c6b8);
188	///
189	/// assert_eq!(game.can_declare_draw(), false); // position has shown up twice
190	///
191	/// game.make_move(b1c3);
192	/// game.make_move(b8c6);
193	/// game.make_move(c3b1);
194	/// game.make_move(c6b8);
195	/// assert_eq!(game.can_declare_draw(), true); // position has shown up three times
196	/// ```
197	pub fn can_declare_draw(&self) -> bool {
198		if self.result().is_some() {
199			return false;
200		}
201
202		let mut legal_moves_per_turn: Vec<(u64, Vec<ChessMove>)> = vec![];
203
204		let mut board = self.start_pos;
205		let mut reversible_moves = 0;
206
207		// Loop over each move, counting the reversible_moves for draw by 50 move rule,
208		// and filling a list of legal_moves_per_turn list for 3-fold repitition
209		legal_moves_per_turn.push((board.get_hash(), MoveGen::new_legal(&board).collect()));
210		for x in self.moves.iter() {
211			if let Action::MakeMove(m) = *x {
212				let white_castle_rights = board.castle_rights(Color::White);
213				let black_castle_rights = board.castle_rights(Color::Black);
214				if board.piece_on(m.get_source()) == Some(Piece::Pawn) || board.piece_on(m.get_dest()).is_some() {
215					reversible_moves = 0;
216					legal_moves_per_turn.clear();
217				} else {
218					reversible_moves += 1;
219				}
220				board = board.make_move_new(m);
221				if board.castle_rights(Color::White) != white_castle_rights || board.castle_rights(Color::Black) != black_castle_rights {
222					reversible_moves = 0;
223					legal_moves_per_turn.clear();
224				}
225				legal_moves_per_turn.push((board.get_hash(), MoveGen::new_legal(&board).collect()));
226			}
227		}
228
229		if reversible_moves >= 100 {
230			return true;
231		}
232
233		// Detect possible draw by 3 fold repitition
234		let last_moves = legal_moves_per_turn[legal_moves_per_turn.len() - 1].clone();
235
236		for i in 1..(legal_moves_per_turn.len() - 1) {
237			for j in 0..i {
238				if legal_moves_per_turn[i] == last_moves && legal_moves_per_turn[j] == last_moves {
239					return true;
240				}
241			}
242		}
243
244		false
245	}
246
247	/// Declare a draw by 3-fold repitition or 50-move rule.
248	///
249	/// ```
250	/// use chess::{Game, Square, ChessMove};
251	///
252	/// let b1c3 = ChessMove::new(Square::B1, Square::C3, None);
253	/// let c3b1 = ChessMove::new(Square::C3, Square::B1, None);
254	///
255	/// let b8c6 = ChessMove::new(Square::B8, Square::C6, None);
256	/// let c6b8 = ChessMove::new(Square::C6, Square::B8, None);
257	///
258	/// let mut game = Game::new();
259	/// assert_eq!(game.can_declare_draw(), false);
260	///
261	/// game.make_move(b1c3);
262	/// game.make_move(b8c6);
263	/// game.make_move(c3b1);
264	/// game.make_move(c6b8);
265	///
266	/// assert_eq!(game.can_declare_draw(), false); // position has shown up twice
267	///
268	/// game.make_move(b1c3);
269	/// game.make_move(b8c6);
270	/// game.make_move(c3b1);
271	/// game.make_move(c6b8);
272	/// assert_eq!(game.can_declare_draw(), true); // position has shown up three times
273	/// game.declare_draw();
274	/// ```
275	pub fn declare_draw(&mut self) -> bool {
276		if self.can_declare_draw() {
277			self.moves.push(Action::DeclareDraw);
278			true
279		} else {
280			false
281		}
282	}
283
284	/// Make a chess move on the board
285	///
286	/// ```
287	/// use chess::{Game, MoveGen};
288	///
289	/// let mut game = Game::new();
290	///
291	/// let mut movegen = MoveGen::new_legal(&game.current_position());
292	///
293	/// game.make_move(movegen.next().expect("At least one legal move"));
294	/// ```
295	pub fn make_move(&mut self, chess_move: ChessMove) -> bool {
296		if self.result().is_some() {
297			return false;
298		}
299		if self.current_position().legal(chess_move) {
300			self.moves.push(Action::MakeMove(chess_move));
301			true
302		} else {
303			false
304		}
305	}
306
307	/// Who's turn is it to move?
308	///
309	/// ```
310	/// use chess::{Game, Color};
311	///
312	/// let game = Game::new();
313	/// assert_eq!(game.side_to_move(), Color::White);
314	/// ```
315	pub fn side_to_move(&self) -> Color {
316		let move_count =
317			self.moves.iter().filter(|m| matches!(*m, Action::MakeMove(_))).count() + if self.start_pos.side_to_move() == Color::White { 0 } else { 1 };
318
319		if move_count % 2 == 0 { Color::White } else { Color::Black }
320	}
321
322	/// Offer a draw to my opponent.  `color` is the player who offered the draw.  The draw must be
323	/// accepted before my opponent moves.
324	///
325	/// ```
326	/// use chess::{Game, Color};
327	///
328	/// let mut game = Game::new();
329	/// game.offer_draw(Color::White);
330	/// ```
331	pub fn offer_draw(&mut self, color: Color) -> bool {
332		if self.result().is_some() {
333			return false;
334		}
335		self.moves.push(Action::OfferDraw(color));
336		true
337	}
338
339	/// Accept a draw offer from my opponent.
340	///
341	/// ```
342	/// use chess::{Game, MoveGen, Color};
343	///
344	/// let mut game = Game::new();
345	/// game.offer_draw(Color::Black);
346	/// assert_eq!(game.accept_draw(), true);
347	///
348	/// let mut game2 = Game::new();
349	/// let mut movegen = MoveGen::new_legal(&game2.current_position());
350	/// game2.offer_draw(Color::Black);
351	/// game2.make_move(movegen.next().expect("At least one legal move"));
352	/// assert_eq!(game2.accept_draw(), false);
353	/// ```
354	pub fn accept_draw(&mut self) -> bool {
355		if self.result().is_some() {
356			return false;
357		}
358		if !self.moves.is_empty()
359			&& (self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::White) || self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::Black))
360		{
361			self.moves.push(Action::AcceptDraw);
362			return true;
363		}
364
365		if self.moves.len() > 1 && self.moves[self.moves.len() - 2] == Action::OfferDraw(!self.side_to_move()) {
366			self.moves.push(Action::AcceptDraw);
367			return true;
368		}
369
370		false
371	}
372
373	/// `color` resigns the game
374	///
375	/// ```
376	/// use chess::{Game, Color};
377	///
378	/// let mut game = Game::new();
379	/// game.resign(Color::White);
380	/// ```
381	pub fn resign(&mut self, color: Color) -> bool {
382		if self.result().is_some() {
383			return false;
384		}
385		self.moves.push(Action::Resign(color));
386		true
387	}
388}
389
390impl FromStr for Game {
391	type Err = Error;
392
393	fn from_str(fen: &str) -> Result<Self, Self::Err> {
394		Ok(Game::new_with_board(Board::from_str(fen)?))
395	}
396}
397
398#[cfg(test)]
399pub fn fake_pgn_parser(moves: &str) -> Game {
400	moves.split_whitespace().filter(|s| !s.ends_with(".")).fold(Game::new(), |mut g, m| {
401		g.make_move(ChessMove::from_san(&g.current_position(), m).expect("Valid SAN Move"));
402		g
403	})
404}
405
406#[test]
407pub fn test_can_declare_draw() {
408	let game = fake_pgn_parser(
409		"1. Nc3 d5 2. e3 Nc6 3. Nf3 Nf6 4. Bb5 a6 5. Bxc6+ bxc6 6. Ne5 Qd6 7. d4 Nd7
410                8. f4 Nxe5 9. dxe5 Qg6 10. O-O Bf5 11. e4 Bxe4 12. Nxe4 Qxe4 13. Re1 Qb4
411                14. e6 f6 15. Be3 g6 16. Qd4 Qxd4 17. Bxd4 Bh6 18. g3 g5 19. f5 g4 20. Rad1
412                Rg8 21. b3 Rb8 22. c4 dxc4 23. bxc4 Rd8 24. Kg2 Rc8 25. Bc5 Rg5 26. Rd7 Bf8
413                27. Rf1 a5 28. Kg1 a4 29. Bb4 Rh5 30. Rf4 Rg5 31. Rf1 Rh5 32. Rf4 Rg5 33.
414                Ba5",
415	);
416	assert!(!game.can_declare_draw());
417
418	// three fold
419	let game = fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1 Ng8");
420	assert!(game.can_declare_draw());
421
422	// three fold (again)
423	let game = fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1");
424	assert!(game.can_declare_draw());
425
426	// three fold, but with a move at the end that breaks the draw cycle
427	let game = fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1 Ng8 7. e4");
428	assert!(!game.can_declare_draw());
429
430	// three fold, but with a move at the end that breaks the draw cycle
431	let game = fake_pgn_parser("1. Nc3 Nf6 2. Nb1 Ng8 3. Nc3 Nf6 4. Nb1 Ng8 5. Nc3 Nf6 6. Nb1 Ng8 7. e4");
432	assert!(!game.can_declare_draw());
433
434	// fifty move rule
435	let game = fake_pgn_parser(
436		"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",
437	);
438	assert!(game.can_declare_draw());
439
440	let game = fake_pgn_parser(
441		"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",
442	);
443	assert!(!game.can_declare_draw());
444}