From 72d0fb75e0502f3b87c8be8cf6f032eb80961663 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Sun, 2 Oct 2016 13:29:11 -0500 Subject: [PATCH 1/5] Update bowling canonical-data This is based on my work implementing this problem for Rust https://github.com/exercism/xrust/pull/213 I'm addressing a few things here. - The description at the top is now about implementing the tests, not about the problem domain. - I've reordered the test to (hopefully) make things flow more smoothly. I found that the previous set of tests introduced concepts in kind of a mix, instead of one at a time. They are now: - Score a 0 point game - Score a game with no spares/strikes - Spares - Simple spare - Spare with bonus - Spare in last frame - Strikes - Simple strike - Strike with bonus - Strike in last frame - Exceptions - Updated descriptions. My goal here was to try to clearly state what bit of scoring logic was being tested in each test, without relying too much on bowling-specific terms. 'strike', 'spare' and 'frame' are unavoidable, but other terms were replaceable. - Remove unnecessary tests. There were a few different tests of games without spares & strikes when only one is really necessary. My goal is that each test should lead the student to make one change to their code. If a bunch of tests can pass because of the same change then some of those tests can be removed. - Explicit description of arity removed. I tried to cover the expected API in the description text. - Expectations of exceptions changed from specific error text to `-1`, which is what I normally see for exception test definitions. --- exercises/bowling/canonical-data.json | 147 ++++++++++++-------------- 1 file changed, 70 insertions(+), 77 deletions(-) diff --git a/exercises/bowling/canonical-data.json b/exercises/bowling/canonical-data.json index fc8d95e028..27a397a17d 100644 --- a/exercises/bowling/canonical-data.json +++ b/exercises/bowling/canonical-data.json @@ -1,117 +1,110 @@ { "#": [ - "Bowling is game where players roll a heavy ball to knock down pins", - "arranged in a triangle. Write code to keep track of the score of a", - "game of bowling." + "Students should implement roll and score methods.", + "Roll should accept a single integer.", + "Score should return the game's final score, when possible", + "For brevity the tests all the rolls in an array;", + "each element of the rolls array should be passed to the roll method", + "The final tests define situations where the score can not be returned", + "The expection for these tests is -1, indicating an exception", + "In these cases you should expect an exception as is idomatic for your language" ], - "methods": { - "description": [ - "Check the public API is correct." - ], - "cases": [{ - "description": "must be able to roll with a number of pins", - "method": "roll", - "arity": 1 - }, { - "description": "must have a score", - "method": "score", - "arity": 0 - }] - }, "score": { "description": [ - "Check game can be scored correctly." + "Returns the final score of a bowling game" ], "cases": [{ - "description": "should be able to score open frame", - "rolls": [3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "expected": 7 - }, { - "description": "should be able to score multiple frames", - "rolls": [3, 4, 2, 3, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "expected": 19 - }, { - "description": "should be able to score a game with all gutterballs", + "description": "should be able to score a game with all zeros", "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "expected": 0 }, { - "description": "should be able to score a game with all single pin rolls", - "rolls": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - "expected": 20 - }, { - "description": "should be able to score a game with all open frames", + "description": "should be able to score a game with no strikes or spares", "rolls": [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6], "expected": 90 }, { - "description": "should be able to score a strike not in the last frame", + "description": "a spare followed by zeros is worth ten points", + "rolls": [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected": 10 + }, { + "description": "points scored in the roll after a spare are counted twice", + "rolls": [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected": 16 + }, { + "description": "consecutive spares each get a one roll bonus", + "rolls": [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected": 31 + }, { + "description": "a spare in the last frame gets a one roll bonus that is counted once", + "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7], + "expected": 17 + }, { + "description": "a strike earns ten points in frame with a single roll", + "rolls": [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected": 10 + }, { + "description": "points scored in the two rolls after a strike are counted twice as a bonus", "rolls": [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "expected": 26 }, { - "description": "should be able to score a spare not in the last frame", - "rolls": [5, 5, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "expected": 20 - }, { - "description": "should be able to score multiple strikes in a row", + "description": "consecutive strikes each get the two roll bonus", "rolls": [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "expected": 81 }, { - "description": "should be able to score multiple spares in a row", - "rolls": [5, 5, 3, 7, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "expected": 32 - }, { - "description": "should allow fill balls when the last frame is a strike", + "description": "a strike in the last frame gets a two roll bonus that is counted once", "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1], "expected": 18 }, { - "description": "should allow fill ball when the last frame is a spare", - "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 1, 7], - "expected": 17 + "description": "rolling a spare with the two roll bonus does not get a bonus roll", + "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3], + "expected": 20 }, { - "description": "should allow fill balls to be a strike", + "description": "strikes with the two roll bonus do not get bonus rolls", "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10], "expected": 30 }, { - "description": "should be able to score a perfect game", + "description": "a strike with the one roll bonus after a spare in the last frame does not get a bonus", + "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10], + "expected": 20 + }, { + "description": "all strikes is a perfect game", "rolls": [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10], "expected": 300 - }] - }, - "validations": { - "description": [ - "Check game rules." - ], - "cases": [{ - "description": "should not allow rolls with negative pins", - "rolls": [-1], - "expected": "Pins must have a value from 0 to 10" }, { - "description": "should not allow rolls better than strike", - "rolls": [11], - "expected": "Pins must have a value from 0 to 10" + "description": "Rolls can not score negative points", + "rolls": [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected": -1 }, { - "description": "should not allow two normal rolls better than strike", - "rolls": [5, 6], - "expected": "Pin count exceeds pins on the lane" + "description": "A roll can not score more than 10 points", + "rolls": [11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected": -1 }, { - "description": "should not allow two normal rolls better than strike in last frame", - "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6], - "expected": "Pin count exceeds pins on the lane" + "description": "Two rolls in a frame can not score more than 10 points", + "rolls": [5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected": -1 }, { - "description": "should not allow to take score at the beginning of the game", + "description": "An unstarted game can not be scored", "rolls": [], - "expected": "Score cannot be taken until the end of the game" + "expected": -1 }, { - "description": "should not allow to take score before the game has ended", - "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "expected": "Score cannot be taken until the end of the game" + "description": "An incomplete game can not be scored", + "rolls": [0, 0], + "expected": -1 }, { - "description": "should not allow rolls after the tenth frame", + "description": "A game with more than ten frames can not be scored", "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "expected": "Should not be able to roll after game is over" + "expected": -1 + }, { + "description": "bonus rolls for a strike in the last frame must be rolled before score can be calculated", + "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10], + "expected": -1 + }, { + "description": "both bonus rolls for a strike in the last frame must be rolled before score can be calculated", + "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10], + "expected": -1 }, { - "description": "should not calculate score before fill balls have been played", - "rolls": [10, 10, 10, 10, 10, 10, 10, 10, 10, 10], - "expected": "Score cannot be taken until the end of the game" + "description": "bonus roll for a spare in the last frame must be rolled before score can be calculated", + "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3], + "expected": -1 }] } } From 0a15722edb4433c993154e49d426052dc01bdc5f Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Wed, 12 Oct 2016 20:30:25 -0500 Subject: [PATCH 2/5] Fix missing word https://github.com/exercism/x-common/pull/391#discussion_r81671601 --- exercises/bowling/canonical-data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/bowling/canonical-data.json b/exercises/bowling/canonical-data.json index 27a397a17d..792c2faa7a 100644 --- a/exercises/bowling/canonical-data.json +++ b/exercises/bowling/canonical-data.json @@ -3,7 +3,7 @@ "Students should implement roll and score methods.", "Roll should accept a single integer.", "Score should return the game's final score, when possible", - "For brevity the tests all the rolls in an array;", + "For brevity the tests display all the rolls in an array;", "each element of the rolls array should be passed to the roll method", "The final tests define situations where the score can not be returned", "The expection for these tests is -1, indicating an exception", From 091abaf6583a423825ca29a4f4e09f8b8b48f06c Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Wed, 12 Oct 2016 20:30:55 -0500 Subject: [PATCH 3/5] Exception vs Error https://github.com/exercism/x-common/pull/391#discussion_r81671673 And spelled idiomatic idiomatically --- exercises/bowling/canonical-data.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/bowling/canonical-data.json b/exercises/bowling/canonical-data.json index 792c2faa7a..0de4bc7b02 100644 --- a/exercises/bowling/canonical-data.json +++ b/exercises/bowling/canonical-data.json @@ -6,8 +6,8 @@ "For brevity the tests display all the rolls in an array;", "each element of the rolls array should be passed to the roll method", "The final tests define situations where the score can not be returned", - "The expection for these tests is -1, indicating an exception", - "In these cases you should expect an exception as is idomatic for your language" + "The expection for these tests is -1, indicating an error", + "In these cases you should expect an error as is idiomatic for your language", ], "score": { "description": [ From bc146518f21d73adf732b7178477b7abbee9188f Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Wed, 12 Oct 2016 20:31:34 -0500 Subject: [PATCH 4/5] The choice of when to error https://github.com/exercism/x-common/pull/391#discussion_r82709117 --- exercises/bowling/canonical-data.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exercises/bowling/canonical-data.json b/exercises/bowling/canonical-data.json index 0de4bc7b02..156e655a3b 100644 --- a/exercises/bowling/canonical-data.json +++ b/exercises/bowling/canonical-data.json @@ -8,6 +8,10 @@ "The final tests define situations where the score can not be returned", "The expection for these tests is -1, indicating an error", "In these cases you should expect an error as is idiomatic for your language", + "When to error is also left up to your implementation. There are two options", + " - Error as soon as an invalid roll is made", + " - Error when scoring a game with an invalid roll", + "You can also error in both cases." ], "score": { "description": [ From 378262f35dea0ab2361c11d3c7b18abcb7edd7cf Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Wed, 12 Oct 2016 20:31:58 -0500 Subject: [PATCH 5/5] Restore cut test Since it did catch at least one edge case https://github.com/exercism/x-common/pull/391#discussion_r82499178 --- exercises/bowling/canonical-data.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exercises/bowling/canonical-data.json b/exercises/bowling/canonical-data.json index 156e655a3b..20b9b56a64 100644 --- a/exercises/bowling/canonical-data.json +++ b/exercises/bowling/canonical-data.json @@ -85,6 +85,10 @@ "description": "Two rolls in a frame can not score more than 10 points", "rolls": [5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "expected": -1 + }, { + "description": "Two bonus rolls after a strike in the last frame can not score more than 10 points", + "rolls": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5, 6], + "expected": -1 }, { "description": "An unstarted game can not be scored", "rolls": [],