Skip to content

Instantly share code, notes, and snippets.

@PurpleBooth
Created October 26, 2019 09:50
Show Gist options
  • Save PurpleBooth/c01dc4a3c6acd8658286b7ffe936a8d7 to your computer and use it in GitHub Desktop.
Save PurpleBooth/c01dc4a3c6acd8658286b7ffe936a8d7 to your computer and use it in GitHub Desktop.
Bowling Kata in rust but more functional

Rules

Create a class with 2 methods:

  • roll(self, pins)
  • score(self)

The rules for scoring are as follows

  • A game is 10 frames
  • Each frame can have up to 2 rolls
  • A spare is when you knock down all pins in a frame
  • A strike is when you knock down all pins in the first roll of a frame
  • The score for a frame is the number of pins knocked down
  • If a spare is scored, the next roll is added as a bonus
  • If a strike is scored, the next 2 rolls are added as a bonus
  • A perfect game is 12 successive strikes and score 300 points
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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment