Skip to main content

bowling_rs/
score.rs

1use std::{cmp::Ordering, fmt};
2
3/// The [Score] of a shot.
4#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq)]
5pub enum Score {
6    /// Shot hasn't happened yet.
7    #[default]
8    EMPTY,
9    /// Shot knocked down `x` pins.
10    PINS(usize),
11    /// Shot was a spare.
12    SPARE,
13    /// Shot was a strike.
14    STRIKE
15}
16
17impl Score {
18    /// Caculate the value of the [Score].
19    pub fn value(&self) -> usize {
20        match self {
21            Score::EMPTY => 0,
22            Score::PINS(value) => *value.min(&10),
23            Score::SPARE | Score::STRIKE => 10
24        }
25    }
26
27    /// Check if the [Score] is a strike.
28    pub fn is_strike(&self) -> bool {
29        if *self == Score::SPARE {
30            return false;
31        }
32
33        if let Score::PINS(value) = self {
34            if value >= &10 {
35                return true;
36            }
37        }
38
39        *self == Score::STRIKE
40    }
41}
42
43impl fmt::Display for Score {
44    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45        let mut value = match self {
46            Score::EMPTY => String::from("<empty>"),
47            Score::PINS(v) => v.to_string(),
48            Score::SPARE => String::from("/"),
49            Score::STRIKE => String::from("X")
50        };
51
52        if value == "0" {
53            value = String::from("-");
54        }
55        
56        write!(formatter, "{}", value)
57    }
58}
59
60impl<V> From<V> for Score where V: Into<usize> {
61    fn from(value: V) -> Self {
62        let v = value.into();
63
64        if v >= 10 {
65            return Self::STRIKE;
66        }
67
68        Self::PINS(v)
69    }
70}
71
72impl PartialOrd for Score {
73    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74        Some(match self {
75            Score::EMPTY => match other {
76                Score::EMPTY => Ordering::Equal,
77                Score::PINS(_) => Ordering::Less,
78                Score::SPARE => Ordering::Less,
79                Score::STRIKE => Ordering::Less
80            },
81            Score::PINS(a) => match other {
82                Score::EMPTY => a.cmp(&0),
83                Score::PINS(b) => a.cmp(b),
84                Score::SPARE => Ordering::Less,
85                Score::STRIKE => Ordering::Less
86            },
87            Score::SPARE => match other {
88                Score::EMPTY => Ordering::Greater,
89                Score::PINS(b) => self.value().cmp(b),
90                Score::SPARE => Ordering::Equal,
91                Score::STRIKE => Ordering::Less
92            },
93            Score::STRIKE => match other {
94                Score::EMPTY => Ordering::Greater,
95                Score::PINS(_) => Ordering::Greater,
96                Score::SPARE => Ordering::Greater,
97                Score::STRIKE => Ordering::Equal
98            }
99        })
100    }
101}