board_game/
symmetry.rs

1use std::fmt::{Debug, Display};
2use std::ops::Sub;
3
4use num_traits::One;
5use rand::distributions::Distribution;
6use rand::seq::SliceRandom;
7use rand::Rng;
8
9use crate::util::coord::Coord;
10
11/// The symmetry group associated with a Board. An instance of this group maps a board and moves such that everything
12/// about the board and its state is invariant under this mapping.
13/// The [Default] value is the identity element.
14pub trait Symmetry: 'static + Default + Debug + Copy + Clone + Eq + PartialEq + Send + Sync {
15    fn all() -> &'static [Self];
16    fn inverse(self) -> Self;
17    fn is_unit() -> bool {
18        Self::all().len() == 1
19    }
20}
21
22#[derive(Debug)]
23pub struct SymmetryDistribution;
24
25impl<S: Symmetry + Sized> Distribution<S> for SymmetryDistribution {
26    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> S {
27        *S::all().choose(rng).expect("A symmetry group cannot be empty")
28    }
29}
30
31/// The trivial symmetry group with only the identity, can be used as a conservative implementation.
32#[derive(Debug, Copy, Clone, Eq, PartialEq)]
33pub struct UnitSymmetry;
34
35impl Symmetry for UnitSymmetry {
36    fn all() -> &'static [Self] {
37        &[Self]
38    }
39    fn inverse(self) -> Self {
40        Self
41    }
42}
43
44impl Default for UnitSymmetry {
45    fn default() -> Self {
46        UnitSymmetry
47    }
48}
49
50/// The D1 symmetry group, representing a single axis mirror, resulting in 2 elements.
51///
52/// The `Default::default()` value means no transformation.
53///
54/// The representation is such that first x and y are optionally transposed,
55/// then each axis is optionally flipped separately.
56#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
57pub struct D1Symmetry {
58    pub mirror: bool,
59}
60
61impl D1Symmetry {
62    pub const fn new(mirror: bool) -> Self {
63        D1Symmetry { mirror }
64    }
65
66    pub fn map_axis<V: Copy + Sub<Output = V> + One>(self, x: V, size: V) -> V {
67        let max = size - V::one();
68        if self.mirror {
69            max - x
70        } else {
71            x
72        }
73    }
74}
75
76impl Symmetry for D1Symmetry {
77    fn all() -> &'static [Self] {
78        const ALL: [D1Symmetry; 2] = [D1Symmetry::new(false), D1Symmetry::new(true)];
79        &ALL
80    }
81
82    fn inverse(self) -> Self {
83        self
84    }
85}
86
87/// The D4 symmetry group that can represent any combination of
88/// flips, rotating and transposing, which result in 8 distinct elements.
89///
90/// The representation is such that first x and y are optionally transposed,
91/// then each axis is optionally flipped separately.
92#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
93pub struct D4Symmetry {
94    pub transpose: bool,
95    pub flip_x: bool,
96    pub flip_y: bool,
97}
98
99impl D4Symmetry {
100    pub const fn new(transpose: bool, flip_x: bool, flip_y: bool) -> Self {
101        D4Symmetry {
102            transpose,
103            flip_x,
104            flip_y,
105        }
106    }
107
108    pub fn map_xy<V: Copy + Sub<Output = V> + One + Display>(self, mut x: V, mut y: V, size: V) -> (V, V) {
109        let max = size - V::one();
110
111        if self.transpose {
112            std::mem::swap(&mut x, &mut y)
113        };
114        if self.flip_x {
115            x = max - x
116        };
117        if self.flip_y {
118            y = max - y
119        };
120
121        (x, y)
122    }
123
124    pub fn map_coord<const X: u8, const Y: u8>(self, coord: Coord<X, Y>, size: u8) -> Coord<X, Y> {
125        assert!(size <= X && size <= Y);
126        let (x, y) = self.map_xy(coord.x(), coord.y(), size);
127        Coord::from_xy(x, y)
128    }
129}
130
131impl Symmetry for D4Symmetry {
132    fn all() -> &'static [Self] {
133        const ALL: [D4Symmetry; 8] = [
134            D4Symmetry::new(false, false, false),
135            D4Symmetry::new(false, false, true),
136            D4Symmetry::new(false, true, false),
137            D4Symmetry::new(false, true, true),
138            D4Symmetry::new(true, false, false),
139            D4Symmetry::new(true, false, true),
140            D4Symmetry::new(true, true, false),
141            D4Symmetry::new(true, true, true),
142        ];
143        &ALL
144    }
145
146    fn inverse(self) -> Self {
147        D4Symmetry::new(
148            self.transpose,
149            if self.transpose { self.flip_y } else { self.flip_x },
150            if self.transpose { self.flip_x } else { self.flip_y },
151        )
152    }
153}