podch/
game.rs

1use std::fmt;
2use crate::{Board, EditedBoard, HashUsedSituations, Stone, UsedSituations};
3
4/// Type of invalid move
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6#[derive(Debug, Copy, Clone, Eq, PartialEq)]
7pub enum MoveError {
8    /// The selected square is outside the board
9    SquareOutOfBounds,
10
11    /// The selected square is occupied by another player's stone
12    SquareOccupied,
13
14    /// The resulting situation is similar to one used before
15    SituationUsedBefore,
16}
17
18impl fmt::Display for MoveError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            MoveError::SquareOutOfBounds => { f.write_str("The square is out of the board") }
22            MoveError::SquareOccupied => { f.write_str("The square is occupied by a foreign stone") }
23            MoveError::SituationUsedBefore => { f.write_str("The situation was used before") }
24        }
25    }
26}
27
28impl std::error::Error for MoveError {}
29
30/// Result of a move attempt
31pub type MoveResult = Result<(), MoveError>;
32
33/// Podch game
34pub trait Game {
35    type Board: Board;
36
37    /// Construct new game
38    ///
39    /// # Examples
40    /// ```
41    /// use podch::Game;
42    /// let game = podch::SimpleGame::<podch::VecBoard>::new(2, 3);
43    /// assert_eq!(game.size(), (2, 3));
44    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "000\n000");
45    /// assert_eq!(game.current_player(), podch::Stone::Dark);
46    /// ```
47    fn new(height: usize, width: usize) -> Self;
48
49    /// Current situation on the board
50    ///
51    /// # Examples
52    /// ```
53    /// use podch::{Game, Board};
54    /// let game = podch::SimpleGame::<podch::BinBoard>::new(2, 3);
55    /// let board = game.board();
56    /// assert_eq!(board.size(), game.size());
57    /// assert_eq!(podch::BoardDisplay::from(board).to_string(), "000\n000");
58    /// ```
59    fn board(&self) -> &Self::Board;
60
61    /// Height and width of the board
62    ///
63    /// # Examples
64    /// ```
65    /// use podch::Game;
66    /// let game = podch::SimpleGame::<podch::VecBoard>::new(2, 3);
67    /// let (height, width) = game.size();
68    /// assert_eq!(height, 2);
69    /// assert_eq!(width, 3);
70    /// ```
71    fn size(&self) -> (usize, usize) {
72        self.board().size()
73    }
74
75    /// Color of the player whose turn
76    ///
77    /// # Examples
78    /// ```
79    /// use podch::{Game, MoveResult};
80    /// let mut game = podch::SimpleGame::<podch::BinBoard>::new(2, 3);
81    /// let player = game.current_player();
82    /// assert_eq!(player, podch::Stone::Dark);
83    /// game.make_move(0, 0).unwrap();
84    /// assert_eq!(game.current_player(), podch::Stone::Light);
85    /// ```
86    fn current_player(&self) -> Stone;
87
88    type UsedSituations: UsedSituations<Self::Board>;
89
90    /// Situations already used
91    ///
92    /// # Examples
93    /// ```
94    /// use podch::{Game, UsedSituations, Board};
95    /// let mut game = podch::SimpleGame::<podch::VecBoard>::new(2, 3);
96    /// let used = game.used_situations();
97    /// let mut board = podch::VecBoard::from_vec(vec![podch::Stone::None; 6], 3);
98    /// assert!(used.is(&board));
99    /// board.set(0, 0, podch::Stone::Dark);
100    /// assert!(!used.is(&board));
101    /// game.make_move(1, 2).unwrap();
102    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "000\n001");
103    /// assert!(game.used_situations().is(&board));
104    /// ```
105    fn used_situations(&self) -> &Self::UsedSituations;
106
107    /// Check whether a move to square in `row` and `col`umn is valid
108    ///
109    /// # Examples
110    /// ```
111    /// use podch::Game;
112    /// let mut game = podch::SimpleGame::<podch::BinBoard>::new(2, 3);
113    /// assert!(game.check_move(0, 0).is_ok());
114    /// assert_eq!(game.check_move(2, 0), Err(podch::MoveError::SquareOutOfBounds));
115    /// game.make_move(0, 0).unwrap();
116    /// assert_eq!(game.check_move(0, 0), Err(podch::MoveError::SquareOccupied));
117    /// game.make_move(1, 0).unwrap();
118    /// assert_eq!(game.check_move(0, 0), Err(podch::MoveError::SituationUsedBefore));
119    /// assert_eq!(game.check_move(1, 0), Err(podch::MoveError::SquareOccupied));
120    /// ```
121    fn check_move(&self, row: usize, col: usize) -> MoveResult;
122
123    /// Make a move to square in `row` and `col`umn (set or remove a stone)
124    ///
125    /// # Examples
126    /// ```
127    /// use podch::{Game, UsedSituations};
128    /// let mut game = podch::SimpleGame::<podch::BinBoard>::new(2, 3);
129    /// assert!(game.make_move(0, 0).is_ok());
130    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "100\n000");
131    /// assert_eq!(game.current_player(), podch::Stone::Light);
132    /// assert!(game.used_situations().is(game.board()));
133    /// assert_eq!(game.make_move(0, 0), Err(podch::MoveError::SquareOccupied));
134    ///
135    /// assert!(game.make_move(1, 0).is_ok());
136    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "100\n200");
137    /// assert_eq!(game.current_player(), podch::Stone::Dark);
138    /// assert!(game.used_situations().is(game.board()));
139    /// assert_eq!(game.make_move(0, 0), Err(podch::MoveError::SituationUsedBefore));
140    /// ```
141    fn make_move(&mut self, row: usize, col: usize) -> MoveResult;
142
143    /// Check whether the game is over
144    ///
145    /// # Examples
146    /// ```
147    /// use podch::Game;
148    /// let mut game = podch::SimpleGame::<podch::BinBoard>::new(1, 2);
149    /// assert!(!game.is_over());
150    /// game.make_move(0, 0).unwrap();
151    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "10");
152    /// assert!(!game.is_over());
153    /// game.make_move(0, 1).unwrap();
154    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "12");
155    /// assert!(game.is_over());
156    /// ```
157    fn is_over(&self) -> bool {
158        (0..self.size().0).map(
159            |i| (0..self.size().1).all(
160                |j| self.check_move(i, j).is_err()
161            )
162        ).all(|x| x)
163    }
164
165    /// Get the winner of a finished game; `None` if the game is not over
166    ///
167    /// # Examples
168    /// ```
169    /// use podch::Game;
170    /// let mut game = podch::SimpleGame::<podch::BinBoard>::new(1, 3);
171    /// game.make_move(0, 0).unwrap();
172    /// game.make_move(0, 2).unwrap();
173    /// game.make_move(0, 1).unwrap();
174    /// game.make_move(0, 2).unwrap();
175    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "110");
176    /// assert!(game.winner().is_none());
177    /// let mut game2 = game.clone();
178    /// game.make_move(0, 2).unwrap();
179    /// assert_eq!(podch::BoardDisplay::from(game.board()).to_string(), "111");
180    /// assert_eq!(game.winner(), Some(podch::Stone::Dark));
181    ///
182    /// game2.make_move(0, 0).unwrap();
183    /// assert_eq!(podch::BoardDisplay::from(game2.board()).to_string(), "010");
184    /// assert!(game2.winner().is_none());
185    /// game2.make_move(0, 0).unwrap();
186    /// assert_eq!(podch::BoardDisplay::from(game2.board()).to_string(), "210");
187    /// assert_eq!(game2.winner(), Some(podch::Stone::Light));
188    /// ```
189    fn winner(&self) -> Option<Stone> {
190        if self.is_over() {
191            Some(-self.current_player())
192        } else {
193            None
194        }
195    }
196}
197
198/// Simple game using HashUsedSituations and EditedBoard
199#[derive(Debug, Clone, Eq, PartialEq)]
200pub struct SimpleGame<B: Board> {
201    board: B,
202    player: Stone,
203    used: HashUsedSituations,
204}
205
206impl<B: Board> Game for SimpleGame<B> {
207    type Board = B;
208
209    fn new(height: usize, width: usize) -> Self {
210        let board = B::new(height, width);
211        let mut used = HashUsedSituations::new();
212        used.add(&board);
213        Self {
214            board,
215            player: Stone::Dark,
216            used,
217        }
218    }
219
220    fn board(&self) -> &Self::Board {
221        &self.board
222    }
223
224    fn current_player(&self) -> Stone {
225        self.player
226    }
227
228    type UsedSituations = HashUsedSituations;
229
230    fn used_situations(&self) -> &Self::UsedSituations {
231        &self.used
232    }
233
234    fn check_move(&self, row: usize, col: usize) -> MoveResult {
235        let val;
236        if row >= self.size().0 || col >= self.size().1 {
237            Err(MoveError::SquareOutOfBounds)
238        } else if {
239            val = self.board.get(row, col);
240            val == -self.player
241        } {
242            Err(MoveError::SquareOccupied)
243        } else if {
244            let edited = EditedBoard::new(self.board(), row, col, if val == Stone::None { self.player } else { Stone::None });
245            self.used.is(&edited)
246        } {
247            Err(MoveError::SituationUsedBefore)
248        } else {
249            Ok(())
250        }
251    }
252
253    fn make_move(&mut self, row: usize, col: usize) -> MoveResult {
254        self.check_move(row, col)?;
255        self.board.set(row, col, if self.board.get(row, col) == Stone::None { self.player } else { Stone::None });
256        self.player = -self.player;
257        self.used.add(&self.board);
258        Ok(())
259    }
260}