|
use std::sync::Arc; |
|
|
|
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; |
|
|
|
fn main() {} |
|
|
|
#[derive(Clone)] |
|
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 next_and_score = (0, 0); |
|
let get_score_calculation = self.calculate_score_fn(self.scores.clone()); |
|
|
|
let next_and_score = (0..FRAMES_IN_GAME).fold(next_and_score, |next_and_score, _| { |
|
get_score_calculation(next_and_score) |
|
}); |
|
|
|
next_and_score.1 |
|
} |
|
|
|
fn calculate_score_fn(&self, scores: Vec<i32>) -> impl Fn((usize, i32)) -> (usize, i32) + '_ { |
|
let scores = Arc::new(scores.clone()); |
|
|
|
move |next_and_score: (usize, i32)| { |
|
let scores = scores.to_vec(); |
|
|
|
if self.is_strike(next_and_score.0, scores.clone()) { |
|
return self.calculate_strike_score(next_and_score, scores.clone()); |
|
} else if self.is_spare(next_and_score.0, scores.clone()) { |
|
return self.calculate_spare_score(next_and_score, scores.clone()); |
|
} |
|
|
|
self.calculate_default_score(next_and_score, scores.clone()) |
|
} |
|
} |
|
|
|
fn is_strike(&self, roll_pointer: usize, scores: Vec<i32>) -> bool { |
|
scores.get(roll_pointer) == Some(&PINS_IN_FRAME) |
|
} |
|
|
|
fn is_spare(&self, roll_pointer: usize, scores: Vec<i32>) -> bool { |
|
let frame_score: i32 = scores |
|
.to_vec() |
|
.into_iter() |
|
.skip(roll_pointer) |
|
.take(ROLLS_IN_FRAME) |
|
.sum(); |
|
frame_score == PINS_IN_FRAME |
|
} |
|
|
|
fn calculate_default_score( |
|
&self, |
|
next_and_score: (usize, i32), |
|
scores: Vec<i32>, |
|
) -> (usize, i32) { |
|
let scores = scores.clone(); |
|
let scored_points: i32 = scores |
|
.clone() |
|
.into_iter() |
|
.skip(next_and_score.0) |
|
.take(ROLLS_IN_SCORE_DEFAULT) |
|
.sum(); |
|
|
|
( |
|
next_and_score.0 + ROLLS_IN_FRAME, |
|
next_and_score.1 + scored_points, |
|
) |
|
} |
|
|
|
fn calculate_spare_score( |
|
&self, |
|
next_and_score: (usize, i32), |
|
scores: Vec<i32>, |
|
) -> (usize, i32) { |
|
let scored_points: i32 = scores |
|
.clone() |
|
.into_iter() |
|
.skip(next_and_score.0) |
|
.take(ROLLS_IN_SCORE_SPARE) |
|
.sum(); |
|
|
|
( |
|
next_and_score.0 + ROLLS_IN_FRAME, |
|
next_and_score.1 + scored_points, |
|
) |
|
} |
|
|
|
fn calculate_strike_score( |
|
&self, |
|
next_and_score: (usize, i32), |
|
scores: Vec<i32>, |
|
) -> (usize, i32) { |
|
let scored_points: i32 = scores |
|
.clone() |
|
.into_iter() |
|
.skip(next_and_score.0) |
|
.take(ROLLS_IN_SCORE_STRIKE) |
|
.sum(); |
|
|
|
( |
|
next_and_score.0 + ROLLS_IN_STRIKE_FRAME, |
|
next_and_score.1 + scored_points, |
|
) |
|
} |
|
} |
|
|
|
#[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); |
|
} |
|
} |