|
const FRAMES_IN_GAME: i32 = 10; |
|
const PINS_IN_FRAME: i32 = 10; |
|
const ROLLS_IN_FRAME: usize = 2; |
|
const ROLLS_IN_STRIKE_FRAME: usize = 1; |
|
const ROLLS_IN_SCORE_DEFAULT: usize = 2; |
|
const ROLLS_IN_SCORE_SPARE: usize = 3; |
|
const ROLLS_IN_SCORE_STRIKE: usize = 3; |
|
|
|
struct Game { |
|
scores: Vec<i32>, |
|
} |
|
|
|
impl Game { |
|
fn new() -> Game { |
|
Game { scores: Vec::new() } |
|
} |
|
|
|
fn roll(&self, pins_knocked_down: i32) -> Game { |
|
Game { |
|
scores: vec![self.scores.clone(), vec![pins_knocked_down]].concat(), |
|
} |
|
} |
|
|
|
fn score(&self) -> i32 { |
|
let mut score = 0; |
|
let mut roll_pointer = 0; |
|
|
|
for _ in 0..FRAMES_IN_GAME { |
|
if self.is_strike(roll_pointer) { |
|
score += self.calculate_strike_score(roll_pointer); |
|
roll_pointer += ROLLS_IN_STRIKE_FRAME; |
|
} else if self.is_spare(roll_pointer) { |
|
score += self.calculate_spare_score(roll_pointer); |
|
roll_pointer += ROLLS_IN_FRAME; |
|
} else { |
|
score += self.calculate_default_score(roll_pointer); |
|
roll_pointer += ROLLS_IN_FRAME; |
|
} |
|
} |
|
|
|
score |
|
} |
|
|
|
fn is_strike(&self, roll_pointer: usize) -> bool { |
|
self.scores.get(roll_pointer) == Some(&PINS_IN_FRAME) |
|
} |
|
|
|
fn calculate_default_score(&self, roll_pointer: usize) -> i32 { |
|
self.scores |
|
.clone() |
|
.into_iter() |
|
.skip(roll_pointer) |
|
.take(ROLLS_IN_SCORE_DEFAULT) |
|
.sum() |
|
} |
|
|
|
fn calculate_spare_score(&self, roll_pointer: usize) -> i32 { |
|
self.scores |
|
.clone() |
|
.into_iter() |
|
.skip(roll_pointer) |
|
.take(ROLLS_IN_SCORE_SPARE) |
|
.sum() |
|
} |
|
|
|
fn calculate_strike_score(&self, roll_pointer: usize) -> i32 { |
|
self.scores |
|
.clone() |
|
.into_iter() |
|
.skip(roll_pointer) |
|
.take(ROLLS_IN_SCORE_STRIKE) |
|
.sum() |
|
} |
|
|
|
fn is_spare(&self, roll_pointer: usize) -> bool { |
|
let frame_score: i32 = self |
|
.scores |
|
.clone() |
|
.into_iter() |
|
.skip(roll_pointer) |
|
.take(ROLLS_IN_FRAME) |
|
.sum(); |
|
frame_score == PINS_IN_FRAME |
|
} |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
use crate::{Game, FRAMES_IN_GAME, PINS_IN_FRAME, ROLLS_IN_FRAME}; |
|
|
|
impl Game { |
|
fn roll_frame(&self, first_roll: i32, second_roll: i32) -> Game { |
|
self.roll(first_roll).roll(second_roll) |
|
} |
|
|
|
fn roll_spare(&self) -> Game { |
|
self.roll_frame(1, 9) |
|
} |
|
|
|
fn roll_strike(&self) -> Game { |
|
self.roll(PINS_IN_FRAME) |
|
} |
|
} |
|
|
|
#[test] |
|
fn it_adds_together_all_rolls_in_game() { |
|
let mut game = Game::new(); |
|
|
|
for _ in 0..FRAMES_IN_GAME { |
|
game = game.roll_frame(1, 2) |
|
} |
|
|
|
assert_eq!(game.score(), 30); |
|
} |
|
|
|
#[test] |
|
fn gutter_ball_game() { |
|
let mut game = Game::new(); |
|
|
|
for _ in 0..FRAMES_IN_GAME { |
|
game = game.roll_frame(0, 0) |
|
} |
|
|
|
assert_eq!(game.score(), 0); |
|
} |
|
|
|
#[test] |
|
fn it_doubles_the_next_roll_on_a_spare() { |
|
let mut game = Game::new(); |
|
|
|
game = game.roll_spare(); |
|
game = game.roll_frame(2, 3); |
|
const ALREADY_ROLLED_FRAMES: i32 = 2; |
|
|
|
for _ in 0..(FRAMES_IN_GAME - ALREADY_ROLLED_FRAMES) { |
|
game = game.roll_frame(0, 0) |
|
} |
|
|
|
assert_eq!(game.score(), 17); |
|
} |
|
|
|
#[test] |
|
fn it_doubles_the_next_two_rolls_on_a_strike() { |
|
let mut game = Game::new(); |
|
|
|
game = game.roll_strike(); |
|
game = game.roll_frame(2, 3); |
|
game = game.roll_frame(2, 3); |
|
const ALREADY_ROLLED_FRAMES: i32 = 3; |
|
|
|
for _ in 0..FRAMES_IN_GAME - ALREADY_ROLLED_FRAMES { |
|
game = game.roll_frame(0, 0); |
|
} |
|
|
|
assert_eq!(game.score(), 25); |
|
} |
|
|
|
#[test] |
|
fn perfect_game() { |
|
let mut game = Game::new(); |
|
const BONUS_ROLLS: i32 = 2; |
|
|
|
for _ in 0..FRAMES_IN_GAME + BONUS_ROLLS { |
|
game = game.roll_strike() |
|
} |
|
|
|
assert_eq!(game.score(), 300); |
|
} |
|
} |