#[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), } ) } } #[cfg(test)] mod throw_tests { #[test] fn make_free() { let throw = super::Throw::new_free(1); assert!(throw.is_free_throw()); assert_eq!("1-Free", format!("{:?}", throw)); } #[test] fn make_strike() { let throw = super::Throw::new(1, 10); assert!(!throw.is_free_throw()); assert_eq!("1-10", format!("{:?}", throw)); assert!(throw.is_strike()); } #[test] fn update() { let mut throw = super::Throw::new_free(1); assert_eq!(throw.score(), None); throw.update(5); assert_eq!(throw.score(), Some(5)); } } } /// A game frame mod frame { #[deny(warnings, missing_docs)] 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(throw.clone()); } 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.is_free_throw()) { None } else { Some(self.throws.iter().map(|throw| throw.score().unwrap()).sum()) } } pub fn is_closed(&self) -> bool { self.throws.len() >= 2 && self.throws.iter().all(|throw| !throw.is_free_throw()) } } /// 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(",") ) } } #[cfg(test)] mod frame_tests { #[test] fn no_score() { let frame = super::Frame::new(1); assert_eq!(frame.score(), None); } #[test] fn score() { let mut frame = super::Frame::new(1); let throw = super::Throw::new(1, 10); frame.add_throw(&super::Rc::new(throw)); assert_eq!(frame.score(), Some(10)); } #[test] fn no_score_with_free_throws() { let mut frame = super::Frame::new(1); let throw1 = super::Throw::new(1, 5); let throw2 = super::Throw::new_free(2); frame.add_throw(&super::Rc::new(throw1)); frame.add_throw(&super::Rc::new(throw2)); assert_eq!(frame.score(), None); } #[test] fn debug() { let mut frame = super::Frame::new(1); let throw1 = super::Throw::new(1, 5); let throw2 = super::Throw::new_free(2); frame.add_throw(&super::Rc::new(throw1)); frame.add_throw(&super::Rc::new(throw2)); assert_eq!("1-?? (1-5,2-Free)", format!("{:?}", frame)); } } } 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 { self.reuse_free_throw(pins); } if pins == 10 { // On a strike, the player get two "free" throws. let last_id = self.throws.len(); frame_throws.push(Rc::new(throw::Throw::new_free(last_id + 1))); frame_throws.push(Rc::new(throw::Throw::new_free(last_id + 2))); } else if self.spare(pins) { let last_id = self.throws.len(); frame_throws.push(Rc::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(); let new_throw = Rc::new(throw::Throw::new(last_id, pins)); self.throws.push(new_throw.clone()); new_throw.clone() } /// Reuse on the free throws in the throw list instead of creating a new one. pub fn reuse_free_throw(&mut self, pins: u16) { (*Rc::get_mut( self.throws .iter_mut() .filter(|throw| throw.is_free_throw()) .take(1) .next() .expect("There are no free throws for reuse"), ) .expect("Failed to get the mutable reference to the free throw")) .update(pins); } /// 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() { let last_id = self.frames.len(); let mut new_frame = frame::Frame::new(last_id + 1); for throw in throws { new_frame.add_throw(&throw.clone()); } self.frames.push(new_frame); } else { let last_frame = self.frames.last_mut().unwrap(); for throw in throws { last_frame.add_throw(&throw.clone()); } } } pub fn push_free_throws(&mut self, throws: &[Rc]) { for throw in throws.iter().filter(|throw| throw.is_free_throw()) { self.throws.push(throw.clone()); } } /// 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.is_free_throw()) } } #[cfg(test)] mod game_internal_tests { use std::rc::Rc; #[test] pub fn unfinished_game_not_enough_frames() { let game = super::BowlingGame::new(); assert!(!game.is_finished()); } #[test] pub fn unfinished_game_free_throws_last_frame() { let mut game = super::BowlingGame::new(); game.frames.push(super::frame::Frame::new(1)); game.frames.push(super::frame::Frame::new(2)); game.frames.push(super::frame::Frame::new(3)); game.frames.push(super::frame::Frame::new(4)); game.frames.push(super::frame::Frame::new(5)); game.frames.push(super::frame::Frame::new(6)); game.frames.push(super::frame::Frame::new(7)); game.frames.push(super::frame::Frame::new(8)); game.frames.push(super::frame::Frame::new(9)); let mut final_frame = super::frame::Frame::new(10); final_frame.add_throw(&Rc::new(super::throw::Throw::new(1, 5))); final_frame.add_throw(&Rc::new(super::throw::Throw::new_free(2))); game.frames.push(final_frame); assert!(!game.is_finished()); } #[test] pub fn finished_game() { let mut game = super::BowlingGame::new(); game.frames.push(super::frame::Frame::new(1)); game.frames.push(super::frame::Frame::new(2)); game.frames.push(super::frame::Frame::new(3)); game.frames.push(super::frame::Frame::new(4)); game.frames.push(super::frame::Frame::new(5)); game.frames.push(super::frame::Frame::new(6)); game.frames.push(super::frame::Frame::new(7)); game.frames.push(super::frame::Frame::new(8)); game.frames.push(super::frame::Frame::new(9)); let mut final_frame = super::frame::Frame::new(10); final_frame.add_throw(&Rc::new(super::throw::Throw::new(1, 5))); final_frame.add_throw(&Rc::new(super::throw::Throw::new(2, 3))); game.frames.push(final_frame); assert!(game.is_finished()); } }