diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md index dd336fe0..60ccad1b 100644 --- a/exercises/practice/bowling/.docs/instructions.md +++ b/exercises/practice/bowling/.docs/instructions.md @@ -2,29 +2,24 @@ Score a bowling game. -Bowling is a 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. +Bowling is a 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. ## Scoring Bowling -The game consists of 10 frames. A frame is composed of one or two ball -throws with 10 pins standing at frame initialization. There are three -cases for the tabulation of a frame. +The game consists of 10 frames. +A frame is composed of one or two ball throws with 10 pins standing at frame initialization. +There are three cases for the tabulation of a frame. -- An open frame is where a score of less than 10 is recorded for the - frame. In this case the score for the frame is the number of pins - knocked down. +- An open frame is where a score of less than 10 is recorded for the frame. + In this case the score for the frame is the number of pins knocked down. -- A spare is where all ten pins are knocked down by the second - throw. The total value of a spare is 10 plus the number of pins - knocked down in their next throw. +- A spare is where all ten pins are knocked down by the second throw. + The total value of a spare is 10 plus the number of pins knocked down in their next throw. -- A strike is where all ten pins are knocked down by the first - throw. The total value of a strike is 10 plus the number of pins - knocked down in the next two throws. If a strike is immediately - followed by a second strike, then the value of the first strike - cannot be determined until the ball is thrown one more time. +- A strike is where all ten pins are knocked down by the first throw. + The total value of a strike is 10 plus the number of pins knocked down in the next two throws. + If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time. Here is a three frame example: @@ -40,11 +35,11 @@ Frame 3 is (9 + 0) = 9 This means the current running total is 48. -The tenth frame in the game is a special case. If someone throws a -strike or a spare then they get a fill ball. Fill balls exist to -calculate the total of the 10th frame. Scoring a strike or spare on -the fill ball does not give the player more fill balls. The total -value of the 10th frame is the total number of pins knocked down. +The tenth frame in the game is a special case. +If someone throws a spare or a strike then they get one or two fill balls respectively. +Fill balls exist to calculate the total of the 10th frame. +Scoring a strike or spare on the fill ball does not give the player more fill balls. +The total value of the 10th frame is the total number of pins knocked down. For a tenth frame of X1/ (strike and a spare), the total value is 20. @@ -52,10 +47,10 @@ For a tenth frame of XXX (three strikes), the total value is 30. ## Requirements -Write code to keep track of the score of a game of bowling. It should -support two operations: +Write code to keep track of the score of a game of bowling. +It should support two operations: -- `roll(pins : int)` is called each time the player rolls a ball. The - argument is the number of pins knocked down. -- `score(): int` is called only at the very end of the game. It - returns the total score for that game. +- `roll(pins : int)` is called each time the player rolls a ball. + The argument is the number of pins knocked down. +- `score() : int` is called only at the very end of the game. + It returns the total score for that game. diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json index b5a47b75..2bac9b3b 100644 --- a/exercises/practice/bowling/.meta/config.json +++ b/exercises/practice/bowling/.meta/config.json @@ -20,7 +20,7 @@ ".meta/example.php" ] }, - "blurb": "Score a bowling game", - "source": "The Bowling Game Kata at but UncleBob", - "source_url": "https://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" + "blurb": "Score a bowling game.", + "source": "The Bowling Game Kata from UncleBob", + "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" } diff --git a/exercises/practice/bowling/.meta/example.php b/exercises/practice/bowling/.meta/example.php index d7b0938a..b008f5fa 100644 --- a/exercises/practice/bowling/.meta/example.php +++ b/exercises/practice/bowling/.meta/example.php @@ -1,27 +1,5 @@ . - * - * To disable strict typing, comment out the directive below. - */ - declare(strict_types=1); /** diff --git a/exercises/practice/bowling/.meta/tests.toml b/exercises/practice/bowling/.meta/tests.toml index 963df175..19042607 100644 --- a/exercises/practice/bowling/.meta/tests.toml +++ b/exercises/practice/bowling/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [656ae006-25c2-438c-a549-f338e7ec7441] description = "should be able to score a game with all zeros" @@ -38,6 +45,9 @@ description = "rolling a spare with the two roll bonus does not get a bonus roll [576faac1-7cff-4029-ad72-c16bcada79b5] description = "strikes with the two roll bonus do not get bonus rolls" +[efb426ec-7e15-42e6-9b96-b4fca3ec2359] +description = "last two strikes followed by only last bonus with non strike points" + [72e24404-b6c6-46af-b188-875514c0377b] description = "a strike with the one roll bonus after a spare in the last frame does not get a bonus" diff --git a/exercises/practice/bowling/BowlingTest.php b/exercises/practice/bowling/BowlingTest.php index 490267d3..186dddcb 100644 --- a/exercises/practice/bowling/BowlingTest.php +++ b/exercises/practice/bowling/BowlingTest.php @@ -1,27 +1,5 @@ . - * - * To disable strict typing, comment out the directive below. - */ - declare(strict_types=1); /** @@ -43,6 +21,10 @@ public function setUp(): void $this->game = new Game(); } + /** + * uuid 656ae006-25c2-438c-a549-f338e7ec7441 + * @testdox should be able to score a game with all zeros + */ public function testShouldBeAbleToScoreAGameWithAllZeros(): void { $this->rollMany(20, 0); @@ -50,6 +32,10 @@ public function testShouldBeAbleToScoreAGameWithAllZeros(): void $this->assertEquals(0, $this->game->score()); } + /** + * uuid f85dcc56-cd6b-4875-81b3-e50921e3597b + * @testdox should be able to score a game with no strikes or spares + */ public function testShouldBeAbleToScoreAGameWithNoStrikesOrSpares(): void { $this->game->roll(3); @@ -76,6 +62,10 @@ public function testShouldBeAbleToScoreAGameWithNoStrikesOrSpares(): void $this->assertEquals(90, $this->game->score()); } + /** + * uuid d1f56305-3ac2-4fe0-8645-0b37e3073e20 + * @testdox a spare followed by zeros is worth ten points + */ public function testASpareFollowedByZerosIsWorthTenPoints(): void { $this->game->roll(6); @@ -85,6 +75,10 @@ public function testASpareFollowedByZerosIsWorthTenPoints(): void $this->assertEquals(10, $this->game->score()); } + /** + * uuid 0b8c8bb7-764a-4287-801a-f9e9012f8be4 + * @testdox points scored in the roll after a spare are counted twice + */ public function testPointsScoredInTheRollAfterASpareAreCountedTwice(): void { $this->game->roll(6); @@ -95,6 +89,10 @@ public function testPointsScoredInTheRollAfterASpareAreCountedTwice(): void $this->assertEquals(16, $this->game->score()); } + /** + * uuid 4d54d502-1565-4691-84cd-f29a09c65bea + * @testdox consecutive spares each get a one roll bonus + */ public function testConsecutiveSparesEachGetAOneRollBonus(): void { $this->game->roll(5); @@ -107,6 +105,10 @@ public function testConsecutiveSparesEachGetAOneRollBonus(): void $this->assertEquals(31, $this->game->score()); } + /** + * uuid e5c9cf3d-abbe-4b74-ad48-34051b2b08c0 + * @testdox a spare in the last frame gets a one roll bonus that is counted once + */ public function testASpareInTheLastFrameGetsAOneRollBonusThatIsCountedOnce(): void { $this->rollMany(18, 0); @@ -117,6 +119,10 @@ public function testASpareInTheLastFrameGetsAOneRollBonusThatIsCountedOnce(): vo $this->assertEquals(17, $this->game->score()); } + /** + * uuid 75269642-2b34-4b72-95a4-9be28ab16902 + * @testdox a strike earns ten points in a frame with a single roll + */ public function testAStrikeEarnsTenPointsInFrameWithASingleRoll(): void { $this->game->roll(10); @@ -125,6 +131,10 @@ public function testAStrikeEarnsTenPointsInFrameWithASingleRoll(): void $this->assertEquals(10, $this->game->score()); } + /** + * uuid 037f978c-5d01-4e49-bdeb-9e20a2e6f9a6 + * @testdox points scored in the two rolls after a strike are counted twice as a bonus + */ public function testPointsScoredInTheTwoRollsAfterAStrikeAreCountedTwiceAsABonus(): void { $this->game->roll(10); @@ -135,6 +145,10 @@ public function testPointsScoredInTheTwoRollsAfterAStrikeAreCountedTwiceAsABonus $this->assertEquals(26, $this->game->score()); } + /** + * uuid 1635e82b-14ec-4cd1-bce4-4ea14bd13a49 + * @testdox consecutive strikes each get the two roll bonus + */ public function testConsecutiveStrikesEachGetTheTwoRollBonus(): void { $this->game->roll(10); @@ -147,6 +161,10 @@ public function testConsecutiveStrikesEachGetTheTwoRollBonus(): void $this->assertEquals(81, $this->game->score()); } + /** + * uuid e483e8b6-cb4b-4959-b310-e3982030d766 + * @testdox a strike in the last frame gets a two roll bonus that is counted once + */ public function testAStrikeInTheLastFrameGetsATwoRollBonusThatIsCountedOnce(): void { $this->rollMany(18, 0); @@ -157,6 +175,24 @@ public function testAStrikeInTheLastFrameGetsATwoRollBonusThatIsCountedOnce(): v $this->assertEquals(18, $this->game->score()); } + /** + * uuid 9d5c87db-84bc-4e01-8e95-53350c8af1f8 + * @testdox rolling a spare with the two roll bonus does not get a bonus roll + */ + public function testAStrikeWithTheOneRollBonusAfterASpareInTheLastFrameDoesNotGetABonus(): void + { + $this->rollMany(18, 0); + $this->game->roll(10); + $this->game->roll(7); + $this->game->roll(3); + + $this->assertEquals(20, $this->game->score()); + } + + /** + * uuid 576faac1-7cff-4029-ad72-c16bcada79b5 + * @testdox strikes with the two roll bonus do not get bonus rolls + */ public function testRollingASpareWithTheTwoRollBonusDoesNotGetABonusRoll(): void { $this->rollMany(18, 0); @@ -167,16 +203,25 @@ public function testRollingASpareWithTheTwoRollBonusDoesNotGetABonusRoll(): void $this->assertEquals(30, $this->game->score()); } - public function testAStrikeWithTheOneRollBonusAfterASpareInTheLastFrameDoesNotGetABonus(): void + /** + * uuid efb426ec-7e15-42e6-9b96-b4fca3ec2359 + * @testdox last two strikes followed by only last bonus with non strike points + */ + public function testLastTwoStrikesFollowedByOnlyLastBonusWithNonStrikePoints(): void { - $this->rollMany(18, 0); - $this->game->roll(7); - $this->game->roll(3); + $this->rollMany(16, 0); + $this->game->roll(10); $this->game->roll(10); + $this->game->roll(0); + $this->game->roll(1); - $this->assertEquals(20, $this->game->score()); + $this->assertEquals(31, $this->game->score()); } + /** + * uuid 72e24404-b6c6-46af-b188-875514c0377b + * @testdox a strike with the one roll bonus after a spare in the last frame does not get a bonus + */ public function testStrikesWithTheTwoRollBonusDoNotGetBonusRolls(): void { $this->rollMany(18, 0); @@ -187,6 +232,10 @@ public function testStrikesWithTheTwoRollBonusDoNotGetBonusRolls(): void $this->assertEquals(20, $this->game->score()); } + /** + * uuid 62ee4c72-8ee8-4250-b794-234f1fec17b1 + * @testdox all strikes is a perfect gam + */ public function testAllStrikesIsAPerfectGame(): void { $this->rollMany(12, 10); @@ -194,6 +243,10 @@ public function testAllStrikesIsAPerfectGame(): void $this->assertEquals(300, $this->game->score()); } + /** + * uuid 1245216b-19c6-422c-b34b-6e4012d7459f + * @testdox rolls cannot score negative points + */ public function testRollsCanNotScoreNegativePoints(): void { $this->expectException(Exception::class); @@ -201,6 +254,10 @@ public function testRollsCanNotScoreNegativePoints(): void $this->game->roll(-1); } + /** + * uuid 5fcbd206-782c-4faa-8f3a-be5c538ba841 + * @testdox a roll cannot score more than 10 points + */ public function testARollCanNotScoreMoreThan10Points(): void { $this->expectException(Exception::class); @@ -210,6 +267,10 @@ public function testARollCanNotScoreMoreThan10Points(): void $this->game->score(); } + /** + * uuid fb023c31-d842-422d-ad7e-79ce1db23c21 + * @testdox two rolls in a frame cannot score more than 10 points + */ public function testTwoRollsInAFrameCanNotScoreMoreThan10Points(): void { $this->expectException(Exception::class); @@ -220,6 +281,26 @@ public function testTwoRollsInAFrameCanNotScoreMoreThan10Points(): void $this->game->score(); } + /** + * uuid 6082d689-d677-4214-80d7-99940189381b + * @testdox bonus roll after a strike in the last frame cannot score more than 10 points + */ + public function testBonusRollsAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points(): void + { + $this->expectException(Exception::class); + + $this->rollMany(18, 0); + $this->game->roll(10); + $this->game->roll(11); + + $this->game->score(); + } + + + /** + * uuid e9565fe6-510a-4675-ba6b-733a56767a45 + * @testdox two bonus rolls after a strike in the last frame cannot score more than 10 points + */ public function testTwoBonusRollsAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points(): void { $this->expectException(Exception::class); @@ -232,6 +313,54 @@ public function testTwoBonusRollsAfterAStrikeInTheLastFrameCanNotScoreMoreThan10 $this->game->score(); } + /** + * uuid 2f6acf99-448e-4282-8103-0b9c7df99c3d + * @testdox two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike + */ + public function testTwoBonusRollsAfterAStrikeInTheLastFramCanScoreMoreThan10PointsIfOneIsAStrike(): void + { + $this->rollMany(18, 0); + $this->game->roll(10); + $this->game->roll(10); + $this->game->roll(6); + + $this->assertEquals(26, $this->game->score()); + } + + /** + * uuid 6380495a-8bc4-4cdb-a59f-5f0212dbed01 + * @testdox the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike + */ + public function testTheSecondBonusRollsAfterAStrikeInTheLastFrameCannotBeAStrikeIfTheFirstOneIsNotAStrike(): void + { + $this->expectException(Exception::class); + $this->rollMany(18, 0); + $this->game->roll(10); + $this->game->roll(6); + $this->game->roll(10); + + $this->game->score(); + } + + /** + * uuid 2b2976ea-446c-47a3-9817-42777f09fe7e + * @testdox second bonus roll after a strike in the last frame cannot score more than 10 points + */ + public function testSecondBonusRollAfterAStrikeInTheLastFrameCannotScoreMoreThan10Points(): void + { + $this->expectException(Exception::class); + $this->rollMany(18, 0); + $this->game->roll(10); + $this->game->roll(10); + $this->game->roll(11); + + $this->game->score(); + } + + /** + * uuid 29220245-ac8d-463d-bc19-98a94cfada8a + * @testdox an unstarted game cannot be scored + */ public function testAnUnstartedGameCanNotBeScored(): void { $this->expectException(Exception::class); @@ -239,6 +368,10 @@ public function testAnUnstartedGameCanNotBeScored(): void $this->game->score(); } + /** + * uuid 4473dc5d-1f86-486f-bf79-426a52ddc955 + * @testdox an incomplete game cannot be scored + */ public function testAnIncompleteGameCanNotBeScored(): void { $this->expectException(Exception::class); @@ -248,14 +381,23 @@ public function testAnIncompleteGameCanNotBeScored(): void $this->game->score(); } - public function testAGameWithMoreThanTenFramesCanNotBeScored(): void + /** + * uuid 2ccb8980-1b37-4988-b7d1-e5701c317df3 + * @testdox cannot roll if game already has ten frames + */ + public function testCannotRollIfGameAlreadyHasTenFrames(): void { $this->expectException(Exception::class); - $this->rollMany(21, 0); + $this->rollMany(20, 0); + $this->game->roll(0); $this->game->score(); } + /** + * uuid 4864f09b-9df3-4b65-9924-c595ed236f1b + * @testdox bonus rolls for a strike in the last frame must be rolled before score can be calculated + */ public function testBonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScoreCanBeCalculated(): void { $this->expectException(Exception::class); @@ -265,6 +407,10 @@ public function testBonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScoreCan $this->game->score(); } + /** + * uuid 537f4e37-4b51-4d1c-97e2-986eb37b2ac1 + * @testdox both bonus rolls for a strike in the last frame must be rolled before score can be calculated + */ public function testBothBonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScoreCanBeCalculated(): void { $this->expectException(Exception::class); @@ -275,6 +421,10 @@ public function testBothBonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScor $this->game->score(); } + /** + * uuid 8134e8c1-4201-4197-bf9f-1431afcde4b9 + * @testdox bonus roll for a spare in the last frame must be rolled before score can be calculated + */ public function testBonusRollForASpareInTheLastFrameMustBeRolledBeforeScoreCanBeCalculated(): void { $this->expectException(Exception::class); @@ -285,6 +435,38 @@ public function testBonusRollForASpareInTheLastFrameMustBeRolledBeforeScoreCanBe $this->game->score(); } + /** + * uuid 9d4a9a55-134a-4bad-bae8-3babf84bd570 + * @testdox cannot roll after bonus roll for spare + */ + public function testCannotRollAfterBonusRollForSpare(): void + { + $this->expectException(Exception::class); + $this->rollMany(18, 0); + $this->game->roll(7); + $this->game->roll(3); + $this->game->roll(2); + $this->game->roll(2); + + $this->game->score(); + } + + /** + * uuid d3e02652-a799-4ae3-b53b-68582cc604be + * @testdox cannot roll after bonus rolls for strike + */ + public function testCannotRollAfterBonusRollsForStrike(): void + { + $this->expectException(Exception::class); + $this->rollMany(18, 0); + $this->game->roll(10); + $this->game->roll(3); + $this->game->roll(2); + $this->game->roll(2); + + $this->game->score(); + } + private function rollStrike(): void { $this->game->roll(10);