1use std::fmt;
2
3use crate::Score;
4
5#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
7pub struct Frame {
8 pub first: Score,
10 pub second: Score,
12 pub bonus: Option<Score>
14}
15
16impl Frame {
17 pub fn new(first: Score, second: Score) -> Self {
19 Self {
20 first,
21 second,
22 bonus: None
23 }
24 }
25
26 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 pub fn empty() -> Self {
37 Self::default()
38 }
39
40 pub fn spare(value: usize) -> Self {
42 Self::new(Score::PINS(value), Score::SPARE)
43 }
44
45 pub fn strike() -> Self {
47 Self::new(Score::STRIKE, Score::EMPTY)
48 }
49
50 pub fn triple_strike() -> Self {
52 Self::with_bonus(Score::STRIKE, Score::STRIKE, Score::STRIKE)
53 }
54
55 pub fn is_spare(&self) -> bool {
57 !self.is_strike() && (self.second == Score::SPARE || self.first.value() + self.second.value() >= 10)
58 }
59
60 pub fn is_strike(&self) -> bool {
62 self.first.is_strike()
63 }
64
65 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}