Skip to content

Instantly share code, notes, and snippets.

@PurpleBooth

PurpleBooth/README.md

Last active Oct 24, 2019
Embed
What would you like to do?
Bowling Kata in rust this time because its been a while.

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
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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment