Skip to main content

voronoi_go/
color.rs

1//! The two players, and values that come in one per player.
2
3use core::ops::{Index, IndexMut};
4
5/// A player.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
9pub enum Color {
10    /// Moves first.
11    Black,
12    /// Moves second.
13    White,
14}
15
16impl Color {
17    /// Both colours, in turn order. Iterate this rather than a map's keys when
18    /// the result is observable — order has to be reproducible.
19    pub const ALL: [Self; 2] = [Self::Black, Self::White];
20
21    /// The other player.
22    #[must_use]
23    pub const fn opposite(self) -> Self {
24        match self {
25            Self::Black => Self::White,
26            Self::White => Self::Black,
27        }
28    }
29}
30
31/// A pair of values, one per [`Color`].
32///
33/// Use this anywhere two per-colour values would otherwise become two named
34/// fields: it makes `for color in Color::ALL` work, and it makes forgetting one
35/// side impossible.
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
37pub struct PerColor<T>([T; 2]);
38
39impl<T> PerColor<T> {
40    /// Builds a pair from its black and white halves.
41    pub fn new(black: T, white: T) -> Self {
42        Self([black, white])
43    }
44
45    /// The black half.
46    pub fn black(&self) -> &T {
47        let [black, _] = &self.0;
48        black
49    }
50
51    /// The white half.
52    pub fn white(&self) -> &T {
53        let [_, white] = &self.0;
54        white
55    }
56
57    /// The half belonging to `color`.
58    pub fn get(&self, color: Color) -> &T {
59        match color {
60            Color::Black => self.black(),
61            Color::White => self.white(),
62        }
63    }
64
65    /// Mutable access to the half belonging to `color`.
66    pub fn get_mut(&mut self, color: Color) -> &mut T {
67        let [black, white] = &mut self.0;
68        match color {
69            Color::Black => black,
70            Color::White => white,
71        }
72    }
73
74    /// Consumes the pair, yielding `(black, white)`.
75    pub fn into_parts(self) -> (T, T) {
76        let [black, white] = self.0;
77        (black, white)
78    }
79}
80
81impl<T> Index<Color> for PerColor<T> {
82    type Output = T;
83
84    fn index(&self, color: Color) -> &T {
85        self.get(color)
86    }
87}
88
89impl<T> IndexMut<Color> for PerColor<T> {
90    fn index_mut(&mut self, color: Color) -> &mut T {
91        self.get_mut(color)
92    }
93}
94
95impl<T> From<[T; 2]> for PerColor<T> {
96    fn from(pair: [T; 2]) -> Self {
97        Self(pair)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    #![allow(clippy::unwrap_used, clippy::expect_used)]
104
105    use super::{Color, PerColor};
106
107    #[test]
108    fn opposite_swaps_and_is_an_involution() {
109        assert_eq!(Color::Black.opposite(), Color::White);
110        assert_eq!(Color::White.opposite(), Color::Black);
111        for color in Color::ALL {
112            assert_eq!(color.opposite().opposite(), color);
113        }
114    }
115
116    #[test]
117    fn all_is_in_turn_order() {
118        assert_eq!(Color::ALL, [Color::Black, Color::White]);
119    }
120
121    #[test]
122    fn per_color_indexes_by_color() {
123        let counts = PerColor::new(3_u32, 1);
124        assert_eq!(*counts.black(), 3);
125        assert_eq!(*counts.white(), 1);
126        assert_eq!(counts[Color::Black], 3);
127        assert_eq!(counts[Color::White], 1);
128        assert_eq!(*counts.get(Color::Black), 3);
129    }
130
131    #[test]
132    fn per_color_is_mutable_through_the_index() {
133        let mut counts = PerColor::new(0_u32, 0);
134        counts[Color::White] += 5;
135        *counts.get_mut(Color::Black) += 2;
136        assert_eq!(counts.into_parts(), (2, 5));
137    }
138
139    #[test]
140    fn per_color_round_trips_an_array() {
141        let pair = PerColor::from(["b", "w"]);
142        assert_eq!(pair.into_parts(), ("b", "w"));
143    }
144}