1use std::ops::Not;
2
3use takparse::{Color, Direction, Move, MoveKind, Pattern, Piece, Square};
4
5use crate::{board::Board, error::PlayError, reserves::Reserves, stack::Stack};
6
7#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
8pub struct Game<const N: usize, const HALF_KOMI: i8> {
9 pub board: Board<N>,
10 pub to_move: Color,
11 pub white_reserves: Reserves<N>,
12 pub black_reserves: Reserves<N>,
13 pub ply: u16,
14 pub reversible_plies: u16,
15}
16
17impl<const N: usize, const HALF_KOMI: i8> Clone for Game<N, HALF_KOMI> {
19 #[inline]
20 fn clone(&self) -> Self {
21 unsafe { std::mem::transmute_copy(self) }
22 }
23}
24
25impl<const N: usize, const HALF_KOMI: i8> Default for Game<N, HALF_KOMI>
26where
27 Reserves<N>: Default,
28{
29 fn default() -> Self {
30 Self {
31 board: Board::default(),
32 to_move: Color::White,
33 white_reserves: Reserves::default(),
34 black_reserves: Reserves::default(),
35 ply: u16::default(),
36 reversible_plies: u16::default(),
37 }
38 }
39}
40
41impl<const N: usize, const HALF_KOMI: i8> Game<N, HALF_KOMI> {
42 pub(crate) const fn is_swapped(&self) -> bool {
43 self.ply < 2
44 }
45
46 pub(crate) fn color_to_place(&self) -> Color {
47 if self.is_swapped() {
48 self.to_move.not()
49 } else {
50 self.to_move
51 }
52 }
53
54 pub(crate) fn get_reserves(&self) -> Reserves<N> {
55 match self.color_to_place() {
56 Color::White => self.white_reserves,
57 Color::Black => self.black_reserves,
58 }
59 }
60
61 fn dec_stones(&mut self) {
62 if (self.to_move == Color::White) ^ self.is_swapped() {
63 self.white_reserves.stones -= 1;
64 } else {
65 self.black_reserves.stones -= 1;
66 }
67 }
68
69 fn dec_caps(&mut self) {
70 match self.to_move {
71 Color::White => self.white_reserves.caps -= 1,
72 Color::Black => self.black_reserves.caps -= 1,
73 }
74 }
75
76 pub fn play(&mut self, my_move: Move) -> Result<(), PlayError> {
83 match my_move.kind() {
84 MoveKind::Place(piece) => self.execute_place(my_move.square(), piece),
85 MoveKind::Spread(direction, pattern) => {
86 self.execute_spread(my_move.square(), direction, pattern)
87 }
88 }?;
89 self.update_reversible(my_move);
90 self.ply += 1;
91 self.to_move = self.to_move.not();
92 Ok(())
93 }
94
95 fn execute_place(&mut self, square: Square, piece: Piece) -> Result<(), PlayError> {
96 let Reserves { stones, caps } = self.get_reserves();
97 let is_swapped = self.is_swapped();
98 let color_to_place = self.color_to_place();
99 let stack = self.board.get_mut(square).ok_or(PlayError::OutOfBounds)?;
100 if !stack.is_empty() {
101 Err(PlayError::AlreadyOccupied)
102 } else if matches!(piece, Piece::Cap) && (caps == 0) {
103 Err(PlayError::NoCapstone)
104 } else if matches!(piece, Piece::Flat | Piece::Wall) && (stones == 0) {
105 Err(PlayError::NoStones)
106 } else if is_swapped && matches!(piece, Piece::Wall | Piece::Cap) {
107 Err(PlayError::OpeningNonFlat)
108 } else {
109 *stack = Stack::new(piece, color_to_place);
110 if matches!(piece, Piece::Flat | Piece::Wall) {
111 self.dec_stones();
112 } else {
113 self.dec_caps();
114 }
115 Ok(())
116 }
117 }
118
119 fn execute_spread(
120 &mut self,
121 square: Square,
122 direction: Direction,
123 pattern: Pattern,
124 ) -> Result<(), PlayError> {
125 let n = u8::try_from(N).unwrap();
126
127 let stack = self.board.get_mut(square).ok_or(PlayError::OutOfBounds)?;
128 let (_, top_color) = stack.top().ok_or(PlayError::EmptySquare)?;
129 if top_color != self.to_move {
130 return Err(PlayError::StackNotOwned);
131 }
132 let mut amount = pattern.count_pieces();
133 let (piece, colors) = stack.take::<N>(amount)?;
134 let mut carry = colors.into_iter();
135
136 let mut pos = square;
138 for drop_count in pattern {
139 pos = pos
140 .checked_step(direction, n)
141 .ok_or(PlayError::SpreadOutOfBounds)?;
142
143 for color in carry.by_ref().take(drop_count as usize) {
144 amount -= 1;
145 let Some(stack) = self.board.get_mut(pos) else {
147 continue;
148 };
149 stack.stack(if amount > 0 { Piece::Flat } else { piece }, color)?;
150 }
151 }
152
153 assert_eq!(amount, 0);
154 assert_eq!(carry.next(), None);
155 Ok(())
156 }
157
158 fn update_reversible(&mut self, my_move: Move) {
159 if matches!(my_move.kind(), MoveKind::Place(_)) {
161 self.reversible_plies = 0;
162 } else {
163 self.reversible_plies += 1;
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use crate::{Game, PlayError, StackError, TakeError};
171
172 #[test]
173 fn square_out_of_bounds() {
174 let mut game = Game::<5, 0>::default();
175 let my_move = "a8".parse().unwrap();
176 assert_eq!(game.play(my_move), Err(PlayError::OutOfBounds));
177 }
178
179 #[test]
180 fn already_occupied() {
181 let mut game = Game::<5, 0>::from_ptn_moves(&["a2"]);
182 let my_move = "a2".parse().unwrap();
183 assert_eq!(game.play(my_move), Err(PlayError::AlreadyOccupied));
184 }
185
186 #[test]
187 fn no_capstone() {
188 let mut game = Game::<6, 0>::from_ptn_moves(&["a1", "b1", "Ca2", "b2"]);
189 let my_move = "Ca3".parse().unwrap();
190 assert_eq!(game.play(my_move), Err(PlayError::NoCapstone));
191 }
192
193 #[test]
194 fn no_stones() {
195 let mut game = Game::<3, 0>::from_ptn_moves(&[
196 "a1", "b1", "a2", "b2", "a2-", "a2", "b1<", "b1", "3a1>", "a1", "b1<", "c1", "3b1>",
197 "b1", "2a1>", "a1", "3b1<", "b1", "c2", "c3",
198 ]);
199 assert_eq!(game.play("c2<".parse().unwrap()), Ok(()));
201 let my_move = "c2".parse().unwrap();
202 assert_eq!(game.play(my_move), Err(PlayError::NoStones));
203 }
204
205 #[test]
206 fn opening_wall() {
207 let mut game = Game::<7, 0>::default();
208 let my_move = "Sa1".parse().unwrap();
209 assert_eq!(game.play(my_move), Err(PlayError::OpeningNonFlat));
210 }
211
212 #[test]
213 fn opening_capstone() {
214 let mut game = Game::<8, 0>::default();
215 let my_move = "Ca1".parse().unwrap();
216 assert_eq!(game.play(my_move), Err(PlayError::OpeningNonFlat));
217 }
218
219 #[test]
220 fn empty_square() {
221 let mut game = Game::<4, 0>::from_ptn_moves(&["a1", "b1", "a2"]);
222 let my_move = "b2<".parse().unwrap();
223 assert_eq!(game.play(my_move), Err(PlayError::EmptySquare));
224 }
225
226 #[test]
227 fn stack_not_owned() {
228 let mut game = Game::<5, 0>::from_ptn_moves(&[
229 "a1", "e5", "c3", "b2", "c2", "c1", "d1", "Cd2", "b1", "c1+", "Cc1", "e2", "c1+",
230 ]);
231 let my_move = "3c2<12".parse().unwrap();
232 assert_eq!(game.play(my_move), Err(PlayError::StackNotOwned));
233 }
234
235 #[test]
236 fn spread_out_of_bounds() {
237 let mut game = Game::<5, 0>::from_ptn_moves(&[
238 "a1", "e5", "c3", "b2", "c2", "c1", "d1", "Cd2", "b1", "c1+", "Cc1", "e2", "c1+", "a2",
239 ]);
240 let my_move = "3c2-111".parse().unwrap();
241 assert_eq!(game.play(my_move), Err(PlayError::SpreadOutOfBounds));
242 }
243
244 #[test]
245 fn stack_on_wall() {
246 let mut game = Game::<4, 0>::from_ptn_moves(&["a1", "a2", "Sb1"]);
247 let my_move = "a1>".parse().unwrap();
248 assert_eq!(
249 game.play(my_move),
250 Err(PlayError::StackError(StackError::Wall))
251 );
252 }
253
254 #[test]
255 fn stack_on_cap() {
256 let mut game = Game::<7, 0>::from_ptn_moves(&["a1", "a2", "Cb1"]);
257 let my_move = "a1>".parse().unwrap();
258 assert_eq!(
259 game.play(my_move),
260 Err(PlayError::StackError(StackError::Cap))
261 );
262 }
263
264 #[test]
265 fn carry_limit() {
266 let mut game =
267 Game::<3, 0>::from_ptn_moves(&["a1", "b1", "b1<", "b1", "2a1>", "a1", "3b1<", "b1"]);
268 let my_move = "4a1>".parse().unwrap();
269 assert_eq!(
270 game.play(my_move),
271 Err(PlayError::TakeError(TakeError::CarryLimit))
272 );
273 }
274
275 #[test]
276 fn take_more_than_stack() {
277 let mut game = Game::<3, 0>::from_ptn_moves(&["a1", "b1", "b1<", "b1"]);
278 let my_move = "3a1>".parse().unwrap();
279 assert_eq!(
280 game.play(my_move),
281 Err(PlayError::TakeError(TakeError::StackSize))
282 );
283 }
284}