diff --git a/rust/bowling/src/lib.rs b/rust/bowling/src/lib.rs index 8c9d174..86f5858 100644 --- a/rust/bowling/src/lib.rs +++ b/rust/bowling/src/lib.rs @@ -92,37 +92,12 @@ mod throw { ) } } - - #[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::cell::RefCell; use std::fmt; use std::rc::Rc; @@ -130,7 +105,7 @@ mod frame { pub struct Frame { id: usize, - throws: Vec>, + throws: Vec>>, } impl Frame { @@ -141,8 +116,8 @@ mod frame { } } - pub fn add_throw(&mut self, throw: &Rc) { - self.throws.push(throw.clone()); + pub fn add_throw(&mut self, throw: &Rc>) { + self.throws.push(Rc::clone(throw)); } pub fn possible_spare(&self, pins: u16) -> bool { @@ -150,15 +125,39 @@ mod frame { } pub fn score(&self) -> Option { - if self.throws.len() == 0 || self.throws.iter().any(|throw| throw.is_free_throw()) { + if self.throws.len() == 0 + || self + .throws + .iter() + .any(|throw| throw.borrow().is_free_throw()) + { None } else { - Some(self.throws.iter().map(|throw| throw.score().unwrap()).sum()) + Some( + self.throws + .iter() + .map(|throw| throw.borrow().score().unwrap()) + .sum(), + ) } } pub fn is_closed(&self) -> bool { - self.throws.len() >= 2 && self.throws.iter().all(|throw| !throw.is_free_throw()) + !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() } } @@ -181,56 +180,14 @@ mod frame { ) } } - - #[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::cell::RefCell; use std::rc::Rc; pub struct BowlingGame { pub frames: Vec, - pub throws: Vec>, + pub throws: Vec>>, } impl BowlingGame { @@ -247,22 +204,23 @@ impl BowlingGame { } else if pins > 10 { Err(Error::NotEnoughPinsLeft) } else { - let mut frame_throws: Vec> = Vec::new(); + 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); + 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(throw::Throw::new_free(last_id + 1))); - frame_throws.push(Rc::new(throw::Throw::new_free(last_id + 2))); + 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(throw::Throw::new_free(last_id + 1))); + frame_throws.push(Rc::new(RefCell::new(throw::Throw::new_free(last_id + 1)))); } self.push_throws_to_last_frame(frame_throws.as_slice()); @@ -276,47 +234,50 @@ impl BowlingGame { } /// 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() + 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(&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); + 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() { + 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(&throw.clone()); + new_frame.add_throw(&Rc::clone(&throw)); } self.frames.push(new_frame); - } else { + } 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(&throw.clone()); + last_frame.add_throw(&Rc::clone(&throw)); } } } - 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()); + 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)); } } @@ -347,61 +308,8 @@ impl BowlingGame { /// 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()); + self.throws + .iter() + .any(|throw| throw.borrow().is_free_throw()) } } diff --git a/rust/bowling/tests/bowling.rs b/rust/bowling/tests/bowling.rs index baf8cd9..a7ca178 100644 --- a/rust/bowling/tests/bowling.rs +++ b/rust/bowling/tests/bowling.rs @@ -92,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(); @@ -108,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(); @@ -126,7 +124,6 @@ fn consecutive_spares_each_get_a_one_roll_bonus() { } #[test] -#[ignore] fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { let mut game = BowlingGame::new(); @@ -142,7 +139,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(); @@ -156,7 +152,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(); @@ -172,7 +167,6 @@ fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() } #[test] -#[ignore] fn consecutive_strikes_each_get_the_two_roll_bonus() { let mut game = BowlingGame::new();