1use std::collections::HashSet;
29use std::iter::repeat;
30use std::str::FromStr;
31
32#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
34pub enum Camel {
35 Red,
37 Orange,
39 Yellow,
41 Green,
43 White,
45}
46
47#[derive(PartialEq, Eq, Copy, Clone, Debug)]
49pub enum Marker {
50 Camel(Camel),
52 Divider,
54 Oasis,
56 FataMorgana,
58}
59
60impl Marker {
61 fn is_a_camel(self) -> bool {
62 match self {
63 Marker::Camel(_) => true,
64 _ => false,
65 }
66 }
67
68 fn is_a_divider(self) -> bool {
69 match self {
70 Marker::Divider => true,
71 _ => false,
72 }
73 }
74
75 fn is_an_oasis(self) -> bool {
76 match self {
77 Marker::Oasis => true,
78 _ => false,
79 }
80 }
81
82 fn is_a_fata_morgana(self) -> bool {
83 match self {
84 Marker::FataMorgana => true,
85 _ => false,
86 }
87 }
88
89 fn is_an_adjustment(self) -> bool {
90 self.is_an_oasis() || self.is_a_fata_morgana()
91 }
92
93 fn to_camel(self) -> Option<Camel> {
94 match self {
95 Marker::Camel(camel) => Some(camel),
96 _ => None,
97 }
98 }
99}
100
101impl FromStr for Marker {
102 type Err = NotAMarker;
103
104 fn from_str(input: &str) -> Result<Self, Self::Err> {
105 match input {
106 "r" => Ok(Marker::Camel(Camel::Red)),
107 "o" => Ok(Marker::Camel(Camel::Orange)),
108 "y" => Ok(Marker::Camel(Camel::Yellow)),
109 "g" => Ok(Marker::Camel(Camel::Green)),
110 "w" => Ok(Marker::Camel(Camel::White)),
111 "," => Ok(Marker::Divider),
112 "+" => Ok(Marker::Oasis),
113 "-" => Ok(Marker::FataMorgana),
114 _ => Err(NotAMarker::But(input.to_owned())),
115 }
116 }
117}
118
119#[derive(PartialEq, Debug)]
121pub enum NotAMarker {
122 But(String),
124}
125
126#[derive(PartialEq, Eq, Debug)]
138pub struct Race {
139 positions: Vec<Marker>,
140}
141
142impl Clone for Race {
143 fn clone(&self) -> Self {
144 Self {
145 positions: self.positions.to_vec(),
146 }
147 }
148}
149
150impl From<Vec<Marker>> for Race {
151 fn from(positions: Vec<Marker>) -> Self {
152 let (min, max) = positions
153 .iter()
154 .zip(0..)
155 .filter(|(marker, _)| marker.is_a_camel())
156 .map(|(_, index)| index)
157 .fold(
158 (core::usize::MAX, core::usize::MIN),
159 |(minimum, maximum), index| (minimum.min(index), maximum.max(index)),
160 );
161 let positions = positions[min..=max]
162 .iter()
163 .skip_while(|marker| **marker == Marker::Divider)
164 .cloned()
165 .collect();
166 Self { positions }
167 }
168}
169
170impl FromStr for Race {
171 type Err = RaceParseError;
172
173 fn from_str(input: &str) -> Result<Self, Self::Err> {
174 let mut result = vec![];
175 let mut cursor = 0;
176 while cursor < input.len() {
177 result.push(input[cursor..=cursor].parse::<Marker>()?);
178 cursor += 1;
179 }
180 if result
181 .iter()
182 .zip(result.iter().skip(1))
183 .filter(|(l, r)| l.is_a_camel() && r.is_an_oasis() || l.is_an_oasis() && r.is_a_camel())
184 .count()
185 > 0
186 {
187 return Err(RaceParseError::CamelInOasis);
188 }
189
190 if result
191 .iter()
192 .zip(result.iter().skip(1))
193 .filter(|(l, r)| l.is_a_camel() && r.is_a_fata_morgana() || l.is_a_fata_morgana() && r.is_a_camel())
194 .count()
195 > 0
196 {
197 return Err(RaceParseError::CamelInFataMorgana);
198 }
199
200 if result
201 .iter()
202 .zip(result.iter().skip(1))
203 .filter(|(l, r)| l.is_an_adjustment() && r.is_an_adjustment())
204 .count()
205 > 0
206 {
207 return Err(RaceParseError::ToManyAdjustmentsInOnePosition);
208 }
209
210 if result
211 .iter()
212 .zip(result.iter().skip(2))
213 .filter(|(l, r)| l.is_an_adjustment() && r.is_an_adjustment())
214 .count()
215 > 0
216 {
217 return Err(RaceParseError::ConsecutiveAdjustments);
218 }
219
220 Ok(Race::from(result))
221 }
222}
223
224#[derive(PartialEq, Debug)]
226pub enum RaceParseError {
227 NotAMarker(NotAMarker),
229 CamelInOasis,
231 CamelInFataMorgana,
233 ToManyAdjustmentsInOnePosition,
235 ConsecutiveAdjustments,
237}
238
239impl From<NotAMarker> for RaceParseError {
240 fn from(problem: NotAMarker) -> Self {
241 Self::NotAMarker(problem)
242 }
243}
244
245#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
247pub struct Roll {
248 camel: Camel,
250 face: Face,
252}
253
254#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
265pub enum Face {
266 One,
268 Two,
270 Three,
272}
273
274impl Face {
275 pub fn values() -> HashSet<Self> {
277 vec![Face::One, Face::Two, Face::Three]
278 .iter()
279 .copied()
280 .collect()
281 }
282}
283
284impl From<(Camel, Face)> for Roll {
285 fn from((camel, face): (Camel, Face)) -> Self {
286 Self { camel, face }
287 }
288}
289
290impl From<Face> for usize {
291 fn from(face: Face) -> Self {
292 match face {
293 Face::One => 1,
294 Face::Two => 2,
295 Face::Three => 3,
296 }
297 }
298}
299
300impl Race {
301 pub fn perform<R>(&self, roll: R) -> Self
303 where
304 R: Into<Roll>,
305 {
306 let roll: Roll = roll.into();
307 if self.positions.contains(&Marker::Camel(roll.camel)) {
308 let index = self.positions.iter().position(|marker| *marker == Marker::Camel(roll.camel)).unwrap();
309 let offset = self.positions[index..]
310 .iter()
311 .take_while(|marker| marker.is_a_camel())
312 .count();
313
314 let unit = &self.positions[index..(index + offset)];
315 let remaining: Vec<Marker> = self.positions[0..index]
316 .iter()
317 .chain(self.positions[(index + offset)..].iter())
318 .chain(repeat(&Marker::Divider).take(4))
319 .copied()
320 .collect();
321
322 let original_divider_offset = remaining[index..].iter().enumerate().filter(|(_, marker)| marker.is_a_divider()).map(|(index, _)| index).skip(roll.face as usize + 1).nth(0).unwrap();
323 let delta: usize = match remaining[index + original_divider_offset - 1] {
324 Marker::Oasis => 2,
325 Marker::FataMorgana => 0,
326 _ => 1,
327 };
328 let divider_offset = remaining[index..].iter().enumerate().filter(|(_, marker)| marker.is_a_divider()).map(|(index, _)| index).skip(roll.face as usize + delta).nth(0).unwrap();
329 let result: Vec<Marker> = remaining[0..(index + divider_offset)]
330 .iter()
331 .chain(unit.iter())
332 .chain(remaining[(index + divider_offset)..].iter())
333 .copied()
334 .collect();
335 Self::from(result)
336 } else {
337 let positions: Vec<Marker> = self.positions.to_vec();
338 Self::from(positions)
339 }
340 }
341
342 pub fn winner(&self) -> Option<Camel> {
344 self.positions
345 .iter()
346 .filter(|marker| marker.is_a_camel())
347 .map(|marker| marker.to_camel().unwrap())
348 .last()
349 }
350
351 pub fn loser(&self) -> Option<Camel> {
353 self.positions
354 .iter()
355 .filter(|marker| marker.is_a_camel())
356 .map(|marker| marker.to_camel().unwrap())
357 .nth(0)
358 }
359
360 pub fn runner_up(&self) -> Option<Camel> {
362 self.positions
363 .iter()
364 .filter(|marker| marker.is_a_camel())
365 .map(|marker| marker.to_camel().unwrap())
366 .rev()
367 .nth(1)
368 }
369}
370
371#[derive(PartialEq, Eq, Clone, Debug)]
373pub struct Dice(HashSet<Camel>); impl Dice {
376 pub fn remove(&self, camel: Camel) -> Self {
378 let mut dice = self.0.clone();
379 dice.remove(&camel);
380 Self::from(dice)
381 }
382}
383
384impl Default for Dice {
385 fn default() -> Self {
386 let mut dice = HashSet::new();
387 dice.insert(Camel::Red);
388 dice.insert(Camel::Orange);
389 dice.insert(Camel::Yellow);
390 dice.insert(Camel::Green);
391 dice.insert(Camel::White);
392 Self::from(dice)
393 }
394}
395
396impl From<HashSet<Camel>> for Dice {
397 fn from(dice: HashSet<Camel>) -> Self {
398 Self(dice)
399 }
400}
401
402impl FromStr for Dice {
403 type Err = NoDice;
404
405 fn from_str(input: &str) -> Result<Self, Self::Err> {
406 let mut dice = HashSet::new();
407 let mut index = 0;
408 while index < input.len() {
409 let marker = input[index..=index].parse::<Marker>()?;
410 index += 1;
411 match marker.to_camel() {
412 Some(camel) => {
413 dice.insert(camel);
414 }
415 None => {
416 return Err(NoDice::NotACamel);
417 }
418 }
419 }
420 Ok(Dice::from(dice))
421 }
422}
423
424impl IntoIterator for Dice {
425 type Item = Camel;
426 type IntoIter = std::collections::hash_set::IntoIter<Self::Item>;
427
428 fn into_iter(self) -> Self::IntoIter {
429 self.0.into_iter()
430 }
431}
432
433#[derive(PartialEq, Debug)]
435pub enum NoDice {
436 NotAMarker(NotAMarker),
438 NotACamel,
440}
441
442impl From<NotAMarker> for NoDice {
443 fn from(error: NotAMarker) -> Self {
444 NoDice::NotAMarker(error)
445 }
446}
447
448#[cfg(test)]
449mod test {
450 use super::*;
451
452 #[test]
453 fn races_can_be_equated() {
454 let left = Race::from(vec![
455 Marker::Camel(Camel::Red),
456 Marker::Divider,
457 Marker::Camel(Camel::Yellow),
458 ]);
459 let right = Race::from(vec![
460 Marker::Camel(Camel::Red),
461 Marker::Divider,
462 Marker::Camel(Camel::Yellow),
463 ]);
464
465 assert_eq!(left, right);
466 }
467
468 #[test]
469 fn races_can_be_parsed() {
470 let left = "r,y".parse::<Race>().expect("to parse");
471 let right = Race::from(vec![
472 Marker::Camel(Camel::Red),
473 Marker::Divider,
474 Marker::Camel(Camel::Yellow),
475 ]);
476
477 assert_eq!(left, right);
478 }
479
480 #[test]
481 fn camel_can_not_be_in_an_oasis() {
482 let left = "r+,y".parse::<Race>();
483 let right = Err(RaceParseError::CamelInOasis);
484
485 assert_eq!(left, right);
486 }
487
488 #[test]
489 fn camel_can_not_be_in_a_fata_morgana() {
490 let left = "r-,y".parse::<Race>();
491 let right = Err(RaceParseError::CamelInFataMorgana);
492
493 assert_eq!(left, right);
494 }
495
496 #[test]
497 fn adjustments_can_not_be_in_same_position() {
498 let left = "r,+-,y".parse::<Race>();
499 let right = Err(RaceParseError::ToManyAdjustmentsInOnePosition);
500
501 assert_eq!(left, right);
502 }
503
504 #[test]
505 fn adjustments_can_not_be_consecutive() {
506 let left = "r,+,-,y".parse::<Race>();
507 let right = Err(RaceParseError::ConsecutiveAdjustments);
508
509 assert_eq!(left, right);
510 }
511
512 #[test]
513 fn races_are_normalized() {
514 let left = ",,,,,,,r,y,,,,,,,,,,,".parse::<Race>().expect("to parse");
515 let right = Race::from(vec![
516 Marker::Camel(Camel::Red),
517 Marker::Divider,
518 Marker::Camel(Camel::Yellow),
519 ]);
520
521 assert_eq!(left, right);
522 }
523
524 #[test]
525 fn races_can_perform_a_roll_one() {
526 let race = "ro,y".parse::<Race>().expect("to parse");
527 let result = race.perform((Camel::Red, Face::One));
528 let expected = "yro".parse::<Race>().expect("to parse");
529
530 assert_eq!(result, expected);
531 }
532
533 #[test]
534 fn races_can_perform_a_roll_two() {
535 let race = "ro,y".parse::<Race>().expect("to parse");
536 let result = race.perform((Camel::Red, Face::Two));
537 let expected = "y,ro".parse::<Race>().expect("to parse");
538
539 assert_eq!(result, expected);
540 }
541
542 #[test]
543 fn races_can_perform_a_roll_three() {
544 let race = "ro,y".parse::<Race>().expect("to parse");
545 let result = race.perform((Camel::Red, Face::Three));
546 let expected = "y,,ro".parse::<Race>().expect("to parse");
547
548 assert_eq!(result, expected);
549 }
550
551 #[test]
552 fn oasis_advance_a_camel_when_it_lands() {
553 let race = "r,+,y".parse::<Race>().expect("to parse");
554 let result = race.perform((Camel::Red, Face::One));
555 let expected = "+,yr".parse::<Race>().expect("to parse");
556
557 assert_eq!(result, expected);
558 }
559
560 #[test]
561 fn fata_morgana_retreats_a_camel_when_it_lands() {
562 let race = "r,-,y".parse::<Race>().expect("to parse");
563 let result = race.perform((Camel::Red, Face::One));
564 let expected = "r,-,y".parse::<Race>().expect("to parse");
565
566 assert_eq!(result, expected);
567 }
568
569 #[test]
570 fn dice_can_be_parsed() {
571 let actual = "ryg".parse::<Dice>().expect("to parse");
572 let mut dice = HashSet::new();
573 dice.insert(Camel::Red);
574 dice.insert(Camel::Yellow);
575 dice.insert(Camel::Green);
576
577 assert_eq!(actual, Dice::from(dice));
578 }
579
580 #[test]
581 fn races_have_winners_runner_ups_and_losers() {
582 let race = "r,y,g".parse::<Race>().expect("to parse");
583 let winner = race.winner();
584 let runner_up = race.runner_up();
585 let loser = race.loser();
586
587 assert_eq!(winner, Some(Camel::Green));
588 assert_eq!(runner_up, Some(Camel::Yellow));
589 assert_eq!(loser, Some(Camel::Red));
590 }
591}