1use core::ops::{Index, IndexMut};
4
5#[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 Black,
12 White,
14}
15
16impl Color {
17 pub const ALL: [Self; 2] = [Self::Black, Self::White];
20
21 #[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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
37pub struct PerColor<T>([T; 2]);
38
39impl<T> PerColor<T> {
40 pub fn new(black: T, white: T) -> Self {
42 Self([black, white])
43 }
44
45 pub fn black(&self) -> &T {
47 let [black, _] = &self.0;
48 black
49 }
50
51 pub fn white(&self) -> &T {
53 let [_, white] = &self.0;
54 white
55 }
56
57 pub fn get(&self, color: Color) -> &T {
59 match color {
60 Color::Black => self.black(),
61 Color::White => self.white(),
62 }
63 }
64
65 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 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}