diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45d62d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.sw? diff --git a/rust/bowling/src/lib.rs b/rust/bowling/src/lib.rs index a11d543..8c9d174 100644 --- a/rust/bowling/src/lib.rs +++ b/rust/bowling/src/lib.rs @@ -145,6 +145,10 @@ mod frame { 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 @@ -153,8 +157,8 @@ mod frame { } } - pub fn is_spare(&self) -> bool { - self.throws.len() == 2 && self.score() == Some(10) + pub fn is_closed(&self) -> bool { + self.throws.len() >= 2 && self.throws.iter().all(|throw| !throw.is_free_throw()) } } @@ -225,8 +229,8 @@ mod frame { use std::rc::Rc; pub struct BowlingGame { - frames: Vec, - throws: Vec>, + pub frames: Vec, + pub throws: Vec>, } impl BowlingGame { @@ -240,76 +244,164 @@ impl BowlingGame { 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 throw = self.make_throw(pins); - let last_id = self.throws.len(); - let frame = self.last_frame(); - frame.add_throw(&throw); + 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); + } - // check if the player gained any free throws. if pins == 10 { - // a strike, you get 2 free throws - let new_throw = Rc::new(throw::Throw::new_free(last_id + 1)); - self.throws.push(new_throw.clone()); - frame.add_throw(&new_throw); - - let new_throw = Rc::new(throw::Throw::new_free(last_id + 2)); - self.throws.push(new_throw.clone()); - frame.add_throw(&new_throw); - } else if frame.is_spare() { - // in a spare, just one throw - let new_throw = Rc::new(throw::Throw::new_free(last_id + 1)); - self.throws.push(new_throw.clone()); - frame.add_throw(&new_throw); + // 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(()) } } - pub fn score(&self) -> Option { - None - // if !self.is_finished() { - // None - // } else { - // Some(self.frames.iter().map(|x| x.score()).sum()) - // } + /// 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() } - /// If there are free throws, update the most recent one; if there are none, create a new - /// throw. - fn make_throw(&mut self, pins: u16) -> Rc { - let new_id = self.throws.len() + 1; - self.throws - .iter_mut() - .filter(|x| x.is_free_throw()) - .take(1) - .next() - .map_or(Rc::new(throw::Throw::new_free(new_id)), |x| { - let throw = Rc::get_mut(x).unwrap(); - throw.update(pins); - x.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); } - /// Check if the list of throws in the game there are at least one free. - fn has_free_throws(&self) -> bool { - self.throws.iter().any(|x| x.is_free_throw()) + /// 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()); + } + } } - /// The game is over when there are 10 frames of scores. - fn is_finished(&self) -> bool { - self.frames.len() == 10 + 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()); + } } - /// Get the last/current frame. - fn last_frame(&mut self) -> &mut frame::Frame { - if self.frames.len() == 0 { - let new_id = self.frames.len() + 1; - self.frames.push(frame::Frame::new(new_id)); + /// 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()) } - self.frames.iter_mut().last().unwrap() + } + + /// 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()); } } diff --git a/rust/bowling/tests/bowling.rs b/rust/bowling/tests/bowling.rs index cfd186f..baf8cd9 100644 --- a/rust/bowling/tests/bowling.rs +++ b/rust/bowling/tests/bowling.rs @@ -1,21 +1,18 @@ use bowling::*; #[test] -#[ignore] fn roll_returns_a_result() { let mut game = BowlingGame::new(); assert!(game.roll(0).is_ok()); } #[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,14 +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(); @@ -48,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(); @@ -61,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(); @@ -73,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(); @@ -86,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();