From cf94fef465b24683adb3cd7ab5e355c957259c12 Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Thu, 28 May 2020 16:44:48 -0300 Subject: [PATCH] 10 tests passing --- rust/bowling/src/lib.rs | 173 +++++++++++++++++++++++++++++++++- rust/bowling/tests/bowling.rs | 15 --- 2 files changed, 169 insertions(+), 19 deletions(-) diff --git a/rust/bowling/src/lib.rs b/rust/bowling/src/lib.rs index 0061bcc..b405fc1 100644 --- a/rust/bowling/src/lib.rs +++ b/rust/bowling/src/lib.rs @@ -4,18 +4,183 @@ pub enum Error { GameComplete, } -pub struct BowlingGame {} +/// A throw; holds the information on how many pins where knocked or None if the throw wasn't done +/// yet. +#[derive(Debug)] +struct Throw(Option); +impl Throw { + fn new() -> Self { + Self(None) + } + + /// True if the throw is a strike + fn is_strike(&self) -> bool { + self.0.unwrap_or(0) == 10 + } + + /// This throw happened. + fn is_some(&self) -> bool { + self.0.is_some() + } + + fn score(&self) -> u16 { + self.0.unwrap_or(0) + } +} + +enum RollType { + Normal, + Streak, + Strike, +} + +/// A normal Roll; the player has the chance of throwing two balls in this. +#[derive(Debug)] +struct Normal(Throw, Throw); +impl Normal { + fn new() -> Self { + Self(Throw::new(), Throw::new()) + } + + fn is_complete(&self) -> bool { + self.0.is_strike() || (self.0.is_some() && self.1.is_some()) + } + + fn roll(&mut self, pins: u16) { + if self.0.is_some() { + self.1 = Throw(Some(pins)) + } else { + self.0 = Throw(Some(pins)) + } + } + + fn score(&self) -> u16 { + self.0.score() + self.1.score() + } + + fn post_streak_score(&self) -> u16 { + self.0.score() * 2 + self.1.score() + } + + fn roll_type(&self) -> RollType { + if self.0.score() == 10 { + RollType::Strike + } else { + if self.0.score() + self.1.score() == 10 { + RollType::Streak + } else { + RollType::Normal + } + } + } +} + +/// The last Roll is special: It can be three if the player hits at least one strike or make a +/// streak. +#[derive(Debug)] +struct Last(Throw, Throw, Throw); +impl Last { + fn new() -> Self { + Self(Throw::new(), Throw::new(), Throw::new()) + } + + fn is_complete(&self) -> bool { + self.0.is_some() && self.1.is_some() + } + + fn roll(&mut self, pins: u16) { + if self.0.is_some() { + if self.1.is_some() { + self.2 = Throw(Some(pins)) + } else { + self.1 = Throw(Some(pins)) + } + } else { + self.0 = Throw(Some(pins)) + } + } + + fn score(&self) -> u16 { + self.0.score() + self.1.score() + self.2.score() + } +} + +pub struct BowlingGame { + frames: [Normal; 9], + last: Last, + current_frame: usize, +} impl BowlingGame { pub fn new() -> Self { - unimplemented!(); + Self { + frames: [ + Normal::new(), + Normal::new(), + Normal::new(), + Normal::new(), + Normal::new(), + Normal::new(), + Normal::new(), + Normal::new(), + Normal::new(), + ], + last: Last::new(), + current_frame: 0, + } } pub fn roll(&mut self, pins: u16) -> Result<(), Error> { - unimplemented!("Record that {} pins have been scored", pins); + if pins > 10 { + Err(Error::NotEnoughPinsLeft) + } else { + // is this the last throw or a normal throw? + if dbg!(self.current_frame == 9) { + self.roll_last(pins) + } else { + self.roll_normal(pins) + } + } + } + + fn roll_last(&mut self, pins: u16) -> Result<(), Error> { + if self.last.is_complete() { + Err(Error::GameComplete) + } else { + self.last.roll(pins); + dbg!(&self.last); + Ok(()) + } + } + + fn roll_normal(&mut self, pins: u16) -> Result<(), Error> { + if dbg!(self.frames[self.current_frame].is_complete()) { + self.current_frame += 1; + self.roll(pins) + } else { + Ok(self.frames[self.current_frame].roll(pins)) + } } pub fn score(&self) -> Option { - unimplemented!("Return the score if the game is complete, or None if not."); + // the only way to have a score is when all throws when done. And for that, we know that + // the last throw must have something. + if self.last.is_complete() { + // Accumulator: (total_score_so_far, roll_type_in_the_previous_frame) + let (total, _) = + self.frames + .iter() + .fold((0, RollType::Normal), |(total, previous_roll), frame| { + let frame_score = match previous_roll { + RollType::Strike => frame.score() * 2, + RollType::Streak => frame.post_streak_score(), + RollType::Normal => frame.score(), + }; + (total + frame_score, frame.roll_type()) + }); + Some(total + self.last.score()) + } else { + None + } } } diff --git a/rust/bowling/tests/bowling.rs b/rust/bowling/tests/bowling.rs index 4e5b96d..7df48f2 100644 --- a/rust/bowling/tests/bowling.rs +++ b/rust/bowling/tests/bowling.rs @@ -7,15 +7,12 @@ fn roll_returns_a_result() { } #[test] -#[ignore] fn you_cannot_roll_more_than_ten_pins_in_a_single_roll() { let mut game = BowlingGame::new(); - assert_eq!(game.roll(11), Err(Error::NotEnoughPinsLeft)); } #[test] -#[ignore] fn a_game_score_is_some_if_ten_frames_have_been_rolled() { let mut game = BowlingGame::new(); @@ -28,15 +25,12 @@ fn a_game_score_is_some_if_ten_frames_have_been_rolled() { } #[test] -#[ignore] fn you_cannot_score_a_game_with_no_rolls() { let game = BowlingGame::new(); - assert_eq!(game.score(), None); } #[test] -#[ignore] fn a_game_score_is_none_if_fewer_than_ten_frames_have_been_rolled() { let mut game = BowlingGame::new(); @@ -49,7 +43,6 @@ fn a_game_score_is_none_if_fewer_than_ten_frames_have_been_rolled() { } #[test] -#[ignore] fn a_roll_is_err_if_the_game_is_done() { let mut game = BowlingGame::new(); @@ -62,7 +55,6 @@ fn a_roll_is_err_if_the_game_is_done() { } #[test] -#[ignore] fn twenty_zero_pin_rolls_scores_zero() { let mut game = BowlingGame::new(); @@ -74,7 +66,6 @@ fn twenty_zero_pin_rolls_scores_zero() { } #[test] -#[ignore] fn ten_frames_without_a_strike_or_spare() { let mut game = BowlingGame::new(); @@ -87,7 +78,6 @@ fn ten_frames_without_a_strike_or_spare() { } #[test] -#[ignore] fn spare_in_the_first_frame_followed_by_zeros() { let mut game = BowlingGame::new(); @@ -102,7 +92,6 @@ fn spare_in_the_first_frame_followed_by_zeros() { } #[test] -#[ignore] fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { let mut game = BowlingGame::new(); @@ -118,7 +107,6 @@ fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { } #[test] -#[ignore] fn consecutive_spares_each_get_a_one_roll_bonus() { let mut game = BowlingGame::new(); @@ -152,7 +140,6 @@ fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { } #[test] -#[ignore] fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { let mut game = BowlingGame::new(); @@ -166,7 +153,6 @@ fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { } #[test] -#[ignore] fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() { let mut game = BowlingGame::new(); @@ -432,7 +418,6 @@ fn cannot_roll_after_bonus_roll_for_strike() { assert_eq!(game.roll(2), Err(Error::GameComplete)); } - #[test] #[ignore] fn last_two_strikes_followed_by_only_last_bonus_with_non_strike_points() {