Skip to main content

bowling_rs/
frame.rs

1use std::fmt;
2
3use crate::Score;
4
5/// A bowling [Frame].
6#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
7pub struct Frame {
8    /// The first shot of the [Frame].
9    pub first: Score,
10    /// The second shot of the [Frame].
11    pub second: Score,
12    /// The optional bonus shot of the [Frame].
13    pub bonus: Option<Score>
14}
15
16impl Frame {
17    /// Create a new [Frame].
18    pub fn new(first: Score, second: Score) -> Self {
19        Self {
20            first,
21            second,
22            bonus: None
23        }
24    }
25
26    /// Create a new [Frame] with a bonus shot.
27    pub fn with_bonus(first: Score, second: Score, bonus: Score) -> Self {
28        Self {
29            first,
30            second,
31            bonus: Some(bonus)
32        }
33    }
34
35    /// Create an empty [Frame].
36    pub fn empty() -> Self {
37        Self::default()
38    }
39
40    /// Create a new spare [Frame].
41    pub fn spare(value: usize) -> Self {
42        Self::new(Score::PINS(value), Score::SPARE)
43    }
44
45    /// Create a new strike [Frame].
46    pub fn strike() -> Self {
47        Self::new(Score::STRIKE, Score::EMPTY)
48    }
49
50    /// Create a new [Frame] with three strikes.
51    pub fn triple_strike() -> Self {
52        Self::with_bonus(Score::STRIKE, Score::STRIKE, Score::STRIKE)
53    }
54
55    /// Check if the [Frame] is a spare.
56    pub fn is_spare(&self) -> bool {
57        !self.is_strike() && (self.second == Score::SPARE || self.first.value() + self.second.value() >= 10)
58    }
59
60    /// Check if the [Frame] is a strike.
61    pub fn is_strike(&self) -> bool {
62        self.first.is_strike()
63    }
64
65    /// Calculate the value of the [Frame].
66    pub fn value(&self) -> usize {
67        let mut value = self.first.value();
68
69        if self.second == Score::SPARE {
70            value += 10 - value;
71        } else {
72            value += self.second.value();
73        }
74
75        if let Some(bonus) = self.bonus {
76            if bonus == Score::SPARE {
77                value += 10 - self.second.value();
78            } else {
79                value += bonus.value();
80            }
81        }
82
83        value
84    }
85}
86
87impl<V> From<[V; 2]> for Frame where V: Into<usize> + Copy {
88    fn from(value: [V; 2]) -> Self {
89        Self::new(Score::PINS(value[0].into()), Score::PINS(value[1].into()))
90    }
91}
92
93impl<V> From<[V; 3]> for Frame where V: Into<usize> + Copy {
94    fn from(value: [V; 3]) -> Self {
95        Self::with_bonus(Score::PINS(value[0].into()), Score::PINS(value[1].into()), Score::PINS(value[2].into()))
96    }
97}
98
99impl<V> From<Vec<V>> for Frame where V: Into<usize> + Copy {
100    fn from(value: Vec<V>) -> Self {
101        let first = value.first();
102        let second = value.get(1);
103        let bonus = value.get(2);
104
105        Self {
106            first: first.and_then(|f| Some(Score::PINS((*f).into()))).unwrap_or(Score::EMPTY),
107            second: second.and_then(|s| Some(Score::PINS((*s).into()))).unwrap_or(Score::EMPTY),
108            bonus: bonus.and_then(|b| Some(Score::PINS((*b).into()))),
109        }
110    }
111}
112
113impl fmt::Display for Frame {
114    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115        if let Some(bonus) = self.bonus {
116            return write!(formatter, "Frame[{}, {}, {}]", self.first, self.second, bonus);
117        }
118        
119        if self.is_strike() {
120            return write!(formatter, "Frame[X]");
121        }
122
123        write!(formatter, "Frame[{}, {}]", self.first, self.second)
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use crate::{Frame, Score};
130
131    #[test]
132    fn is_spare() {
133        let frame = Frame::new(Score::PINS(5), Score::PINS(7));
134        assert!(frame.is_spare());
135    }
136
137    #[test]
138    fn is_strike() {
139        let frame = Frame::new(Score::PINS(11), Score::PINS(1));
140        assert!(frame.is_strike());
141    }
142
143    #[test]
144    fn triple_strike() {
145        let frame = Frame::triple_strike();
146        assert_eq!(frame.value(), 30);
147    }
148
149    #[test]
150    fn bonus_frame() {
151        let frame = Frame::with_bonus(Score::STRIKE, Score::PINS(9), Score::EMPTY);
152        assert_eq!(frame.value(), 19);
153    }
154
155    #[test]
156    fn spare_value() {
157        let frame = Frame::spare(9);
158        assert_eq!(frame.value(), 10);
159    }
160}