forge_foundation/
color.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[repr(u8)]
6pub enum Color {
7 White = 1,
8 Blue = 2,
9 Black = 4,
10 Red = 8,
11 Green = 16,
12}
13
14impl Color {
15 pub const ALL: [Color; 5] = [
16 Color::White,
17 Color::Blue,
18 Color::Black,
19 Color::Red,
20 Color::Green,
21 ];
22
23 pub fn short_name(self) -> &'static str {
24 match self {
25 Color::White => "W",
26 Color::Blue => "U",
27 Color::Black => "B",
28 Color::Red => "R",
29 Color::Green => "G",
30 }
31 }
32
33 pub fn long_name(self) -> &'static str {
34 match self {
35 Color::White => "white",
36 Color::Blue => "blue",
37 Color::Black => "black",
38 Color::Red => "red",
39 Color::Green => "green",
40 }
41 }
42
43 pub fn symbol(self) -> &'static str {
44 match self {
45 Color::White => "{W}",
46 Color::Blue => "{U}",
47 Color::Black => "{B}",
48 Color::Red => "{R}",
49 Color::Green => "{G}",
50 }
51 }
52
53 pub fn basic_land_type(self) -> &'static str {
54 match self {
55 Color::White => "Plains",
56 Color::Blue => "Island",
57 Color::Black => "Swamp",
58 Color::Red => "Mountain",
59 Color::Green => "Forest",
60 }
61 }
62
63 pub fn mask(self) -> u8 {
64 self as u8
65 }
66
67 pub fn from_char(c: char) -> Option<Color> {
68 match c.to_ascii_uppercase() {
69 'W' => Some(Color::White),
70 'U' => Some(Color::Blue),
71 'B' => Some(Color::Black),
72 'R' => Some(Color::Red),
73 'G' => Some(Color::Green),
74 _ => None,
75 }
76 }
77
78 pub fn from_name(s: &str) -> Option<Color> {
79 if s.len() == 1 {
80 return Color::from_char(s.as_bytes()[0] as char);
81 }
82 match s.to_ascii_lowercase().as_str() {
83 "white" => Some(Color::White),
84 "blue" => Some(Color::Blue),
85 "black" => Some(Color::Black),
86 "red" => Some(Color::Red),
87 "green" => Some(Color::Green),
88 _ => None,
89 }
90 }
91}
92
93#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
96pub struct ColorSet(u8);
97
98impl ColorSet {
99 pub const COLORLESS: ColorSet = ColorSet(0);
100 pub const WHITE: ColorSet = ColorSet(1);
101 pub const BLUE: ColorSet = ColorSet(2);
102 pub const BLACK: ColorSet = ColorSet(4);
103 pub const RED: ColorSet = ColorSet(8);
104 pub const GREEN: ColorSet = ColorSet(16);
105 pub const ALL_COLORS: ColorSet = ColorSet(0b11111);
106
107 pub fn from_mask(mask: u8) -> Self {
108 ColorSet(mask & 0b11111)
109 }
110
111 pub fn mask(self) -> u8 {
112 self.0
113 }
114
115 pub fn is_colorless(self) -> bool {
116 self.0 == 0
117 }
118
119 pub fn is_multicolor(self) -> bool {
120 self.count_colors() > 1
121 }
122
123 pub fn is_mono_color(self) -> bool {
124 self.count_colors() == 1
125 }
126
127 pub fn count_colors(self) -> u32 {
128 self.0.count_ones()
129 }
130
131 pub fn has_color(self, color: Color) -> bool {
132 (self.0 & color.mask()) != 0
133 }
134
135 pub fn has_white(self) -> bool {
136 self.has_color(Color::White)
137 }
138 pub fn has_blue(self) -> bool {
139 self.has_color(Color::Blue)
140 }
141 pub fn has_black(self) -> bool {
142 self.has_color(Color::Black)
143 }
144 pub fn has_red(self) -> bool {
145 self.has_color(Color::Red)
146 }
147 pub fn has_green(self) -> bool {
148 self.has_color(Color::Green)
149 }
150
151 pub fn has_any_color(self, mask: u8) -> bool {
152 (self.0 & mask) != 0
153 }
154
155 pub fn has_all_colors(self, mask: u8) -> bool {
156 (self.0 & mask) == mask
157 }
158
159 pub fn contains_all_colors_from(self, other: ColorSet) -> bool {
160 (!self.0 & other.0) == 0
161 }
162
163 pub fn shares_color_with(self, other: ColorSet) -> bool {
164 (self.0 & other.0) != 0
165 }
166
167 pub fn union(self, other: ColorSet) -> ColorSet {
168 ColorSet(self.0 | other.0)
169 }
170
171 pub fn intersection(self, other: ColorSet) -> ColorSet {
172 ColorSet(self.0 & other.0)
173 }
174
175 pub fn inverse(self) -> ColorSet {
176 ColorSet(self.0 ^ 0b11111)
177 }
178
179 pub fn iter(self) -> impl Iterator<Item = Color> {
180 Color::ALL.into_iter().filter(move |c| self.has_color(*c))
181 }
182
183 pub fn from_names(s: &str) -> Self {
184 let mut mask = 0u8;
185 for c in s.chars() {
186 if let Some(color) = Color::from_char(c) {
187 mask |= color.mask();
188 }
189 }
190 if mask == 0 {
191 if let Some(color) = Color::from_name(s) {
193 mask = color.mask();
194 }
195 }
196 ColorSet(mask & 0b11111)
197 }
198}
199
200impl std::fmt::Debug for ColorSet {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 if self.is_colorless() {
203 return write!(f, "ColorSet(C)");
204 }
205 write!(f, "ColorSet(")?;
206 for c in self.iter() {
207 write!(f, "{}", c.short_name())?;
208 }
209 write!(f, ")")
210 }
211}
212
213impl std::fmt::Display for ColorSet {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 if self.is_colorless() {
216 return write!(f, "C");
217 }
218 for c in self.iter() {
219 write!(f, "{}", c.short_name())?;
220 }
221 Ok(())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn color_masks() {
231 assert_eq!(Color::White.mask(), 1);
232 assert_eq!(Color::Blue.mask(), 2);
233 assert_eq!(Color::Black.mask(), 4);
234 assert_eq!(Color::Red.mask(), 8);
235 assert_eq!(Color::Green.mask(), 16);
236 }
237
238 #[test]
239 fn color_from_char() {
240 assert_eq!(Color::from_char('W'), Some(Color::White));
241 assert_eq!(Color::from_char('u'), Some(Color::Blue));
242 assert_eq!(Color::from_char('X'), None);
243 }
244
245 #[test]
246 fn colorset_basics() {
247 let cs = ColorSet::from_mask(Color::White.mask() | Color::Blue.mask());
248 assert!(cs.has_white());
249 assert!(cs.has_blue());
250 assert!(!cs.has_black());
251 assert!(cs.is_multicolor());
252 assert_eq!(cs.count_colors(), 2);
253 }
254
255 #[test]
256 fn colorset_all_32_valid() {
257 for i in 0..32u8 {
258 let cs = ColorSet::from_mask(i);
259 assert_eq!(cs.mask(), i);
260 }
261 }
262
263 #[test]
264 fn colorset_inverse() {
265 let wu = ColorSet::from_mask(Color::White.mask() | Color::Blue.mask());
266 let brg = wu.inverse();
267 assert!(brg.has_black());
268 assert!(brg.has_red());
269 assert!(brg.has_green());
270 assert!(!brg.has_white());
271 assert!(!brg.has_blue());
272 }
273}