1use std::array;
2
3use takparse::{Direction, Move, MoveKind, Square};
4
5use crate::{board::Board, game::Game};
6
7pub trait Symmetry<const N: usize>: Sized {
8 fn symmetries(&self) -> [Self; 8];
9}
10
11impl<const N: usize> Symmetry<N> for Square {
12 fn symmetries(&self) -> [Self; 8] {
13 let n = u8::try_from(N).unwrap();
14 [
15 *self,
16 self.rotate(n),
17 self.rotate(n).rotate(n),
18 self.rotate(n).rotate(n).rotate(n),
19 self.mirror(n),
20 self.mirror(n).rotate(n),
21 self.mirror(n).rotate(n).rotate(n),
22 self.mirror(n).rotate(n).rotate(n).rotate(n),
23 ]
24 }
25}
26
27impl<const N: usize> Symmetry<N> for Direction {
28 fn symmetries(&self) -> [Self; 8] {
29 [
30 *self,
31 self.rotate(),
32 self.rotate().rotate(),
33 self.rotate().rotate().rotate(),
34 self.mirror(),
35 self.mirror().rotate(),
36 self.mirror().rotate().rotate(),
37 self.mirror().rotate().rotate().rotate(),
38 ]
39 }
40}
41
42impl<const N: usize> Symmetry<N> for Move {
43 fn symmetries(&self) -> [Self; 8] {
44 let square = self.square();
45 let kind = self.kind();
46 match kind {
47 MoveKind::Place(_) => {
48 Symmetry::<N>::symmetries(&square).map(|square| Self::new(square, kind))
49 }
50 MoveKind::Spread(direction, pattern) => zip(
51 Symmetry::<N>::symmetries(&square),
52 Symmetry::<N>::symmetries(&direction),
53 )
54 .map(|(square, direction)| Self::new(square, MoveKind::Spread(direction, pattern))),
55 }
56 }
57}
58
59impl<const N: usize> Symmetry<N> for Board<N> {
60 fn symmetries(&self) -> [Self; 8] {
61 let n = u8::try_from(N).unwrap();
62 array::from_fn(|i| {
63 let mut board = Self::default();
64 for x in 0..n {
65 for y in 0..n {
66 let square = Square::new(y, x);
67 let target = Symmetry::<N>::symmetries(&square)[i];
68 *board.get_mut(target).unwrap() = *self.get(square).unwrap();
69 }
70 }
71 board
72 })
73 }
74}
75
76impl<const N: usize, const HALF_KOMI: i8> Symmetry<N> for Game<N, HALF_KOMI> {
77 fn symmetries(&self) -> [Self; 8] {
78 let mut iter = self.board.symmetries().into_iter();
79 array::from_fn(|_| {
80 let mut game = self.clone();
81 game.board = iter.next().unwrap();
82 game
83 })
84 }
85}
86
87#[inline]
88fn zip<const N: usize, A: Copy, B: Copy>(a: [A; N], b: [B; N]) -> [(A, B); N] {
89 array::from_fn(|i| (a[i], b[i]))
90}
91
92impl<const N: usize, const HALF_KOMI: i8> Game<N, HALF_KOMI> {
93 #[must_use]
94 #[allow(clippy::missing_panics_doc)]
95 pub fn canonical(mut self) -> Self {
96 self.board = self.board.symmetries().into_iter().min().unwrap();
97 self
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use crate::{reserves::Reserves, symm::Symmetry, Game, GameResult, PlayError};
104
105 fn symmetrical_boards<const N: usize>(seed: usize) -> Result<(), PlayError>
106 where
107 Reserves<N>: Default,
108 {
109 let mut game: Game<N, 0> = Game::default();
110 let mut moves = Vec::new();
111 while matches!(game.result(), GameResult::Ongoing) {
112 moves.clear();
113 game.possible_moves(&mut moves);
114 let my_move = moves[seed % moves.len()];
115
116 let mut game_syms = game.symmetries();
117 let move_syms = Symmetry::<N>::symmetries(&my_move);
118
119 game.play(my_move)?;
120 game_syms
121 .iter_mut()
122 .zip(move_syms)
123 .try_for_each(|(game, m)| game.play(m))?;
124
125 let result = game.result();
126 assert!(game_syms.iter().all(|game| game.result() == result));
127 }
128 Ok(())
129 }
130
131 macro_rules! symmetrical_boards_seeded {
132 [$($name:ident $seed:literal),*] => {
133 $(
134 #[test]
135 fn $name() {
136 symmetrical_boards::<3>($seed).unwrap();
137 symmetrical_boards::<4>($seed).unwrap();
138 symmetrical_boards::<5>($seed).unwrap();
139 symmetrical_boards::<6>($seed).unwrap();
140 symmetrical_boards::<7>($seed).unwrap();
141 symmetrical_boards::<8>($seed).unwrap();
142 }
143 )*
144 };
145 }
146
147 symmetrical_boards_seeded![
148 symmetrical_boards_5915587277 5_915_587_277,
149 symmetrical_boards_1500450271 1_500_450_271,
150 symmetrical_boards_3267000013 3_267_000_013,
151 symmetrical_boards_5754853343 5_754_853_343,
152 symmetrical_boards_4093082899 4_093_082_899,
153 symmetrical_boards_9576890767 9_576_890_767,
154 symmetrical_boards_3628273133 3_628_273_133,
155 symmetrical_boards_2860486313 2_860_486_313,
156 symmetrical_boards_5463458053 5_463_458_053,
157 symmetrical_boards_3367900313 3_367_900_313
158 ];
159}