Browse Source

10 tests passing

master
Julio Biason 4 years ago
parent
commit
cf94fef465
  1. 173
      rust/bowling/src/lib.rs
  2. 15
      rust/bowling/tests/bowling.rs

173
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<u16>);
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<u16> {
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
}
}
}

15
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() {

Loading…
Cancel
Save