diff --git a/config.json b/config.json
index 01bb3e9a0..89a8fd46b 100644
--- a/config.json
+++ b/config.json
@@ -30,6 +30,7 @@
"robot-simulator",
"bracket-push",
"queen-attack",
+ "bowling",
"sublist",
"space-age",
"allergies",
diff --git a/exercises/bowling/.gitignore b/exercises/bowling/.gitignore
new file mode 100644
index 000000000..0e49cdd58
--- /dev/null
+++ b/exercises/bowling/.gitignore
@@ -0,0 +1,7 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+Cargo.lock
\ No newline at end of file
diff --git a/exercises/bowling/Cargo.toml b/exercises/bowling/Cargo.toml
new file mode 100644
index 000000000..0964c18d9
--- /dev/null
+++ b/exercises/bowling/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bowling"
+version = "0.0.0"
diff --git a/exercises/bowling/example.rs b/exercises/bowling/example.rs
new file mode 100644
index 000000000..089ef3dcb
--- /dev/null
+++ b/exercises/bowling/example.rs
@@ -0,0 +1,134 @@
+
+pub struct BowlingGame {
+ frames: Vec,
+}
+
+struct Frame {
+ rolls: Vec,
+ bonus: Vec,
+}
+
+impl Frame {
+ fn score(&self) -> u16 {
+ self.roll_score() + self.bonus_score()
+ }
+
+ fn roll_score(&self) -> u16 {
+ self.rolls.iter().sum()
+ }
+
+ fn bonus_score(&self) -> u16 {
+ self.bonus.iter().sum()
+ }
+
+ fn is_valid(&self) -> bool {
+ self.rolls_valid() && self.bonus_valid()
+ }
+
+ fn rolls_valid(&self) -> bool {
+ self.roll_score() <= 10
+ }
+
+ fn bonus_valid(&self) -> bool {
+ if self.is_open() || !self.bonus_done() {
+ return true;
+ }
+
+ if self.is_spare() {
+ return self.bonus_score() <= 10;
+ }
+
+ if let Some(first) = self.bonus.iter().next() {
+ if *first == 10 {
+ self.bonus_score() <= 20
+ } else {
+ self.bonus_score() <= 10
+ }
+ } else {
+ unreachable!();
+ }
+ }
+
+ fn is_complete(&self) -> bool {
+ self.is_open() || self.bonus_done()
+ }
+
+ fn rolls_done(&self) -> bool {
+ self.rolls.len() == 2 || self.is_strike()
+ }
+
+ fn bonus_done(&self) -> bool {
+ (self.is_spare() && self.bonus.len() == 1) || (self.is_strike() && self.bonus.len() == 2)
+ }
+
+ fn is_open(&self) -> bool {
+ self.rolls.len() == 2 && self.roll_score() < 10
+ }
+
+ fn is_spare(&self) -> bool {
+ self.rolls.len() == 2 && self.roll_score() == 10
+ }
+
+ fn is_strike(&self) -> bool {
+ self.rolls.len() == 1 && self.roll_score() == 10
+ }
+
+ fn add_roll(&mut self, roll: u16) {
+ if !self.is_complete() {
+ if self.is_spare() || self.is_strike() {
+ self.bonus.push(roll)
+ } else {
+ self.rolls.push(roll)
+ }
+ }
+ }
+
+ fn new() -> Self {
+ Frame {
+ rolls: vec![],
+ bonus: vec![],
+ }
+ }
+}
+
+impl BowlingGame {
+ pub fn new() -> Self {
+ BowlingGame { frames: vec![Frame::new()] }
+ }
+
+ pub fn roll(&mut self, pins: u16) -> Result<(), &'static str> {
+ if pins > 10 {
+ Err("Greater than 10 pins")
+ } else {
+ if self.score().is_ok() {
+ return Err("Game Finished. No more rolls.");
+ }
+
+ for mut frame in self.frames.iter_mut() {
+ frame.add_roll(pins)
+ }
+
+ if self.frames.iter().any(|f| !f.is_valid()) {
+ return Err("Invalid Roll");
+ }
+
+ if self.frames.iter().last().unwrap().rolls_done() && self.frames.len() < 10 {
+ self.frames.push(Frame::new());
+ }
+
+ Ok(())
+ }
+ }
+
+ pub fn score(&self) -> Result {
+ if !self.is_done() {
+ Err("Game Incomplete")
+ } else {
+ Ok(self.frames.iter().fold(0, |acc, r| acc + r.score()))
+ }
+ }
+
+ fn is_done(&self) -> bool {
+ self.frames.len() == 10 && self.frames.iter().all(|f| f.is_complete())
+ }
+}
diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs
new file mode 100644
index 000000000..7a4b2b9b0
--- /dev/null
+++ b/exercises/bowling/tests/bowling.rs
@@ -0,0 +1,373 @@
+extern crate bowling;
+
+use bowling::*;
+
+#[test]
+fn roll_returns_a_result() {
+ let mut game = BowlingGame::new();
+ assert!(game.roll(0).is_ok());
+}
+
+#[test]
+#[ignore]
+fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() {
+ let mut game = BowlingGame::new();
+
+ assert!(game.roll(11).is_err());
+}
+
+#[test]
+#[ignore]
+fn a_game_score_is_ok_if_ten_frames_have_been_rolled() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..10 {
+ let _ = game.roll(0);
+ let _ = game.roll(0);
+ }
+
+ assert!(game.score().is_ok());
+}
+
+#[test]
+#[ignore]
+fn you_can_not_score_a_game_with_no_rolls() {
+ let game = BowlingGame::new();
+
+ assert!(game.score().is_err());
+}
+
+#[test]
+#[ignore]
+fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..9 {
+ let _ = game.roll(0);
+ let _ = game.roll(0);
+ }
+
+ assert!(game.score().is_err());
+}
+
+#[test]
+#[ignore]
+fn a_roll_is_err_if_the_game_is_done() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..10 {
+ let _ = game.roll(0);
+ let _ = game.roll(0);
+ }
+
+ assert!(game.roll(0).is_err());
+}
+
+#[test]
+#[ignore]
+fn twenty_zero_pin_rolls_scores_zero() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..20 {
+ let _ = game.roll(0);
+ }
+
+ assert_eq!(game.score().unwrap(), 0);
+}
+
+#[test]
+#[ignore]
+fn ten_frames_without_a_strike_or_spare() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..10 {
+ let _ = game.roll(3);
+ let _ = game.roll(6);
+ }
+
+ assert_eq!(game.score().unwrap(), 90);
+}
+
+#[test]
+#[ignore]
+fn spare_in_the_first_frame_followed_by_zeros() {
+ let mut game = BowlingGame::new();
+
+ let _ = game.roll(6);
+ let _ = game.roll(4);
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ assert_eq!(game.score().unwrap(), 10);
+}
+
+#[test]
+#[ignore]
+fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() {
+ let mut game = BowlingGame::new();
+
+ let _ = game.roll(6);
+ let _ = game.roll(4);
+ let _ = game.roll(3);
+
+ for _ in 0..17 {
+ let _ = game.roll(0);
+ }
+
+ assert_eq!(game.score().unwrap(), 16);
+}
+
+#[test]
+#[ignore]
+fn consecutive_spares_each_get_a_one_roll_bonus() {
+ let mut game = BowlingGame::new();
+
+ let _ = game.roll(5);
+ let _ = game.roll(5);
+ let _ = game.roll(3);
+ let _ = game.roll(7);
+ let _ = game.roll(4);
+
+ for _ in 0..15 {
+ let _ = game.roll(0);
+ }
+
+ assert_eq!(game.score().unwrap(), 31);
+}
+
+#[test]
+#[ignore]
+fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(5);
+ let _ = game.roll(5);
+ let _ = game.roll(7);
+
+ assert_eq!(game.score().unwrap(), 17);
+}
+
+#[test]
+#[ignore]
+fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() {
+ let mut game = BowlingGame::new();
+
+ let _ = game.roll(10);
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ assert_eq!(game.score().unwrap(), 10);
+}
+
+#[test]
+#[ignore]
+fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() {
+ let mut game = BowlingGame::new();
+
+ let _ = game.roll(10);
+ let _ = game.roll(5);
+ let _ = game.roll(3);
+
+ for _ in 0..16 {
+ let _ = game.roll(0);
+ }
+
+ assert_eq!(game.score().unwrap(), 26);
+}
+
+#[test]
+#[ignore]
+fn consecutive_strikes_each_get_the_two_roll_bonus() {
+ let mut game = BowlingGame::new();
+
+ let _ = game.roll(10);
+ let _ = game.roll(10);
+ let _ = game.roll(10);
+ let _ = game.roll(5);
+ let _ = game.roll(3);
+
+ for _ in 0..12 {
+ let _ = game.roll(0);
+ }
+
+ assert_eq!(game.score().unwrap(), 81);
+}
+
+#[test]
+#[ignore]
+fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(10);
+ let _ = game.roll(7);
+ let _ = game.roll(1);
+
+ assert_eq!(game.score().unwrap(), 18);
+}
+
+#[test]
+#[ignore]
+fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(10);
+ let _ = game.roll(7);
+ let _ = game.roll(3);
+
+ assert_eq!(game.score().unwrap(), 20);
+}
+
+#[test]
+#[ignore]
+fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(10);
+ let _ = game.roll(10);
+ let _ = game.roll(10);
+
+ assert_eq!(game.score().unwrap(), 30);
+}
+
+#[test]
+#[ignore]
+fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_frame_does_not_get_a_bonus() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(7);
+ let _ = game.roll(3);
+ let _ = game.roll(10);
+
+ assert_eq!(game.score().unwrap(), 20);
+}
+
+#[test]
+#[ignore]
+fn all_strikes_is_a_perfect_score_of_300() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..12 {
+ let _ = game.roll(10);
+ }
+
+ assert_eq!(game.score().unwrap(), 300);
+}
+
+#[test]
+#[ignore]
+fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() {
+ let mut game = BowlingGame::new();
+
+ assert!(game.roll(5).is_ok());
+ assert!(game.roll(6).is_err());
+}
+
+#[test]
+#[ignore]
+fn the_two_balls_after_a_final_strike_can_not_score_an_invalid_number_of_pins() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(10);
+
+ assert!(game.roll(5).is_ok());
+ assert!(game.roll(6).is_err());
+}
+
+#[test]
+#[ignore]
+fn the_two_balls_after_a_final_strike_can_be_a_strike_and_non_strike() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(10);
+
+ assert!(game.roll(10).is_ok());
+ assert!(game.roll(6).is_ok());
+}
+
+#[test]
+#[ignore]
+fn the_two_balls_after_a_final_strike_can_not_be_a_non_strike_followed_by_a_strike() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(10);
+
+ assert!(game.roll(6).is_ok());
+ assert!(game.roll(10).is_err());
+}
+
+#[test]
+#[ignore]
+fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_taken() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(10);
+
+ assert!(game.score().is_err());
+
+ let _ = game.roll(10);
+
+ assert!(game.score().is_err());
+
+ let _ = game.roll(10);
+
+ assert!(game.score().is_ok());
+}
+
+#[test]
+#[ignore]
+fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() {
+ let mut game = BowlingGame::new();
+
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
+
+ let _ = game.roll(5);
+ let _ = game.roll(5);
+
+ assert!(game.score().is_err());
+
+ let _ = game.roll(10);
+
+ assert!(game.score().is_ok());
+}
diff --git a/problems.md b/problems.md
index 642f02940..41df24594 100644
--- a/problems.md
+++ b/problems.md
@@ -49,6 +49,7 @@ grade-school | struct, entry api, Vec, Option
robot-simulator | Immutability, enum
bracket-push | From trait, stack or recursion
queen-attack | struct, trait (optional), Result
+bowling | struct, Result, goofy bowling logic
sublist | enum, generic over type
space-age | Custom Trait, From Trait, Default Trait implementation
allergies | struct, enum, bitwise (probably), vectors, filter