fast_tak/
wins.rs

1use std::{cmp::Ordering, ops::Not};
2
3use takparse::Color;
4
5use crate::{game_result::Reason, Game, GameResult};
6
7pub const MAX_REVERSIBLE_PLIES: u16 = 50;
8
9impl<const N: usize, const HALF_KOMI: i8> Game<N, HALF_KOMI> {
10    #[must_use]
11    pub fn result(&self) -> GameResult {
12        // We check the result after a move, so for the dragon clause
13        // we look at the other player's path first (they just played).
14        if self.board.has_road(self.to_move.not()) {
15            GameResult::Winner {
16                color: self.to_move.not(),
17                reason: Reason::Road,
18            }
19        } else if self.board.has_road(self.to_move) {
20            GameResult::Winner {
21                color: self.to_move,
22                reason: Reason::Road,
23            }
24        } else if self.white_reserves.depleted()
25            || self.black_reserves.depleted()
26            || self.board.full()
27        {
28            self.flat_end()
29        } else if self.reversible_plies >= MAX_REVERSIBLE_PLIES {
30            GameResult::Draw {
31                reason: Reason::ReversiblePlies,
32            }
33        } else {
34            GameResult::Ongoing
35        }
36    }
37
38    fn flat_end(&self) -> GameResult {
39        let reason = if self.white_reserves.depleted() || self.black_reserves.depleted() {
40            Reason::ReservesDepleted
41        } else {
42            Reason::BoardFill
43        };
44        let flat_diff = self.board.flat_diff();
45        match flat_diff.cmp(&(HALF_KOMI / 2)) {
46            Ordering::Greater => GameResult::Winner {
47                color: Color::White,
48                reason,
49            },
50            Ordering::Less => GameResult::Winner {
51                color: Color::Black,
52                reason,
53            },
54            Ordering::Equal => {
55                if HALF_KOMI % 2 == 0 {
56                    GameResult::Draw { reason }
57                } else {
58                    GameResult::Winner {
59                        color: if HALF_KOMI > 0 {
60                            Color::Black
61                        } else {
62                            Color::White
63                        },
64                        reason,
65                    }
66                }
67            }
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use takparse::Color;
75
76    use crate::{game_result::Reason, Game, GameResult};
77
78    #[test]
79    fn dragon_clause() {
80        let game = Game::<6, 0>::from_ptn_moves(&[
81            "a4", "a3", "b3", "b4", "c3", "c4", "d3", "d4", "d3+", "e4", "e3", "f4", "f3", "Cb5",
82            "d4-",
83        ]);
84        assert_eq!(game.result(), GameResult::Winner {
85            color: Color::White,
86            reason: Reason::Road
87        });
88    }
89
90    #[test]
91    fn flat_win() {
92        let game =
93            Game::<3, 0>::from_ptn_moves(&["a3", "c1", "c2", "c3", "b3", "b2", "b1", "a1", "a2"]);
94        assert_eq!(game.result(), GameResult::Winner {
95            color: Color::White,
96            reason: Reason::BoardFill
97        });
98    }
99
100    #[test]
101    fn road_win() {
102        let game = Game::<5, 0>::from_ptn_moves(&[
103            "d2", "a5", "b4", "d3", "Cc3", "Cc2", "b2", "b1", "b3", "a1", "c4", "c1", "e2", "e3",
104        ]);
105        assert_eq!(game.result(), GameResult::Winner {
106            color: Color::Black,
107            reason: Reason::Road,
108        });
109    }
110
111    #[test]
112    fn road_beats_flats() {
113        let game =
114            Game::<3, 0>::from_ptn_moves(&["a1", "c1", "c2", "a2", "Sa3", "b1", "Sb3", "b2", "c3"]);
115        assert_eq!(game.result(), GameResult::Winner {
116            color: Color::White,
117            reason: Reason::Road,
118        });
119    }
120
121    #[test]
122    fn reserves() {
123        let game = Game::<3, 0>::from_ptn_moves(&[
124            "a3", "b3", "a2", "b2", "b3<", "b2<", "b3", "b2", "c2", "c3", "2a3-", "b2<", "b3>",
125            "b3", "b2", "a3", "a1", "b1", "b2<", "c1", "c2<", "b1+", "b1", "2b2+", "b2", "a3-",
126            "a3", "3b3<", "b1<", "3a2-", "b1",
127        ]);
128        assert_eq!(game.result(), GameResult::Winner {
129            color: Color::White,
130            reason: Reason::ReservesDepleted
131        });
132    }
133
134    #[test]
135    fn board_fill_komi() {
136        let moves = [
137            "a1", "a2", "b1", "b2", "c2", "c1", "d1", "d2", "d3", "c3", "b3", "a3", "a4", "b4",
138            "c4", "d4",
139        ];
140        let game = Game::<4, 0>::from_ptn_moves(&moves);
141        assert_eq!(game.result(), GameResult::Draw {
142            reason: Reason::BoardFill
143        });
144        let game = Game::<4, -1>::from_ptn_moves(&moves);
145        assert_eq!(game.result(), GameResult::Winner {
146            color: Color::White,
147            reason: Reason::BoardFill
148        });
149        let game = Game::<4, 1>::from_ptn_moves(&moves);
150        assert_eq!(game.result(), GameResult::Winner {
151            color: Color::Black,
152            reason: Reason::BoardFill,
153        });
154        let game = Game::<4, 2>::from_ptn_moves(&moves);
155        assert_eq!(game.result(), GameResult::Winner {
156            color: Color::Black,
157            reason: Reason::BoardFill
158        });
159    }
160}