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}