1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::str::FromStr;

use crate::{Board, ChessMove, Color, Square};

/// The Result of the game.
#[derive(Copy, Clone, Eq, PartialEq, Default, Debug)]
pub enum GameState {
    /// The game is still ongoing.
    #[default]
    Ongoing,
    /// A player is checkmates.
    Checkmates(Color),
    /// Draw by Stalemate.
    Stalemate,
    /// Draw by request accepted (ie. Mutual Agreement).
    DrawAccepted,
    /// Draw declared by a player.
    DrawDeclared,
    /// The [`Color`] has resigns.
    Resigns(Color),
}

impl GameState {
    pub fn winner(&self) -> Option<Color> {
        match *self {
            GameState::Checkmates(color) | GameState::Resigns(color) => Some(color),
            _ => None,
        }
    }
}

/// A Standard Chess game.
///
/// TODO: Add a timer for each player.
#[derive(Clone, Eq, PartialEq, Default, Debug)]
pub struct Chess {
    pub(crate) board: Board,
    pub(crate) square_focused: Option<Square>,
    pub(crate) history: Vec<String>,
    pub(crate) state: GameState,
}

impl Chess {
    /// Create a new instance of Chess.
    pub fn new(board: Board) -> Self {
        Chess {
            board,
            square_focused: None,
            history: vec![],
            state: GameState::Ongoing,
        }
    }

    /// Get the history of the game.
    ///
    /// The [`Vec`] contains a FEN-string.
    pub fn history(&self) -> Vec<String> {
        self.history.clone()
    }

    /// Go back one step in history.
    ///
    /// If the history is empty, reset the board to it's [`default`][Board::default] value.
    ///
    /// # Examples
    ///
    /// ```
    /// use chess::{Chess, Square};
    /// let mut chess = Chess::default();
    /// let expected = Chess::default();
    /// chess.play(Square::A2, Square::A4);
    /// chess.undo();
    ///
    /// assert_eq!(chess, expected);
    /// ```
    pub fn undo(&mut self) {
        if let Some(fen) = self.history.pop() {
            self.board = Board::from_str(fen.as_str()).expect("valid fen from history");
        }
    }

    /// Reset the Game (board and history).
    pub fn reset(&mut self) {
        self.board = Board::default();
        self.square_focused = None;
        self.history = vec![];
        self.state = GameState::Ongoing;
    }

    /// Return the [`State`][GameState] of the Game.
    pub fn state(&mut self) -> GameState {
        self.state
    }

    /// Base function to call when a user click on the screen.
    pub fn play(&mut self, from: Square, to: Square) {
        let m = ChessMove::new(from, to);
        if self.board.is_legal(m) {
            self.history.push(self.board.to_string());
            self.board.update(m);
            self.state = self.board.state();
        }
        self.square_focused = None;
    }

    /// [`Color`] offer a draw.
    #[cfg(any())]
    pub fn offer_draw(&mut self, color: Color) {}

    /// [`Color`] accept the draw. Assumes that a draw is offered.
    ///
    /// > **Caution**: This crate don't implement the offer_draw() method.
    ///   You need to react yourself to this action.
    pub fn accept_draw(&mut self) {
        self.state = GameState::DrawAccepted;
    }

    /// Verify if a player can legally declare a draw by 3-fold repetition or 50-move rule.
    pub fn can_declare_draw(&self) -> bool {
        let t = self.history.len();
        if t >= 8 {
            let fen_boards = [
                self.board
                    .to_string()
                    .split_once(' ')
                    .unwrap()
                    .0
                    .to_string(),
                self.history[t - 4].split_once(' ').unwrap().0.to_string(),
                self.history[t - 8].split_once(' ').unwrap().0.to_string(),
            ];
            if fen_boards[0] == fen_boards[1] && fen_boards[1] == fen_boards[2] {
                return true;
            }
            if self.board.halfmoves() >= 100 {
                return true;
            }
        }
        false
    }

    /// Declare a draw by 3-fold repetition or 50-move rule. Assumes that a draw can be declare.
    pub fn declare_draw(&mut self) {
        self.state = GameState::DrawDeclared;
    }

    /// [`Color`] resigns the game.
    pub fn resign(&mut self, color: Color) {
        self.state = GameState::Resigns(color);
    }
}