chess/
chess.rs

1use std::str::FromStr;
2
3use crate::{Board, ChessMove, Color, Square};
4
5/// The Result of the game.
6#[derive(Copy, Clone, Eq, PartialEq, Default, Debug)]
7pub enum GameState {
8    /// The game is still ongoing.
9    #[default]
10    Ongoing,
11    /// A player is checkmates.
12    Checkmates(Color),
13    /// Draw by Stalemate.
14    Stalemate,
15    /// Draw by request accepted (ie. Mutual Agreement).
16    DrawAccepted,
17    /// Draw declared by a player.
18    DrawDeclared,
19    /// The [`Color`] has resigns.
20    Resigns(Color),
21}
22
23impl GameState {
24    /// Verify if the game is ongoing.
25    pub fn is_ongoing(&self) -> bool {
26        matches!(self, GameState::Ongoing)
27    }
28
29    /// Verify if the game is finish.
30    pub fn is_finish(&self) -> bool {
31        !matches!(self, GameState::Ongoing)
32    }
33}
34
35/// A Standard Chess game.
36///
37/// TODO: Add a timer for each player.
38#[derive(Clone, Eq, PartialEq, Default, Debug)]
39pub struct Chess {
40    pub(crate) board: Board,
41    pub(crate) square_focused: Option<Square>,
42    pub(crate) offer_draw: bool,
43    pub(crate) state: GameState,
44    pub(crate) history: Vec<String>,
45}
46
47impl Chess {
48    /// Create a new instance of Chess.
49    pub fn new(board: Board) -> Self {
50        Chess {
51            board,
52            square_focused: None,
53            offer_draw: false,
54            history: vec![],
55            state: GameState::Ongoing,
56        }
57    }
58
59    /// Get the history of the game.
60    ///
61    /// The [`Vec`] contains a FEN-string.
62    pub fn history(&self) -> Vec<String> {
63        self.history.clone()
64    }
65
66    /// Go back one step in history.
67    ///
68    /// If the history is empty, reset the board to it's [`default`][Board::default] value.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use chess::{Chess, Square};
74    /// let mut chess = Chess::default();
75    /// let expected = Chess::default();
76    /// chess.play(Square::A2, Square::A4);
77    /// chess.undo();
78    ///
79    /// assert_eq!(chess, expected);
80    /// ```
81    pub fn undo(&mut self) {
82        if let Some(fen) = self.history.pop() {
83            self.board = Board::from_str(fen.as_str()).expect("valid fen from history");
84        }
85    }
86
87    /// Reset the Game (board and history).
88    pub fn reset(&mut self) {
89        self.board = Board::default();
90        self.offer_draw = false;
91        self.square_focused = None;
92        self.history = vec![];
93        self.state = GameState::Ongoing;
94    }
95
96    /// Return the [`State`][GameState] of the Game.
97    pub fn state(&mut self) -> GameState {
98        self.state
99    }
100
101    /// Base function to call when a user click on the screen.
102    pub fn play(&mut self, from: Square, to: Square) {
103        let m = ChessMove::new(from, to);
104        if self.board.is_legal(m) {
105            self.history.push(self.board.to_string());
106            self.board.update(m);
107            if self.offer_draw {
108                self.offer_draw = false;
109            }
110            self.state = self.board.state();
111        }
112        self.square_focused = None;
113    }
114
115    /// The current player offer a draw.
116    ///
117    /// The offer is cancel when the player play.
118    pub fn offer_draw(&mut self) {
119        self.offer_draw = true;
120    }
121
122    /// Accept the draw. Assumes that a draw is offered.
123    pub fn accept_draw(&mut self) {
124        self.state = GameState::DrawAccepted;
125    }
126
127    /// Verify if a player can legally declare a draw by 3-fold repetition or 50-move rule.
128    pub fn can_declare_draw(&self) -> bool {
129        let t = self.history.len();
130        if t >= 8 {
131            let fen_boards = [
132                self.board
133                    .to_string()
134                    .split_once(' ')
135                    .unwrap()
136                    .0
137                    .to_string(),
138                self.history[t - 4].split_once(' ').unwrap().0.to_string(),
139                self.history[t - 8].split_once(' ').unwrap().0.to_string(),
140            ];
141            if fen_boards[0] == fen_boards[1] && fen_boards[1] == fen_boards[2] {
142                return true;
143            }
144            if self.board.halfmoves() >= 100 {
145                return true;
146            }
147        }
148        false
149    }
150
151    /// Declare a draw by 3-fold repetition or 50-move rule. Assumes that a draw can be declare.
152    pub fn declare_draw(&mut self) {
153        self.state = GameState::DrawDeclared;
154    }
155
156    /// [`Color`] resigns the game.
157    pub fn resign(&mut self, color: Color) {
158        self.state = GameState::Resigns(color);
159    }
160}