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}