1use crate::ai::AIConfig;
2use serde::{Deserialize, Serialize};
3use std::rc::Rc;
4
5pub mod chip;
6pub use chip::*;
7
8pub type Checker = Rc<dyn Fn(&Game) -> bool>;
9
10#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
11pub enum PlayerType {
12 Local,
13 AI(AIConfig),
14 Remote,
15}
16
17#[derive(Clone, Serialize, Deserialize)]
18pub struct Player {
19 pub player_type: PlayerType,
20 pub chip_options: Vec<ChipDescrip>,
21 pub win_conditions: Vec<Vec<chip::ChipDescrip>>,
22}
23
24impl Player {
25 pub fn just_won(&self, game: &Game) -> bool {
26 self.win_conditions.iter()
27 .any(|chip_pattern| check_linear_pattern(&chip_pattern, game))
28 }
29}
30
31impl std::fmt::Debug for Player {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 write!(
34 f,
35 "({:?}, {:?}, {:?})",
36 &self.player_type,
37 &self.chip_options,
38 &self.win_conditions.len()
39 )
40 }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
44pub enum BoardState {
45 Invalid,
46 Win(isize),
47 Draw,
48 Ongoing,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct Game {
53 turn: isize,
54 board: Board,
55 pub players: Vec<Player>,
56}
57
58impl Game {
59 pub fn new(board: Board, players: Vec<Player>) -> Self {
60 Self {
61 turn: 0,
62 players,
63 board,
64 }
65 }
66
67 pub fn get_board(&self) -> &Board {
68 &self.board
69 }
70
71 pub fn get_board_mut(&mut self) -> &mut Board {
72 &mut self.board
73 }
74
75 pub fn play_no_check(&mut self, col: isize, color: ChipDescrip) {
76 self.board.insert(Chip::new(col, color));
77 self.turn += 1;
78 }
79
80 pub fn invalid_column(&self, col: isize) -> bool {
81 let y = self.board.get_col_height(col);
82 y + 1 > self.board.height || col > self.board.width
83 }
84
85 pub fn play(&mut self, col: isize, color: ChipDescrip) -> BoardState {
86 if self.invalid_column(col) {
87 BoardState::Invalid
88 } else {
89 self.play_no_check(col, color);
90 self.compute_board_state()
91 }
92 }
93
94 pub fn get_turn(&self) -> isize {
95 self.turn
96 }
97
98 pub fn print_moves(&self) {
99 for c in self.board.chips.iter() {
100 print!("{}, ", c.get_x());
101 }
102 }
103
104 pub fn get_board_layout(&self) -> &Vec<Option<ChipDescrip>> {
105 self.board.get_layout()
106 }
107
108 pub fn undo_move(&mut self) {
109 self.turn -= 1;
110 self.board.remove_last_chip();
111 }
112
113 pub fn get_player_count(&self) -> usize {
114 self.players.len()
115 }
116
117 pub fn get_player(&self, p: usize) -> &Player {
118 &self.players[p]
119 }
120
121 pub fn current_player(&self) -> &Player {
122 &self.players[self.turn as usize % self.players.len()]
123 }
124
125 pub fn next_player(&self) -> &Player {
126 &self.players[(self.turn as usize + 1) % self.players.len()]
127 }
128
129 pub fn compute_board_state(&self) -> BoardState {
130 let game = &self;
131 let mut wins = 0;
132 let mut draws = false;
133 for (player_num, player) in self.players.iter().enumerate() {
134 if player.just_won(&game) {
135 if wins == 0 {
136 wins = player_num as isize + 1;
137 } else {
138 draws = true;
139 }
140 }
141 }
142 if wins == 0 || draws {
143 if draws {
144 BoardState::Draw
145 } else if self.turn == self.board.width * self.board.height {
146 BoardState::Draw
147 } else {
148 BoardState::Ongoing
149 }
150 } else {
151 BoardState::Win(wins)
152 }
153 }
154}
155
156#[derive(Debug, Serialize, Deserialize)]
157pub struct Board {
158 pub width: isize,
159 pub height: isize,
160 pub chips: Vec<Chip>,
161 layout: Vec<Option<ChipDescrip>>,
162}
163
164impl Clone for Board {
165 fn clone(&self) -> Self {
166 let mut x = Self {
167 width: self.width,
168 height: self.height,
169 layout: vec![None; (self.width * self.height) as usize],
170 chips: Vec::new(),
171 };
172 for chip in &self.chips {
173 x.insert(Chip::new(chip.get_x(), chip.get_descrip()));
174 }
175 x
176 }
177}
178
179impl Board {
180 pub fn new(width: isize, height: isize) -> Self {
181 Self {
182 width,
183 height,
184 chips: Vec::new(),
185 layout: vec![None; (height * width) as usize],
186 }
187 }
188
189 fn insert(&mut self, chip: Chip) {
190 let y = self.get_col_height(chip.get_x());
191 self.layout[(chip.get_x() + y * self.width) as usize] = Some(chip.get_descrip());
192 self.chips.push(chip);
193 }
194
195 pub fn get_col_height(&self, x: isize) -> isize {
196 for y in 0..self.height {
197 if self.layout[(x + y * self.width) as usize].is_none() {
198 return y;
199 }
200 }
201 self.height
202 }
203
204 pub fn get_valid_moves(&self) -> Vec<isize> {
205 (0..self.width)
206 .filter(|x| self.get_col_height(*x) < self.height)
207 .collect()
208 }
209
210 pub fn last_move_loc(&self) -> (isize, isize) {
211 let x = self.chips[self.chips.len() - 1].get_x();
212 (x, self.get_col_height(x) - 1)
213 }
214
215 pub fn get_layout(&self) -> &Vec<Option<ChipDescrip>> {
216 &self.layout
217 }
218
219 pub fn remove_last_chip(&mut self) {
220 let chip = self.chips.pop();
221 let x = chip.expect("Should never undo no moves").get_x();
222
223 let y = self.get_col_height(x) - 1;
224 self.layout[(x + y * self.width) as usize] = None;
225 }
226 pub fn height(&self) -> usize {
227 self.height as usize
228 }
229 pub fn width(&self) -> usize {
230 self.width as usize
231 }
232}
233
234pub fn check_linear_pattern(pattern: &[ChipDescrip], game: &Game) -> bool {
235 let lay = game.get_board_layout();
236 let width = game.board.width;
237 let height = game.board.height;
238 let len = pattern.len() as isize;
239 assert!(len <= width);
240 assert!(len <= height);
241 if game.turn == 0 {
242 return false
243 }
244
245 fn check_dir(
246 x: isize,
247 y: isize,
248 dx: isize,
249 dy: isize,
250 len: isize,
251 width: isize,
252 height: isize,
253 pattern: &[ChipDescrip],
254 lay: &[Option<ChipDescrip>],
255 ) -> bool {
256 let idx = |x, y| (x + y * width) as usize;
258 let mut x = x;
259 let mut y = y;
260 let mut matched = 0;
261
262 while x >= 0 && x < width && y >= 0 && y < height {
263 match lay[idx(x, y)] {
264 _ if matched == len => return true,
265 Some(chip) if chip == pattern[matched as usize] => {
266 matched += 1;
267 }
268 _ => {
269 x -= dx * (matched as isize);
270 y -= dy * (matched as isize);
271 matched = 0;
272 }
273 }
274
275 x += dx;
276 y += dy;
277 }
278 matched == len
279 };
280
281 let check_line = |x, y, dx, dy| check_dir(x, y, dx, dy, len, width, height, pattern, &lay);
282
283 let mut res = false;
284
285 let (x, y) = game.get_board().last_move_loc();
286
287 let m = std::cmp::min(x, y);
288 let h = game.get_board().height - 1;
289 let _w = game.get_board().width - 1;
290 let m2 = std::cmp::min(x, h - y);
291
292 res |= check_line(x, 0, 0, 1);
293 res |= check_line(0, y, 1, 0); res |= check_line(x - m2, y + m2, 1, -1); res |= check_line(x - m, y - m, 1, 1); res
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
304 use crate::games::*;
305 fn make_game(locs: &[isize]) -> Game {
309 let mut game = connect4();
310 for x in locs {
311 let col = game.current_player().chip_options[0];
312 game.play(*x, col);
313 }
314 game
315 }
316
317 fn make_game_toto(locs: &[(isize, ChipDescrip)]) -> Game {
318 let mut game = toto();
319 for (x, col) in locs {
320 game.play(*x, *col);
321 }
322 game
323 }
324
325 #[test]
326 fn test_hor_check() {
327 let pat = vec![RED_CHIP, RED_CHIP];
328 assert!(check_linear_pattern(
329 &pat,
330 &make_game(&[0, 1, 2, 3, 0, 1, 2, 3, 0, 2, 1, 3])
331 ));
332 assert!(check_linear_pattern(&pat, &make_game(&[0, 6, 0])));
333
334 let pat = vec![RED_CHIP, RED_CHIP, RED_CHIP];
335 assert!(!check_linear_pattern(&pat, &make_game(&[0, 2, 1])));
336
337 let pat = vec![O_CHIP, T_CHIP, T_CHIP, O_CHIP];
338 let game = &make_game_toto(&[
339 (0, T_CHIP),
340 (1, O_CHIP),
341 (2, T_CHIP),
342 (3, T_CHIP),
343 (4, T_CHIP),
344 (0, O_CHIP),
345 (2, T_CHIP),
346 (3, O_CHIP),
347 (1, T_CHIP),
348 ]);
349 crate::io::draw_term_board(game.get_board());
350 assert!(check_linear_pattern(&pat, game));
351 }
352
353 #[test]
354 fn test_ver_check() {
355 let pat = vec![RED_CHIP, RED_CHIP, RED_CHIP];
356 assert!(check_linear_pattern(&pat, &make_game(&[0, 1, 0, 1, 0])));
357 assert!(check_linear_pattern(
358 &pat,
359 &make_game(&[0, 6, 1, 6, 6, 1, 6, 1, 6])
360 ));
361 assert!(!check_linear_pattern(&pat, &make_game(&[0, 2, 1])));
362 }
363
364 #[test]
365 fn test_dia_check() {
366 let pat = vec![RED_CHIP, RED_CHIP, RED_CHIP];
367
368 assert!(check_linear_pattern(
369 &pat,
370 &make_game(&[0, 1, 1, 2, 3, 2, 2])
371 ));
372 assert!(check_linear_pattern(
373 &pat,
374 &make_game(&[0, 0, 0, 1, 1, 3, 2])
375 ));
376
377 let pat = vec![RED_CHIP, RED_CHIP, RED_CHIP, RED_CHIP];
378 assert!(check_linear_pattern(
379 &pat,
380 &make_game(&[0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1])
381 ));
382 }
383
384 #[test]
385 fn test_dia_check2() {
386 let pat = vec![RED_CHIP, RED_CHIP, RED_CHIP, RED_CHIP];
387 assert!(!check_linear_pattern(
388 &pat,
389 &make_game(&[0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4])
390 ));
391
392 let pat = vec![YELLOW_CHIP, YELLOW_CHIP, YELLOW_CHIP, YELLOW_CHIP];
393 assert!(check_linear_pattern(
394 &pat,
395 &make_game(&[
396 3, 0, 3, 1, 3, 3, 3, 1, 3, 2, 4, 6, 4, 4, 4, 1, 1, 0, 4, 0, 0, 6, 4, 6, 6, 6, 5, 5
397 ])
398 ));
399
400 let pat = vec![YELLOW_CHIP, YELLOW_CHIP, YELLOW_CHIP, YELLOW_CHIP];
401 assert!(!check_linear_pattern(
402 &pat,
403 &make_game(&[3, 2, 3, 3, 3, 2, 3, 1, 3, 4, 2, 4, 2, 5, 2, 2, 1, 0, 1, 0, 1, 0, 1])
404 ));
405 }
406
407 #[test]
408 fn test_check_small() {
409 let pat = vec![RED_CHIP, RED_CHIP];
410 assert!(check_linear_pattern(&pat, &make_game(&[0, 1, 0])));
411
412 let game = make_game(&[2]);
413 }
415}