use std::fmt;
use crate::Score;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Frame {
pub first: Score,
pub second: Score,
pub bonus: Option<Score>
}
impl Frame {
pub fn new(first: Score, second: Score) -> Self {
Self {
first,
second,
bonus: None
}
}
pub fn with_bonus(first: Score, second: Score, bonus: Score) -> Self {
Self {
first,
second,
bonus: Some(bonus)
}
}
pub fn empty() -> Self {
Self::default()
}
pub fn spare(value: usize) -> Self {
Self::new(Score::PINS(value), Score::SPARE)
}
pub fn strike() -> Self {
Self::new(Score::STRIKE, Score::EMPTY)
}
pub fn triple_strike() -> Self {
Self::with_bonus(Score::STRIKE, Score::STRIKE, Score::STRIKE)
}
pub fn is_spare(&self) -> bool {
!self.is_strike() && (self.second == Score::SPARE || self.first.value() + self.second.value() >= 10)
}
pub fn is_strike(&self) -> bool {
self.first.is_strike()
}
pub fn value(&self) -> usize {
let mut value = self.first.value();
if self.second == Score::SPARE {
value += 10 - value;
} else {
value += self.second.value();
}
if let Some(bonus) = self.bonus {
if bonus == Score::SPARE {
value += 10 - self.second.value();
} else {
value += bonus.value();
}
}
value
}
}
impl<V> From<[V; 2]> for Frame where V: Into<usize> + Copy {
fn from(value: [V; 2]) -> Self {
Self::new(Score::PINS(value[0].into()), Score::PINS(value[1].into()))
}
}
impl<V> From<[V; 3]> for Frame where V: Into<usize> + Copy {
fn from(value: [V; 3]) -> Self {
Self::with_bonus(Score::PINS(value[0].into()), Score::PINS(value[1].into()), Score::PINS(value[2].into()))
}
}
impl<V> From<Vec<V>> for Frame where V: Into<usize> + Copy {
fn from(value: Vec<V>) -> Self {
let first = value.first();
let second = value.get(1);
let bonus = value.get(2);
Self {
first: first.and_then(|f| Some(Score::PINS((*f).into()))).unwrap_or(Score::EMPTY),
second: second.and_then(|s| Some(Score::PINS((*s).into()))).unwrap_or(Score::EMPTY),
bonus: bonus.and_then(|b| Some(Score::PINS((*b).into()))),
}
}
}
impl fmt::Display for Frame {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(bonus) = self.bonus {
return write!(formatter, "Frame[{}, {}, {}]", self.first, self.second, bonus);
}
if self.is_strike() {
return write!(formatter, "Frame[X]");
}
write!(formatter, "Frame[{}, {}]", self.first, self.second)
}
}
#[cfg(test)]
mod tests {
use crate::{Frame, Score};
#[test]
fn is_spare() {
let frame = Frame::new(Score::PINS(5), Score::PINS(7));
assert!(frame.is_spare());
}
#[test]
fn is_strike() {
let frame = Frame::new(Score::PINS(11), Score::PINS(1));
assert!(frame.is_strike());
}
#[test]
fn triple_strike() {
let frame = Frame::triple_strike();
assert_eq!(frame.value(), 30);
}
#[test]
fn bonus_frame() {
let frame = Frame::with_bonus(Score::STRIKE, Score::PINS(9), Score::EMPTY);
assert_eq!(frame.value(), 19);
}
#[test]
fn spare_value() {
let frame = Frame::spare(9);
assert_eq!(frame.value(), 10);
}
}