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 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}