1use takparse::{Direction, Move, MoveKind, Piece, Square};
2
3use crate::{game::Game, reserves::Reserves, stack::Stack, GameResult};
4
5impl<const N: usize, const HALF_KOMI: i8> Game<N, HALF_KOMI> {
6 pub fn possible_moves(&self, moves: &mut Vec<Move>) {
12 debug_assert!(moves.is_empty());
13 let n = u8::try_from(N).unwrap();
14 if self.is_swapped() {
16 self.add_opening_moves(moves);
17 return;
18 }
19
20 for x in 0..n {
22 for y in 0..n {
23 let square = Square::new(x, y);
24 match self.board.get(square).and_then(Stack::top) {
25 Some((piece, color)) => {
26 if color == self.to_move {
27 self.add_spreads(square, piece, moves);
28 }
29 }
30 None => self.add_places(square, moves),
31 }
32 }
33 }
34 }
35
36 fn add_opening_moves(&self, moves: &mut Vec<Move>) {
37 let n = u8::try_from(N).unwrap();
38 for x in 0..n {
39 for y in 0..n {
40 let square = Square::new(x, y);
41 let Some(stack) = self.board.get(square) else {
42 continue;
43 };
44 if stack.is_empty() {
45 moves.push(Move::new(square, MoveKind::Place(Piece::Flat)));
46 }
47 }
48 }
49 }
50
51 fn add_places(&self, square: Square, moves: &mut Vec<Move>) {
52 let Reserves { stones, caps } = self.get_reserves();
53 if stones > 0 {
54 moves.push(Move::new(square, MoveKind::Place(Piece::Flat)));
55 moves.push(Move::new(square, MoveKind::Place(Piece::Wall)));
56 }
57 if caps > 0 {
58 moves.push(Move::new(square, MoveKind::Place(Piece::Cap)));
59 }
60 }
61
62 fn add_spreads(&self, square: Square, piece: Piece, moves: &mut Vec<Move>) {
63 struct Spread<const N: usize> {
65 square: Square,
66 hand: u8,
67 drops: [u8; N],
68 drop_counts: usize,
69 }
70
71 let n = u8::try_from(N).unwrap();
72
73 let Some(stack) = self.board.get(square) else {
74 return;
75 };
76 #[allow(clippy::cast_possible_truncation)]
77 let max_carry: u8 = stack.size().min(N as u32) as u8;
78
79 let mut spreads = Vec::new();
80 for pickup in 1..=max_carry {
81 for direction in [
82 Direction::Up,
83 Direction::Down,
84 Direction::Left,
85 Direction::Right,
86 ] {
87 assert!(spreads.is_empty());
88 spreads.push(Spread {
90 square,
91 hand: pickup,
92 drops: [0; N],
93 drop_counts: 0,
94 });
95 while let Some(mut spread) = spreads.pop() {
96 if let Some(next) = spread.square.checked_step(direction, n) {
97 if !match self.board.get(next).and_then(Stack::top) {
99 None => true,
100 Some((Piece::Flat, _color)) => true,
101 Some((Piece::Cap, _color)) => false,
102 Some((Piece::Wall, _color)) => spread.hand == 1 && piece == Piece::Cap,
103 } {
104 continue;
105 }
106
107 for drop in 1..spread.hand {
108 let mut drops = spread.drops;
109 drops[spread.drop_counts] = drop;
110 spreads.push(Spread {
111 square: next,
112 hand: spread.hand - drop,
113 drops,
114 drop_counts: spread.drop_counts + 1,
115 });
116 }
117
118 spread.drops[spread.drop_counts] = spread.hand;
120 moves.push(Move::new(
121 square,
122 MoveKind::Spread(
123 direction,
124 spread
125 .drops
126 .into_iter()
127 .take(spread.drop_counts + 1)
128 .map(u32::from)
129 .collect(),
130 ),
131 ));
132 }
133 }
134 }
135 }
136 }
137}
138
139#[must_use]
140pub fn perf_count<const N: usize, const HALF_KOMI: i8>(
141 game: &Game<N, HALF_KOMI>,
142 depth: usize,
143) -> usize {
144 if depth == 0 || game.result() != GameResult::Ongoing {
145 1
146 } else if depth == 1 {
147 let mut moves = Vec::new();
148 game.possible_moves(&mut moves);
149 moves.len()
150 } else {
151 let mut moves = Vec::new();
152 game.possible_moves(&mut moves);
153 moves
154 .into_iter()
155 .map(|m| {
156 let mut clone = game.clone();
157 if clone.play(m).is_err() {
158 return 0;
159 };
160 perf_count(&clone, depth - 1)
161 })
162 .sum()
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use crate::{move_gen::perf_count, Game};
169
170 #[test]
171 fn move_stack_perft() {
172 let game = Game::<5, 0>::from_ptn_moves(&["d3", "c3", "c4", "1d3<", "1c4-", "Sc4"]);
173 assert_eq!(perf_count(&game, 1), 87);
174 assert_eq!(perf_count(&game, 2), 6_155);
175 assert_eq!(perf_count(&game, 3), 461_800);
176 }
177
178 #[test]
179 fn respect_carry_limit_perft() {
180 let game = Game::<5, 0>::from_ptn_moves(&[
181 "c2", "c3", "d3", "b3", "c4", "1c2+", "1d3<", "1b3>", "1c4-", "Cc2", "a1", "1c2+", "a2",
182 ]);
183 assert_eq!(perf_count(&game, 1), 104);
184 assert_eq!(perf_count(&game, 2), 7_743);
185 assert_eq!(perf_count(&game, 3), 592_645);
186 }
187
188 #[test]
189 fn suicide_perft() {
190 let game = Game::<5, 0>::from_ptn_moves(&[
191 "c4", "c2", "d2", "c3", "b2", "d3", "1d2+", "b3", "d2", "b4", "1c2+", "1b3>", "2d3<",
192 "1c4-", "d4", "5c3<23", "c2", "c4", "1d4<", "d3", "1d2+", "1c3+", "Cc3", "2c4>",
193 "1c3<", "d2", "c3", "1d2+", "1c3+", "1b4>", "2b3>11", "3c4-12", "d2", "c4", "b4", "c5",
194 "1b3>", "1c4<", "3c3-", "e5", "e2",
195 ]);
196 assert_eq!(perf_count(&game, 1), 85);
197 assert_eq!(perf_count(&game, 2), 11_206);
198 assert_eq!(perf_count(&game, 3), 957_000);
199 }
200
201 #[test]
202 fn endgame_perft() {
203 let game = Game::<5, 0>::from_ptn_moves(&[
204 "a5", "b4", "c3", "d2", "e1", "d1", "c2", "d3", "c1", "d4", "d5", "c4", "c5", "b3",
205 "b2", "a2", "Sb1", "a3", "Ce4", "Cb5", "a4", "a1", "e5", "e3", "c3<", "Sc3", "c1>",
206 "c1", "2d1+", "c3-", "c3", "a3>", "a3", "d1", "e4<", "2c2>", "c2", "e2", "b2+", "b2",
207 ]);
208 assert_eq!(perf_count(&game, 1), 65);
209 assert_eq!(perf_count(&game, 2), 4_072);
210 assert_eq!(perf_count(&game, 3), 272_031);
211 assert_eq!(perf_count(&game, 4), 16_642_760);
212 }
213
214 #[test]
215 fn reserves_perft() {
216 let game = Game::<5, 0>::from_ptn_moves(&[
217 "a1", "b1", "c1", "d1", "e1", "e2", "d2", "c2", "b2", "a2", "a3", "b3", "c3", "d3",
218 "e3", "a4", "b4", "c4", "d4", "e4", "a5", "a4-", "b4-", "c4-", "d4-", "e4-", "a4",
219 "b4", "c4", "d4", "e4", "2a3>", "c4>", "2e3<", "a3", "4b3-", "b3", "c4", "e3", "d5",
220 "d2<", "d2", "2d4-", "d4", "c5", "b5", "2c2>", "d1+", "c2", "e2+", "d1", "e2", "c5<",
221 "c5", "e4<", "Se4", "2b5-", "e4-", "a3-",
222 ]);
223 assert_eq!(perf_count(&game, 1), 152);
224 assert_eq!(perf_count(&game, 2), 15_356);
225 assert_eq!(perf_count(&game, 3), 1_961_479);
226 }
228
229 #[test]
230 fn perft_5() {
231 type G = Game<5, 0>;
232 assert_eq!(perf_count(&G::default(), 0), 1);
233 assert_eq!(perf_count(&G::default(), 1), 25);
234 assert_eq!(perf_count(&G::default(), 2), 600);
235 assert_eq!(perf_count(&G::default(), 3), 43_320);
236 assert_eq!(perf_count(&G::default(), 4), 2_999_784);
237 }
238
239 #[test]
240 fn perft_6() {
241 type G = Game<6, 0>;
242 assert_eq!(perf_count(&G::default(), 0), 1);
243 assert_eq!(perf_count(&G::default(), 1), 36);
244 assert_eq!(perf_count(&G::default(), 2), 1_260);
245 assert_eq!(perf_count(&G::default(), 3), 132_720);
246 assert_eq!(perf_count(&G::default(), 4), 13_586_048);
247 }
249}