From 1e14ac0d809377a3e5ff8536cb8988b546450cab Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sat, 1 Oct 2016 19:24:28 -0500 Subject: [PATCH 01/16] Add tests for Bowling exercise These tests basically follow the canonical tests, with some exceptions. Exceptions ------ I've changed the descriptions to try to clarify what condition/rule/logic each test is trying to cover. I've reordered the tests. The new order: - Simplest case (all zeroes) - Open frames - Spares in non-final frame - Spares in final frame - Strikes in non-final frame - Strikes in final frame - Errors I've left out some tests, for a couple of reasons: - Won't work in Rust (e.g., passing a signed int to a function that expects unsigned -- won't compile) - Duplicative (e.g., multiple open frame tests that all exercise the same logic) I've altered test inputs from the canonical inputs. This is mostly in the tests around bonus points for strikes/spares. The canonical tests include rolls that aren't relevant to the logic being tested. I have left those rolls out so that the students can focus on the important inputs. --- exercises/bowling/.gitignore | 7 + exercises/bowling/Cargo.toml | 3 + exercises/bowling/example.rs | 1 + exercises/bowling/tests/bowling.rs | 262 +++++++++++++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 exercises/bowling/.gitignore create mode 100644 exercises/bowling/Cargo.toml create mode 100644 exercises/bowling/example.rs create mode 100644 exercises/bowling/tests/bowling.rs 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..8b1378917 --- /dev/null +++ b/exercises/bowling/example.rs @@ -0,0 +1 @@ + diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs new file mode 100644 index 000000000..5e31d0aa9 --- /dev/null +++ b/exercises/bowling/tests/bowling.rs @@ -0,0 +1,262 @@ +#[test] +fn twenty_zero_pin_rolls_is_zero() { + let game = BowlingGame::new(); + + for x in (0..20) { + game.roll(0); + } + + assert_eq!(game.score().unwrap(), 0); +} + +#[test] +fn twenty_one_pin_rolls_is_twenty() { + let game = BowlingGame::new(); + + for x in (0..20) { + game.roll(1); + } + + assert_eq!(game.score().unwrap(), 20); +} + +#[test] +fn ten_frames_without_a_strike_or_spare() { + let game = BowlingGame::new(); + + for x in (0..20) { + game.roll(4); + } + + assert_eq!(game.score().unwrap(), 80); +} + +#[test] +fn spare_in_the_first_frame_followed_by_zeros() { + let game = BowlingGame::new(); + + game.roll(4); + game.roll(6); + + for x in (0..18) { + game.roll(0); + } + + assert_eq!(game.score().unwrap(), 10); +} + +#[test] +fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { + let game = BowlingGame::new(); + + game.roll(5); + game.roll(5); + game.roll(3); + + for x in (0..17) { + game.roll(0); + } + + assert_eq!(game.score().unwrap(), 16); +} + +#[test] +fn consecutive_spares_each_get_their_bonus_points() { + let game = BowlingGame::new(); + + game.roll(5); + game.roll(5); + game.roll(3); + game.roll(7); + game.roll(4); + + for x in (0..15) { + game.roll(0); + } + + assert_eq!(game.score().unwrap(), 31); +} + +#[test] +fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { + let game = BowlingGame::new(); + + for x in (0..17) { + game.roll(0); + } + + game.roll(5); + game.roll(5); + game.roll(7); + + assert_eq!(game.score().unwrap(), 17); +} + +#[test] +fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { + let game = BowlingGame::new(); + + game.roll(10); + + for x in (0..17) { + game.roll(0); + } + + assert_eq!(game.score().unwrap(), 0); +} + +#[test] +fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() { + let game = BowlingGame::new(); + + game.roll(10); + game.roll(5); + game.roll(3); + + for x in (0..15) { + game.roll(0); + } + + assert_eq!(game.score().unwrap(), 26); +} + +#[test] +fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_with_consecutive_strikes() { + let game = BowlingGame::new(); + + game.roll(10); + game.roll(10); + game.roll(10); + game.roll(5); + game.roll(3); + + for x in (0..11) { + game.roll(0); + } + + assert_eq!(game.score().unwrap(), 81); +} + +#[test] +fn if_the_last_frame_is_a_strike_you_get_two_extra_rolls() { + let game = BowlingGame::new(); + + for x in (0..17) { + game.roll(0); + } + + game.roll(10); + game.roll(7); + game.roll(1); + + assert_eq!(game.score().unwrap(), 18); +} + +#[test] +fn strikes_in_extra_rolls_after_a_strike_in_the_final_frame_do_not_get_the_bonus() { + let game = BowlingGame::new(); + + for x in (0..17) { + game.roll(0); + } + + game.roll(10); + game.roll(10); + game.roll(10); + + assert_eq!(game.score().unwrap(), 30); +} + +fn all_strikes_is_a_perfect_score_of_300() { + let game = BowlingGame::new(); + + for x in (0..11) { + game.roll(10); + } + + assert_eq!(game.score().unwrap(), 300); +} + +fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { + let game = BowlingGame::new(); + + for x in (0..19) { + game.roll(0); + } + + game.roll(11); + + assert!(game.score().is_err()); +} + +fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { + let game = BowlingGame::new(); + + for x in (0..18) { + game.roll(0); + } + + game.roll(5); + game.roll(6); + + assert!(game.score().is_err()); +} + +fn you_can_not_score_a_game_with_no_rolls() { + let game = BowlingGame::new(); + + assert!(game.score().is_err()); +} + +fn you_can_not_score_an_incomplete_game() { + let game = BowlingGame::new(); + + for x in (0..18) { + game.roll(0); + } + + assert!(game.score().is_err()); +} + +fn you_can_not_score_a_game_with_more_than_ten_frames() { + let game = BowlingGame::new(); + + for x in (0..21) { + game.roll(0); + } + + assert!(game.score().is_err()); +} + +fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() { + let game = BowlingGame::new(); + + for x in (0..17) { + game.roll(0); + } + + game.roll(5); + game.roll(5); + + assert!(game.score().is_err()); +} + +fn if_the_last_frame_is_a_strike_you_can_not_create_a_score_before_extra_rolls_are_taken() { + let game = BowlingGame::new(); + + for x in (0..17) { + game.roll(0); + } + + game.roll(10); + + assert!(game.score().is_err()); + + game.roll(10); + + assert!(game.score().is_err()); + + game.roll(10); + + assert!(game.score().is_ok()); +} From 4644815b62ce62dda593b6f94b749c97d6775860 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 16 Oct 2016 10:40:30 -0500 Subject: [PATCH 02/16] Handle results, reorder tests, fix errors Errors ---- I think I caught all of the logical errors in my use of ranges. A second set of eyes checking that will be much appreciated Ordering ---- This mostly follows the new canonical_data.json file. I've moved some of the exception tests up front so that students can quickly handle the broad edge cases of the problem - Rolls return results - Score return results - A single roll can't be more than 10 - A game needs 10 frames to be OK The weirder edge cases are saved until the end as I don't think they need to be handled up front. Handle results ---- Based on the conversation here https://github.com/exercism/xrust/pull/213#issuecomment-253384376 I think a bare `is_ok` is fine for all of the rolls that we expect be ok. We could wrap it in `assert!`, but I think that is noisy as it indicates we're testing something that we're not actually testing. --- exercises/bowling/tests/bowling.rs | 256 +++++++++++++++++------------ 1 file changed, 155 insertions(+), 101 deletions(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 5e31d0aa9..222465478 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -1,45 +1,91 @@ #[test] -fn twenty_zero_pin_rolls_is_zero() { +fn roll_returns_a_result() { let game = BowlingGame::new(); + assert!(game.roll(0).is_ok()); +} - for x in (0..20) { - game.roll(0); +#[test] +fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { + let game = BowlingGame::new(); + + assert!(game.roll(11).is_err()); +} + +#[test] +fn a_game_score_is_ok_if_ten_frames_have_been_rolled() { + let game = BowlingGame::new(); + + for x in (0..10) { + game.roll(0).is_ok(); + game.roll(0).is_ok(); } - assert_eq!(game.score().unwrap(), 0); + assert!(game.score().is_ok()); } #[test] -fn twenty_one_pin_rolls_is_twenty() { +fn you_can_not_score_a_game_with_no_rolls() { + let game = BowlingGame::new(); + + assert!(game.score().is_err()); +} + +#[test] +fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { + let game = BowlingGame::new(); + + for x in (0..9) { + game.roll(0).is_ok(); + game.roll(0).is_ok(); + } + + assert!(game.score().is_err()); +} + +#[test] +fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { + let game = BowlingGame::new(); + + for x in (0..11) { + game.roll(0).is_ok(); + game.roll(0).is_ok(); + } + + assert!(game.score().is_err()); +} + +#[test] +fn twenty_zero_pin_rolls_scores_zero() { let game = BowlingGame::new(); for x in (0..20) { - game.roll(1); + game.roll(0).is_ok(); } - assert_eq!(game.score().unwrap(), 20); + assert_eq!(game.score().unwrap(), 0); } #[test] fn ten_frames_without_a_strike_or_spare() { let game = BowlingGame::new(); - for x in (0..20) { - game.roll(4); + for x in (0..10) { + game.roll(3).is_ok(); + game.roll(6).is_ok(); } - assert_eq!(game.score().unwrap(), 80); + assert_eq!(game.score().unwrap(), 90); } #[test] fn spare_in_the_first_frame_followed_by_zeros() { let game = BowlingGame::new(); - game.roll(4); - game.roll(6); + game.roll(6).is_ok(); + game.roll(4).is_ok(); for x in (0..18) { - game.roll(0); + game.roll(0).is_ok(); } assert_eq!(game.score().unwrap(), 10); @@ -49,29 +95,29 @@ fn spare_in_the_first_frame_followed_by_zeros() { fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { let game = BowlingGame::new(); - game.roll(5); - game.roll(5); - game.roll(3); + game.roll(6).is_ok(); + game.roll(4).is_ok(); + game.roll(3).is_ok(); for x in (0..17) { - game.roll(0); + game.roll(0).is_ok(); } assert_eq!(game.score().unwrap(), 16); } #[test] -fn consecutive_spares_each_get_their_bonus_points() { +fn consecutive_spares_each_get_a_one_roll_bonus() { let game = BowlingGame::new(); - game.roll(5); - game.roll(5); - game.roll(3); - game.roll(7); - game.roll(4); + game.roll(5).is_ok(); + game.roll(5).is_ok(); + game.roll(3).is_ok(); + game.roll(7).is_ok(); + game.roll(4).is_ok(); for x in (0..15) { - game.roll(0); + game.roll(0).is_ok(); } assert_eq!(game.score().unwrap(), 31); @@ -81,13 +127,13 @@ fn consecutive_spares_each_get_their_bonus_points() { fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { let game = BowlingGame::new(); - for x in (0..17) { - game.roll(0); + for x in (0..18) { + game.roll(0).is_ok(); } - game.roll(5); - game.roll(5); - game.roll(7); + game.roll(5).is_ok(); + game.roll(5).is_ok(); + game.roll(7).is_ok(); assert_eq!(game.score().unwrap(), 17); } @@ -96,167 +142,175 @@ fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { let game = BowlingGame::new(); - game.roll(10); + game.roll(10).is_ok(); - for x in (0..17) { - game.roll(0); + for x in (0..18) { + game.roll(0).is_ok(); } - assert_eq!(game.score().unwrap(), 0); + assert_eq!(game.score().unwrap(), 10); } #[test] fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() { let game = BowlingGame::new(); - game.roll(10); - game.roll(5); - game.roll(3); + game.roll(10).is_ok(); + game.roll(5).is_ok(); + game.roll(3).is_ok(); - for x in (0..15) { - game.roll(0); + for x in (0..16) { + game.roll(0).is_ok(); } assert_eq!(game.score().unwrap(), 26); } #[test] -fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_with_consecutive_strikes() { +fn consecutive_strikes_each_get_the_two_roll_bonus() { let game = BowlingGame::new(); - game.roll(10); - game.roll(10); - game.roll(10); - game.roll(5); - game.roll(3); + game.roll(10).is_ok(); + game.roll(10).is_ok(); + game.roll(10).is_ok(); + game.roll(5).is_ok(); + game.roll(3).is_ok(); - for x in (0..11) { - game.roll(0); + for x in (0..12) { + game.roll(0).is_ok(); } assert_eq!(game.score().unwrap(), 81); } #[test] -fn if_the_last_frame_is_a_strike_you_get_two_extra_rolls() { +fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { let game = BowlingGame::new(); - for x in (0..17) { - game.roll(0); + for x in (0..18) { + game.roll(0).is_ok(); } - game.roll(10); - game.roll(7); - game.roll(1); + game.roll(10).is_ok(); + game.roll(7).is_ok(); + game.roll(1).is_ok(); assert_eq!(game.score().unwrap(), 18); } #[test] -fn strikes_in_extra_rolls_after_a_strike_in_the_final_frame_do_not_get_the_bonus() { +fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { let game = BowlingGame::new(); - for x in (0..17) { - game.roll(0); + for x in (0..18) { + game.roll(0).is_ok(); } - game.roll(10); - game.roll(10); - game.roll(10); + game.roll(10).is_ok(); + game.roll(7).is_ok(); + game.roll(3).is_ok(); - assert_eq!(game.score().unwrap(), 30); + assert_eq!(game.score().unwrap(), 20); } -fn all_strikes_is_a_perfect_score_of_300() { +#[test] +fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { let game = BowlingGame::new(); - for x in (0..11) { - game.roll(10); + for x in (0..18) { + game.roll(0).is_ok(); } - assert_eq!(game.score().unwrap(), 300); + game.roll(10).is_ok(); + game.roll(10).is_ok(); + game.roll(10).is_ok(); + + assert_eq!(game.score().unwrap(), 30); } -fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { +#[test] +fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_a_bonus() { let game = BowlingGame::new(); - for x in (0..19) { - game.roll(0); + for x in (0..18) { + game.roll(0).is_ok(); } - game.roll(11); + game.roll(7).is_ok(); + game.roll(3).is_ok(); + game.roll(10).is_ok(); - assert!(game.score().is_err()); + assert_eq!(game.score().unwrap(), 20); } -fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { +#[test] +fn all_strikes_is_a_perfect_score_of_300() { let game = BowlingGame::new(); - for x in (0..18) { - game.roll(0); + for x in (0..12) { + game.roll(10).is_ok(); } - game.roll(5); - game.roll(6); - - assert!(game.score().is_err()); + assert_eq!(game.score().unwrap(), 300); } -fn you_can_not_score_a_game_with_no_rolls() { +#[test] +fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { let game = BowlingGame::new(); - assert!(game.score().is_err()); + assert!(game.roll(5).is_ok()); + assert!(game.roll(6).is_err()); } -fn you_can_not_score_an_incomplete_game() { +#[test] +fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_strike() { let game = BowlingGame::new(); for x in (0..18) { - game.roll(0); + game.roll(0).is_ok(); } - assert!(game.score().is_err()); + game.roll(10).is_ok(); + + assert!(game.roll(5).is_ok()); + assert!(game.roll(6).is_err()); } -fn you_can_not_score_a_game_with_more_than_ten_frames() { +#[test] +fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_taken() { let game = BowlingGame::new(); - for x in (0..21) { - game.roll(0); + for x in (0..18) { + game.roll(0).is_ok(); } + game.roll(10).is_ok(); + assert!(game.score().is_err()); -} -fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() { - let game = BowlingGame::new(); + game.roll(10).is_ok(); - for x in (0..17) { - game.roll(0); - } + assert!(game.score().is_err()); - game.roll(5); - game.roll(5); + game.roll(10).is_ok(); - assert!(game.score().is_err()); + assert!(game.score().is_ok()); } -fn if_the_last_frame_is_a_strike_you_can_not_create_a_score_before_extra_rolls_are_taken() { +#[test] +fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() { let game = BowlingGame::new(); - for x in (0..17) { - game.roll(0); + for x in (0..18) { + game.roll(0).is_ok(); } - game.roll(10); - - assert!(game.score().is_err()); - - game.roll(10); + game.roll(5).is_ok(); + game.roll(5).is_ok(); assert!(game.score().is_err()); - game.roll(10); + game.roll(10).is_ok(); assert!(game.score().is_ok()); } From e3d3c207635b2d0e67038def4b4ff70393676fa2 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 16 Oct 2016 12:53:43 -0500 Subject: [PATCH 03/16] Include crate, stylistic fixes The changes to the for loops resolve warnings thrown by the compiler. --- exercises/bowling/tests/bowling.rs | 44 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 222465478..4df896e1a 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -1,3 +1,7 @@ +extern crate bowling; + +use bowling::*; + #[test] fn roll_returns_a_result() { let game = BowlingGame::new(); @@ -15,7 +19,7 @@ fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { fn a_game_score_is_ok_if_ten_frames_have_been_rolled() { let game = BowlingGame::new(); - for x in (0..10) { + for _ in 0..10 { game.roll(0).is_ok(); game.roll(0).is_ok(); } @@ -34,7 +38,7 @@ fn you_can_not_score_a_game_with_no_rolls() { fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { let game = BowlingGame::new(); - for x in (0..9) { + for _ in 0..9 { game.roll(0).is_ok(); game.roll(0).is_ok(); } @@ -46,7 +50,7 @@ fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { let game = BowlingGame::new(); - for x in (0..11) { + for _ in 0..11 { game.roll(0).is_ok(); game.roll(0).is_ok(); } @@ -58,7 +62,7 @@ fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { fn twenty_zero_pin_rolls_scores_zero() { let game = BowlingGame::new(); - for x in (0..20) { + for _ in 0..20 { game.roll(0).is_ok(); } @@ -69,7 +73,7 @@ fn twenty_zero_pin_rolls_scores_zero() { fn ten_frames_without_a_strike_or_spare() { let game = BowlingGame::new(); - for x in (0..10) { + for _ in 0..10 { game.roll(3).is_ok(); game.roll(6).is_ok(); } @@ -84,7 +88,7 @@ fn spare_in_the_first_frame_followed_by_zeros() { game.roll(6).is_ok(); game.roll(4).is_ok(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -99,7 +103,7 @@ fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { game.roll(4).is_ok(); game.roll(3).is_ok(); - for x in (0..17) { + for _ in 0..17 { game.roll(0).is_ok(); } @@ -116,7 +120,7 @@ fn consecutive_spares_each_get_a_one_roll_bonus() { game.roll(7).is_ok(); game.roll(4).is_ok(); - for x in (0..15) { + for _ in 0..15 { game.roll(0).is_ok(); } @@ -127,7 +131,7 @@ fn consecutive_spares_each_get_a_one_roll_bonus() { fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -144,7 +148,7 @@ fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { game.roll(10).is_ok(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -159,7 +163,7 @@ fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() game.roll(5).is_ok(); game.roll(3).is_ok(); - for x in (0..16) { + for _ in 0..16 { game.roll(0).is_ok(); } @@ -176,7 +180,7 @@ fn consecutive_strikes_each_get_the_two_roll_bonus() { game.roll(5).is_ok(); game.roll(3).is_ok(); - for x in (0..12) { + for _ in 0..12 { game.roll(0).is_ok(); } @@ -187,7 +191,7 @@ fn consecutive_strikes_each_get_the_two_roll_bonus() { fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -202,7 +206,7 @@ fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -217,7 +221,7 @@ fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -232,7 +236,7 @@ fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_a_bonus() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -247,7 +251,7 @@ fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_ fn all_strikes_is_a_perfect_score_of_300() { let game = BowlingGame::new(); - for x in (0..12) { + for _ in 0..12 { game.roll(10).is_ok(); } @@ -266,7 +270,7 @@ fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_strike() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -280,7 +284,7 @@ fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_st fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_taken() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } @@ -301,7 +305,7 @@ fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_ta fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() { let game = BowlingGame::new(); - for x in (0..18) { + for _ in 0..18 { game.roll(0).is_ok(); } From 64d8305446f19e8e2ab602f96026c41f74eaecbf Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 16 Oct 2016 13:10:37 -0500 Subject: [PATCH 04/16] Ignore all but first test --- exercises/bowling/tests/bowling.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 4df896e1a..deed4a251 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -9,6 +9,7 @@ fn roll_returns_a_result() { } #[test] +#[ignore] fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { let game = BowlingGame::new(); @@ -16,6 +17,7 @@ fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { } #[test] +#[ignore] fn a_game_score_is_ok_if_ten_frames_have_been_rolled() { let game = BowlingGame::new(); @@ -28,6 +30,7 @@ fn a_game_score_is_ok_if_ten_frames_have_been_rolled() { } #[test] +#[ignore] fn you_can_not_score_a_game_with_no_rolls() { let game = BowlingGame::new(); @@ -35,6 +38,7 @@ fn you_can_not_score_a_game_with_no_rolls() { } #[test] +#[ignore] fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { let game = BowlingGame::new(); @@ -47,6 +51,7 @@ fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { } #[test] +#[ignore] fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { let game = BowlingGame::new(); @@ -59,6 +64,7 @@ fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { } #[test] +#[ignore] fn twenty_zero_pin_rolls_scores_zero() { let game = BowlingGame::new(); @@ -70,6 +76,7 @@ fn twenty_zero_pin_rolls_scores_zero() { } #[test] +#[ignore] fn ten_frames_without_a_strike_or_spare() { let game = BowlingGame::new(); @@ -82,6 +89,7 @@ fn ten_frames_without_a_strike_or_spare() { } #[test] +#[ignore] fn spare_in_the_first_frame_followed_by_zeros() { let game = BowlingGame::new(); @@ -96,6 +104,7 @@ fn spare_in_the_first_frame_followed_by_zeros() { } #[test] +#[ignore] fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { let game = BowlingGame::new(); @@ -111,6 +120,7 @@ fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { } #[test] +#[ignore] fn consecutive_spares_each_get_a_one_roll_bonus() { let game = BowlingGame::new(); @@ -128,6 +138,7 @@ fn consecutive_spares_each_get_a_one_roll_bonus() { } #[test] +#[ignore] fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { let game = BowlingGame::new(); @@ -143,6 +154,7 @@ fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { } #[test] +#[ignore] fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { let game = BowlingGame::new(); @@ -156,6 +168,7 @@ fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { } #[test] +#[ignore] fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() { let game = BowlingGame::new(); @@ -171,6 +184,7 @@ fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() } #[test] +#[ignore] fn consecutive_strikes_each_get_the_two_roll_bonus() { let game = BowlingGame::new(); @@ -188,6 +202,7 @@ fn consecutive_strikes_each_get_the_two_roll_bonus() { } #[test] +#[ignore] fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { let game = BowlingGame::new(); @@ -203,6 +218,7 @@ fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { } #[test] +#[ignore] fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { let game = BowlingGame::new(); @@ -218,6 +234,7 @@ fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { } #[test] +#[ignore] fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { let game = BowlingGame::new(); @@ -233,6 +250,7 @@ fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { } #[test] +#[ignore] fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_a_bonus() { let game = BowlingGame::new(); @@ -248,6 +266,7 @@ fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_ } #[test] +#[ignore] fn all_strikes_is_a_perfect_score_of_300() { let game = BowlingGame::new(); @@ -259,6 +278,7 @@ fn all_strikes_is_a_perfect_score_of_300() { } #[test] +#[ignore] fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { let game = BowlingGame::new(); @@ -267,6 +287,7 @@ fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { } #[test] +#[ignore] fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_strike() { let game = BowlingGame::new(); @@ -281,6 +302,7 @@ fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_st } #[test] +#[ignore] fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_taken() { let game = BowlingGame::new(); @@ -302,6 +324,7 @@ fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_ta } #[test] +#[ignore] fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() { let game = BowlingGame::new(); From ef00726f693198b60d811930a4dbe7e7a28d32da Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 16 Oct 2016 19:28:38 -0500 Subject: [PATCH 05/16] Make game mutable Since we're not using immutable games, I think the expectation here is that `roll` will mutate the game in some way, so `game` should be mutable in the tests. --- exercises/bowling/tests/bowling.rs | 46 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index deed4a251..93c3f7a8d 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -4,14 +4,14 @@ use bowling::*; #[test] fn roll_returns_a_result() { - let game = BowlingGame::new(); + 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 game = BowlingGame::new(); + let mut game = BowlingGame::new(); assert!(game.roll(11).is_err()); } @@ -19,7 +19,7 @@ fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { #[test] #[ignore] fn a_game_score_is_ok_if_ten_frames_have_been_rolled() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..10 { game.roll(0).is_ok(); @@ -40,7 +40,7 @@ fn you_can_not_score_a_game_with_no_rolls() { #[test] #[ignore] fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..9 { game.roll(0).is_ok(); @@ -53,7 +53,7 @@ fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { #[test] #[ignore] fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..11 { game.roll(0).is_ok(); @@ -66,7 +66,7 @@ fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { #[test] #[ignore] fn twenty_zero_pin_rolls_scores_zero() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..20 { game.roll(0).is_ok(); @@ -78,7 +78,7 @@ fn twenty_zero_pin_rolls_scores_zero() { #[test] #[ignore] fn ten_frames_without_a_strike_or_spare() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..10 { game.roll(3).is_ok(); @@ -91,7 +91,7 @@ fn ten_frames_without_a_strike_or_spare() { #[test] #[ignore] fn spare_in_the_first_frame_followed_by_zeros() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); game.roll(6).is_ok(); game.roll(4).is_ok(); @@ -106,7 +106,7 @@ fn spare_in_the_first_frame_followed_by_zeros() { #[test] #[ignore] fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); game.roll(6).is_ok(); game.roll(4).is_ok(); @@ -122,7 +122,7 @@ fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { #[test] #[ignore] fn consecutive_spares_each_get_a_one_roll_bonus() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); game.roll(5).is_ok(); game.roll(5).is_ok(); @@ -140,7 +140,7 @@ fn consecutive_spares_each_get_a_one_roll_bonus() { #[test] #[ignore] fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); @@ -156,7 +156,7 @@ fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { #[test] #[ignore] fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); game.roll(10).is_ok(); @@ -170,7 +170,7 @@ fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { #[test] #[ignore] fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); game.roll(10).is_ok(); game.roll(5).is_ok(); @@ -186,7 +186,7 @@ fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() #[test] #[ignore] fn consecutive_strikes_each_get_the_two_roll_bonus() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); game.roll(10).is_ok(); game.roll(10).is_ok(); @@ -204,7 +204,7 @@ fn consecutive_strikes_each_get_the_two_roll_bonus() { #[test] #[ignore] fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); @@ -220,7 +220,7 @@ fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { #[test] #[ignore] fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); @@ -236,7 +236,7 @@ fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { #[test] #[ignore] fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); @@ -252,7 +252,7 @@ fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { #[test] #[ignore] fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_a_bonus() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); @@ -268,7 +268,7 @@ fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_ #[test] #[ignore] fn all_strikes_is_a_perfect_score_of_300() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..12 { game.roll(10).is_ok(); @@ -280,7 +280,7 @@ fn all_strikes_is_a_perfect_score_of_300() { #[test] #[ignore] fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); assert!(game.roll(5).is_ok()); assert!(game.roll(6).is_err()); @@ -289,7 +289,7 @@ fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { #[test] #[ignore] fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_strike() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); @@ -304,7 +304,7 @@ fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_st #[test] #[ignore] fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_taken() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); @@ -326,7 +326,7 @@ fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_ta #[test] #[ignore] fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() { - let game = BowlingGame::new(); + let mut game = BowlingGame::new(); for _ in 0..18 { game.roll(0).is_ok(); From 3e7025fba1d239afc8626867c612d0167df62ce4 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 16 Oct 2016 19:29:47 -0500 Subject: [PATCH 06/16] Error on the first roll after the game is complete As suggested https://github.com/exercism/xrust/pull/213#pullrequestreview-4393858 --- exercises/bowling/tests/bowling.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 93c3f7a8d..b14452029 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -55,12 +55,12 @@ fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { let mut game = BowlingGame::new(); - for _ in 0..11 { + for _ in 0..10 { game.roll(0).is_ok(); game.roll(0).is_ok(); } - assert!(game.score().is_err()); + assert!(game.roll(0).is_err()); } #[test] From 7ed61e2d3885eeea0f7526df358d788932cef547 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 16 Oct 2016 19:31:16 -0500 Subject: [PATCH 07/16] Spelling --- exercises/bowling/tests/bowling.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index b14452029..3d088a700 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -251,7 +251,7 @@ fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { #[test] #[ignore] -fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_fram_does_not_get_a_bonus() { +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 { From dc7828343e56e83ca17013910cf6b2c1c0800f5b Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Mon, 17 Oct 2016 05:28:42 -0500 Subject: [PATCH 08/16] Test name clarification --- exercises/bowling/tests/bowling.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 3d088a700..100c5a755 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -52,7 +52,7 @@ fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { #[test] #[ignore] -fn a_game_score_is_err_if_more_than_ten_frames_have_been_rolled() { +fn a_roll_is_err_if_the_game_is_done() { let mut game = BowlingGame::new(); for _ in 0..10 { From 37f247d9463afcd4143cae654f7b01482565699f Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Tue, 18 Oct 2016 07:45:20 -0500 Subject: [PATCH 09/16] Declare results of game.roll as unused Binding the return to _ is an idiomatic way to show that the return value is unimportant. https://github.com/exercism/xrust/pull/213#pullrequestreview-4393900 --- exercises/bowling/tests/bowling.rs | 130 ++++++++++++++--------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 100c5a755..2fae10b41 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -22,8 +22,8 @@ fn a_game_score_is_ok_if_ten_frames_have_been_rolled() { let mut game = BowlingGame::new(); for _ in 0..10 { - game.roll(0).is_ok(); - game.roll(0).is_ok(); + let _ = game.roll(0); + let _ = game.roll(0); } assert!(game.score().is_ok()); @@ -43,8 +43,8 @@ fn a_game_score_is_err_if_fewer_than_ten_frames_have_been_rolled() { let mut game = BowlingGame::new(); for _ in 0..9 { - game.roll(0).is_ok(); - game.roll(0).is_ok(); + let _ = game.roll(0); + let _ = game.roll(0); } assert!(game.score().is_err()); @@ -56,8 +56,8 @@ fn a_roll_is_err_if_the_game_is_done() { let mut game = BowlingGame::new(); for _ in 0..10 { - game.roll(0).is_ok(); - game.roll(0).is_ok(); + let _ = game.roll(0); + let _ = game.roll(0); } assert!(game.roll(0).is_err()); @@ -69,7 +69,7 @@ fn twenty_zero_pin_rolls_scores_zero() { let mut game = BowlingGame::new(); for _ in 0..20 { - game.roll(0).is_ok(); + let _ = game.roll(0); } assert_eq!(game.score().unwrap(), 0); @@ -81,8 +81,8 @@ fn ten_frames_without_a_strike_or_spare() { let mut game = BowlingGame::new(); for _ in 0..10 { - game.roll(3).is_ok(); - game.roll(6).is_ok(); + let _ = game.roll(3); + let _ = game.roll(6); } assert_eq!(game.score().unwrap(), 90); @@ -93,11 +93,11 @@ fn ten_frames_without_a_strike_or_spare() { fn spare_in_the_first_frame_followed_by_zeros() { let mut game = BowlingGame::new(); - game.roll(6).is_ok(); - game.roll(4).is_ok(); + let _ = game.roll(6); + let _ = game.roll(4); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } assert_eq!(game.score().unwrap(), 10); @@ -108,12 +108,12 @@ fn spare_in_the_first_frame_followed_by_zeros() { fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { let mut game = BowlingGame::new(); - game.roll(6).is_ok(); - game.roll(4).is_ok(); - game.roll(3).is_ok(); + let _ = game.roll(6); + let _ = game.roll(4); + let _ = game.roll(3); for _ in 0..17 { - game.roll(0).is_ok(); + let _ = game.roll(0); } assert_eq!(game.score().unwrap(), 16); @@ -124,14 +124,14 @@ fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { fn consecutive_spares_each_get_a_one_roll_bonus() { let mut game = BowlingGame::new(); - game.roll(5).is_ok(); - game.roll(5).is_ok(); - game.roll(3).is_ok(); - game.roll(7).is_ok(); - game.roll(4).is_ok(); + let _ = game.roll(5); + let _ = game.roll(5); + let _ = game.roll(3); + let _ = game.roll(7); + let _ = game.roll(4); for _ in 0..15 { - game.roll(0).is_ok(); + let _ = game.roll(0); } assert_eq!(game.score().unwrap(), 31); @@ -143,12 +143,12 @@ 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 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(5).is_ok(); - game.roll(5).is_ok(); - game.roll(7).is_ok(); + let _ = game.roll(5); + let _ = game.roll(5); + let _ = game.roll(7); assert_eq!(game.score().unwrap(), 17); } @@ -158,10 +158,10 @@ fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { let mut game = BowlingGame::new(); - game.roll(10).is_ok(); + let _ = game.roll(10); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } assert_eq!(game.score().unwrap(), 10); @@ -172,12 +172,12 @@ fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() { let mut game = BowlingGame::new(); - game.roll(10).is_ok(); - game.roll(5).is_ok(); - game.roll(3).is_ok(); + let _ = game.roll(10); + let _ = game.roll(5); + let _ = game.roll(3); for _ in 0..16 { - game.roll(0).is_ok(); + let _ = game.roll(0); } assert_eq!(game.score().unwrap(), 26); @@ -188,14 +188,14 @@ fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() fn consecutive_strikes_each_get_the_two_roll_bonus() { let mut game = BowlingGame::new(); - game.roll(10).is_ok(); - game.roll(10).is_ok(); - game.roll(10).is_ok(); - game.roll(5).is_ok(); - game.roll(3).is_ok(); + let _ = game.roll(10); + let _ = game.roll(10); + let _ = game.roll(10); + let _ = game.roll(5); + let _ = game.roll(3); for _ in 0..12 { - game.roll(0).is_ok(); + let _ = game.roll(0); } assert_eq!(game.score().unwrap(), 81); @@ -207,12 +207,12 @@ 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 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(10).is_ok(); - game.roll(7).is_ok(); - game.roll(1).is_ok(); + let _ = game.roll(10); + let _ = game.roll(7); + let _ = game.roll(1); assert_eq!(game.score().unwrap(), 18); } @@ -223,12 +223,12 @@ fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { let mut game = BowlingGame::new(); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(10).is_ok(); - game.roll(7).is_ok(); - game.roll(3).is_ok(); + let _ = game.roll(10); + let _ = game.roll(7); + let _ = game.roll(3); assert_eq!(game.score().unwrap(), 20); } @@ -239,12 +239,12 @@ fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { let mut game = BowlingGame::new(); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(10).is_ok(); - game.roll(10).is_ok(); - game.roll(10).is_ok(); + let _ = game.roll(10); + let _ = game.roll(10); + let _ = game.roll(10); assert_eq!(game.score().unwrap(), 30); } @@ -255,12 +255,12 @@ fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_frame_does_not_get let mut game = BowlingGame::new(); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(7).is_ok(); - game.roll(3).is_ok(); - game.roll(10).is_ok(); + let _ = game.roll(7); + let _ = game.roll(3); + let _ = game.roll(10); assert_eq!(game.score().unwrap(), 20); } @@ -271,7 +271,7 @@ fn all_strikes_is_a_perfect_score_of_300() { let mut game = BowlingGame::new(); for _ in 0..12 { - game.roll(10).is_ok(); + let _ = game.roll(10); } assert_eq!(game.score().unwrap(), 300); @@ -292,10 +292,10 @@ fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_st let mut game = BowlingGame::new(); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(10).is_ok(); + let _ = game.roll(10); assert!(game.roll(5).is_ok()); assert!(game.roll(6).is_err()); @@ -307,18 +307,18 @@ fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_ta let mut game = BowlingGame::new(); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(10).is_ok(); + let _ = game.roll(10); assert!(game.score().is_err()); - game.roll(10).is_ok(); + let _ = game.roll(10); assert!(game.score().is_err()); - game.roll(10).is_ok(); + let _ = game.roll(10); assert!(game.score().is_ok()); } @@ -329,15 +329,15 @@ fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_ let mut game = BowlingGame::new(); for _ in 0..18 { - game.roll(0).is_ok(); + let _ = game.roll(0); } - game.roll(5).is_ok(); - game.roll(5).is_ok(); + let _ = game.roll(5); + let _ = game.roll(5); assert!(game.score().is_err()); - game.roll(10).is_ok(); + let _ = game.roll(10); assert!(game.score().is_ok()); } From fd2f804b2862e7668990ba84112fd98436a87c5b Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Tue, 18 Oct 2016 07:51:53 -0500 Subject: [PATCH 10/16] Very rough implementation This passes the tests. I want to work on it further before it becomes the official example, but getting working code in place will let us talk about what techniques the solution will require & where this exercise should be placed. --- exercises/bowling/example.rs | 108 +++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/exercises/bowling/example.rs b/exercises/bowling/example.rs index 8b1378917..a2e769013 100644 --- a/exercises/bowling/example.rs +++ b/exercises/bowling/example.rs @@ -1 +1,109 @@ +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().fold(0, |acc, r| acc + r) + } + + fn bonus_score(&self) -> u16 { + self.bonus.iter().fold(0, |acc, r| acc + r) + } + + fn is_complete(&self) -> bool { + if self.is_spare() { + self.bonus.len() == 1 + } else if self.is_strike() { + self.bonus.len() == 2 + } else { + self.rolls_done() + } + } + + fn rolls_done(&self) -> bool { + self.is_spare() || self.is_strike() || self.rolls.len() == 2 + } + + 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_spare() { + if self.bonus.len() < 1 { + self.bonus.push(roll) + } + } else if self.is_strike() { + if self.bonus.len() < 2 { + self.bonus.push(roll) + } + } else { + if !self.rolls_done() { + 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("Greate than 10") + } 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().last().unwrap().roll_score() > 10 { + return Err("Invalid Roll"); + } + + if self.frames.iter().last().unwrap().bonus_score() > 10 { + 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.frames.len() != 10 || self.frames.iter().any(|f| !f.is_complete()) { + Err("Game Incomplete") + } else { + Ok(self.frames.iter().fold(0, |acc, r| acc + r.score())) + } + } +} From 882c9bbc4a4702e619b542df5ffe505adf8a2af5 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Thu, 20 Oct 2016 21:41:26 -0500 Subject: [PATCH 11/16] Add fill-ball edge cases Based on https://github.com/exercism/x-common/pull/418 These tests should capture all of the edge cases of validating the two balls that can come after a strike in the final frame --- exercises/bowling/tests/bowling.rs | 31 +++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 2fae10b41..292d4b900 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -288,7 +288,7 @@ fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { #[test] #[ignore] -fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_strike() { +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 { @@ -303,6 +303,35 @@ fn you_can_not_roll_more_than_ten_pins_with_the_two_bonus_rolls_after_a_final_st #[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] fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_taken() { let mut game = BowlingGame::new(); From ac5a7e447363c636fb5f7776fcd2d966db634ba9 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Thu, 20 Oct 2016 21:44:39 -0500 Subject: [PATCH 12/16] Refactoring of example Other than the very hairy bonus validation code, I'm OK with this example. The approach of calling `add_roll` on every frame can be seen as overkill, but I think it's the frame's responsibility to know what to do with a roll, not the game's responsibility to decide which frame to add a roll to. Yeah, it's always going to be the current frame & previous frame, I realize. But adding it to each frame reads cleaner. --- exercises/bowling/example.rs | 73 ++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/exercises/bowling/example.rs b/exercises/bowling/example.rs index a2e769013..d2811af7a 100644 --- a/exercises/bowling/example.rs +++ b/exercises/bowling/example.rs @@ -1,3 +1,4 @@ + pub struct BowlingGame { frames: Vec, } @@ -20,18 +21,50 @@ impl Frame { self.bonus.iter().fold(0, |acc, r| acc + r) } - fn is_complete(&self) -> bool { - if self.is_spare() { - self.bonus.len() == 1 - } else if self.is_strike() { - self.bonus.len() == 2 + 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.bonus_done() { + if self.is_spare() { + self.bonus_score() <= 10 + } else if self.is_strike() { + if let Some(first) = self.bonus.iter().rev().last() { + if *first == 10 { + self.bonus_score() <= 20 + } else { + self.bonus_score() <= 10 + } + } else { + true + } + } else { + true + } } else { - self.rolls_done() + true } } + fn is_complete(&self) -> bool { + self.is_open() || self.bonus_done() + } + fn rolls_done(&self) -> bool { - self.is_spare() || self.is_strike() || self.rolls.len() == 2 + 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 { @@ -43,16 +76,10 @@ impl Frame { } fn add_roll(&mut self, roll: u16) { - if self.is_spare() { - if self.bonus.len() < 1 { - self.bonus.push(roll) - } - } else if self.is_strike() { - if self.bonus.len() < 2 { + if !self.is_complete() { + if self.is_spare() || self.is_strike() { self.bonus.push(roll) - } - } else { - if !self.rolls_done() { + } else { self.rolls.push(roll) } } @@ -73,7 +100,7 @@ impl BowlingGame { pub fn roll(&mut self, pins: u16) -> Result<(), &'static str> { if pins > 10 { - Err("Greate than 10") + Err("Greater than 10 pins") } else { if self.score().is_ok() { return Err("Game Finished. No more rolls."); @@ -83,11 +110,7 @@ impl BowlingGame { frame.add_roll(pins) } - if self.frames.iter().last().unwrap().roll_score() > 10 { - return Err("Invalid Roll"); - } - - if self.frames.iter().last().unwrap().bonus_score() > 10 { + if self.frames.iter().any(|f| !f.is_valid()) { return Err("Invalid Roll"); } @@ -100,10 +123,14 @@ impl BowlingGame { } pub fn score(&self) -> Result { - if self.frames.len() != 10 || self.frames.iter().any(|f| !f.is_complete()) { + 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()) + } } From b7effb32da17e8a51ee5f1e73e74c54bfaf2009b Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Thu, 20 Oct 2016 21:51:20 -0500 Subject: [PATCH 13/16] Use `sum()` --- exercises/bowling/example.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/bowling/example.rs b/exercises/bowling/example.rs index d2811af7a..5c79ce301 100644 --- a/exercises/bowling/example.rs +++ b/exercises/bowling/example.rs @@ -14,11 +14,11 @@ impl Frame { } fn roll_score(&self) -> u16 { - self.rolls.iter().fold(0, |acc, r| acc + r) + self.rolls.iter().sum() } fn bonus_score(&self) -> u16 { - self.bonus.iter().fold(0, |acc, r| acc + r) + self.bonus.iter().sum() } fn is_valid(&self) -> bool { From a6d751bd1127371d5fc2e7e8439f9fbfed5390e5 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Fri, 21 Oct 2016 07:31:32 -0500 Subject: [PATCH 14/16] Place bowling after queen-attack The Rust techniques necessary to solve bowling are pretty straightforward. It's mostly just iterator functions, addition and a Result. But the modeling of the domain is tricky. I think this pairs well with Queen Attack, which is similarly easy to write but hard to model. --- config.json | 1 + problems.md | 1 + 2 files changed, 2 insertions(+) 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/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 From d2d1d173da69646f41fbe82474708ad1342e1000 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Fri, 21 Oct 2016 07:34:50 -0500 Subject: [PATCH 15/16] Ignore recently added test --- exercises/bowling/tests/bowling.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/bowling/tests/bowling.rs b/exercises/bowling/tests/bowling.rs index 292d4b900..7a4b2b9b0 100644 --- a/exercises/bowling/tests/bowling.rs +++ b/exercises/bowling/tests/bowling.rs @@ -332,6 +332,7 @@ fn the_two_balls_after_a_final_strike_can_not_be_a_non_strike_followed_by_a_stri } #[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(); From d3ca115c47e380eaa410c2c48e8aa79037d26d28 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Tue, 25 Oct 2016 19:37:09 -0500 Subject: [PATCH 16/16] A more readable bonus_valid With the unreachable branch marked --- exercises/bowling/example.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/exercises/bowling/example.rs b/exercises/bowling/example.rs index 5c79ce301..089ef3dcb 100644 --- a/exercises/bowling/example.rs +++ b/exercises/bowling/example.rs @@ -30,24 +30,22 @@ impl Frame { } fn bonus_valid(&self) -> bool { - if self.bonus_done() { - if self.is_spare() { - self.bonus_score() <= 10 - } else if self.is_strike() { - if let Some(first) = self.bonus.iter().rev().last() { - if *first == 10 { - self.bonus_score() <= 20 - } else { - self.bonus_score() <= 10 - } - } else { - true - } + 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 { - true + self.bonus_score() <= 10 } } else { - true + unreachable!(); } }