#[deny(warnings, missing_docs)] /// Errors created when in the game #[derive(Debug, PartialEq)] pub enum Error { /// The throw is higher the number of pins available. NotEnoughPinsLeft, /// The game is over, no more throws are possible. GameComplete, } /// A throw. // I'd usually move those to their own file, but since Exercism wants a single file... here we go. mod throw { #[deny(warnings, missing_docs)] use std::fmt; #[derive(PartialEq)] enum ThrowType { /// This throw doesn't exist yet; the player did something (a strike, a spare) that gave /// them another throw. Free, /// Pins knocked (or not, if the value is 0). Knock(u16), } pub struct Throw { /// The throw id; it is used only for our debugging reference. id: usize, /// The result of the throw. result: ThrowType, } impl Throw { /// Create a free Throw pub fn new_free(id: usize) -> Self { Self { id: id, result: ThrowType::Free, } } /// Create a throw that knocked some pins pub fn new(id: usize, pins: u16) -> Self { Self { id: id, result: ThrowType::Knock(pins), } } /// In-place update of a free throw pub fn update(&mut self, pins: u16) { // Silently refusing to update a non-free throw result. if self.result == ThrowType::Free { self.result = ThrowType::Knock(pins); } } /// Indicate that the Throw is a free and has no value. pub fn is_free_throw(&self) -> bool { self.result == ThrowType::Free } /// Indicate that the player made a strike with this throw. pub fn is_strike(&self) -> bool { self.result == ThrowType::Knock(10) } /// Score of this throw. pub fn score(&self) -> Option { match self.result { ThrowType::Free => None, ThrowType::Knock(x) => Some(x), } } } /// A different debug information, so it will be easier to read. impl fmt::Debug for Throw { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}-{}", self.id, match self.result { ThrowType::Free => "Free".into(), ThrowType::Knock(x) => format!("{}", x), } ) } } } /// A game frame mod frame { #[deny(warnings, missing_docs)] use std::cell::RefCell; use std::fmt; use std::rc::Rc; use super::throw::Throw; pub struct Frame { id: usize, throws: Vec>>, } impl Frame { pub fn new(id: usize) -> Self { Self { id: id, throws: vec![], } } pub fn add_throw(&mut self, throw: &Rc>) { self.throws.push(Rc::clone(throw)); } pub fn possible_spare(&self, pins: u16) -> bool { self.throws.len() == 1 && self.score().unwrap() + pins == 10 } pub fn score(&self) -> Option { if self.throws.len() == 0 || self .throws .iter() .any(|throw| throw.borrow().is_free_throw()) { None } else { Some( self.throws .iter() .map(|throw| throw.borrow().score().unwrap()) .sum(), ) } } pub fn is_closed(&self) -> bool { !self.has_free_throws() || self.is_a_strike() || self.throws.len() >= 2 } pub fn accept_more_throws(&self) -> bool { self.throws.len() != 3 } fn has_free_throws(&self) -> bool { self.throws .iter() .all(|throw| !throw.borrow().is_free_throw()) } fn is_a_strike(&self) -> bool { self.throws.len() == 1 && self.throws[0].borrow().is_strike() } } /// A different debug information, so it will be easier to read. impl fmt::Debug for Frame { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}-{} ({})", self.id, match self.score() { None => "??".into(), Some(x) => format!("{}", x), }, self.throws .iter() .map(|x| format!("{:?}", x)) .collect::>() .join(",") ) } } } use std::cell::RefCell; use std::rc::Rc; pub struct BowlingGame { pub frames: Vec, pub throws: Vec>>, } impl BowlingGame { pub fn new() -> Self { Self { frames: vec![], throws: vec![], } } pub fn roll(&mut self, pins: u16) -> Result<(), Error> { if !self.has_free_throws() && self.is_finished() { Err(Error::GameComplete) } else if pins > 10 { Err(Error::NotEnoughPinsLeft) } else { let mut frame_throws: Vec>> = Vec::new(); if !self.has_free_throws() { let throw = self.add_new_throw(pins); frame_throws.push(throw); } else { let reused_throw = self.reuse_free_throw(pins); frame_throws.push(reused_throw); } if pins == 10 { // On a strike, the player get two "free" throws. let last_id = self.throws.len(); frame_throws.push(Rc::new(RefCell::new(throw::Throw::new_free(last_id + 1)))); frame_throws.push(Rc::new(RefCell::new(throw::Throw::new_free(last_id + 2)))); } else if self.spare(pins) { let last_id = self.throws.len(); frame_throws.push(Rc::new(RefCell::new(throw::Throw::new_free(last_id + 1)))); } self.push_throws_to_last_frame(frame_throws.as_slice()); self.push_free_throws(frame_throws.as_slice()); dbg!(&self.throws); dbg!(&self.frames); Ok(()) } } /// Add a new throw to the throw list. pub fn add_new_throw(&mut self, pins: u16) -> Rc> { let last_id = self.throws.len() + 1; let new_throw = Rc::new(RefCell::new(throw::Throw::new(last_id, pins))); self.throws.push(Rc::clone(&new_throw)); Rc::clone(&new_throw) } /// Reuse on the free throws in the throw list instead of creating a new one. pub fn reuse_free_throw(&self, pins: u16) -> Rc> { let throw = self .throws .iter() .filter(|throw| throw.borrow().is_free_throw()) .take(1) .next() .expect("There are no free throws for reuse"); throw.borrow_mut().update(pins); throw.clone() } /// A a throw to the last available frame. pub fn push_throws_to_last_frame(&mut self, throws: &[Rc>]) { if self.frames.len() == 0 || (self.frames.last().unwrap().is_closed() && self.frames.len() < 10) { let last_id = self.frames.len(); let mut new_frame = frame::Frame::new(last_id + 1); for throw in throws { new_frame.add_throw(&Rc::clone(&throw)); } self.frames.push(new_frame); } else if self.frames.last().unwrap().accept_more_throws() { let last_frame = self.frames.last_mut().unwrap(); for throw in throws { last_frame.add_throw(&Rc::clone(&throw)); } } } pub fn push_free_throws(&mut self, throws: &[Rc>]) { for throw in throws.iter().filter(|throw| throw.borrow().is_free_throw()) { self.throws.push(Rc::clone(throw)); } } /// Return the game score, but only if the game is fininshed. pub fn score(&self) -> Option { if !self.is_finished() { None } else { Some(self.frames.iter().map(|x| x.score().unwrap_or(0)).sum()) } } /// Check if the game is fininshed. pub fn is_finished(&self) -> bool { self.frames.len() == 10 && self .frames .iter() .last() .map_or(false, |frame| frame.is_closed()) } pub fn spare(&self, pins: u16) -> bool { self.frames .last() .map_or(false, |frame| frame.possible_spare(pins)) } /// Check if there are any free throws available. pub fn has_free_throws(&self) -> bool { self.throws .iter() .any(|throw| throw.borrow().is_free_throw()) } }