Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- class BowlingGame
- {
- private array $rolls = [];
- public function roll(int $count): void
- {
- $this->rolls[] = $count;
- }
- public function getScore()
- {
- $score = 0;
- $roll = 0;
- $this->validateRollCount();
- $strikeCount = 0;
- $hitLastFrame = false;
- for ($frame = 0; $frame < 10; $frame++) {
- // Because we stop at 10, therefore 9 is last
- if ($frame == 9) {
- $hitLastFrame = true;
- }
- if ($this->isStrike($roll)) {
- ++$strikeCount;
- $score += $this->getStrikeScore($roll);
- ++$roll;
- continue;
- }
- if ($this->isSpare($roll)) {
- $score += $this->getSpareBonus($roll);
- }
- $score += $this->getNormalScore($roll);
- $roll += 2;
- }
- $this->validateRollInterval($strikeCount, $hitLastFrame);
- return $score;
- }
- /**
- * @param int $roll
- * @return int
- */
- private function getNormalScore(int $roll): int
- {
- return $this->rolls[$roll] + $this->rolls[$roll + 1];
- }
- /**
- * @param int $roll
- * @return bool
- */
- private function isSpare(int $roll): bool
- {
- return $this->getNormalScore($roll) === 10;
- }
- /**
- * @param int $roll
- * @return int
- */
- private function getSpareBonus(int $roll): int
- {
- return $this->rolls[$roll + 2];
- }
- private function isStrike(int $roll): bool
- {
- return $this->rolls[$roll] === 10;
- }
- private function getStrikeScore(int $roll)
- {
- return $this->rolls[$roll + 1] + $this->rolls[$roll + 2] + 10;
- }
- private function validateRollCount()
- {
- $rollCount = count($this->rolls);
- // Too much rolls
- if ($rollCount > 20) {
- throw new \Exception('Max possible roll count per game exceeded.');
- }
- // Too few rolls
- if ($rollCount < 12) {
- throw new \Exception('Game is not finished yet.');
- }
- return true;
- }
- private function validateRollInterval($strikeCount, $hitLastFrame)
- {
- $rollCount = count($this->rolls);
- // depending on how many strikes we can calculate approx interval in which game should be settled
- // therefore I have some algorithms for any further classes so they are more exciting
- // if I dont calculate scores I can assume interval in which score should be known, if roll count is not withing
- // this interval well, game is not finished
- // e.g. min game count is 12, max is 20, if I do a strike, min count decreases by 1, max count decreases by 2
- // unless it is last frame that's been a strike
- $intervalMin = 12 - $strikeCount;
- // Why -2? Because count does not include index 0
- $oneBeforeLast = $this->rolls[$rollCount - 2];
- $twoBeforeLast = $this->rolls[$rollCount - 3];
- $intervalMax = 20 - $strikeCount;
- if ($hitLastFrame && ($oneBeforeLast == 10 || ($oneBeforeLast + $twoBeforeLast) == 10)) {
- // if one of the last rolls were strike or sum of 10, we get additional roll
- $intervalMax = $intervalMax + 2;
- }
- if ($rollCount < $intervalMin || $rollCount > $intervalMax) {
- throw new \Exception('Insufficient roll count');
- }
- }
- }
- class BowlingGameTest extends \PHPUnit\Framework\TestCase
- {
- // roll || int count
- // getScore || return int
- public function testGetScore_withZeros_scoreZero()
- {
- // setup
- $game = new BowlingGame();
- for ($i = 0; $i < 20; $i++) {
- $game->roll(0);
- }
- // test
- $score = $game->getScore();
- // assert
- self::assertEquals(0, $score);
- }
- public function testGetScore_withOnes_scoreTwenty()
- {
- $game = new BowlingGame();
- for ($i = 0; $i < 20; $i++) {
- $game->roll(1);
- }
- // test
- $score = $game->getScore();
- // assert
- self::assertEquals(20, $score);
- }
- public function testGetScore_withASpare_willAddSpareBonus()
- {
- $game = new BowlingGame();
- $game->roll(8);
- $game->roll(2);
- $game->roll(5);
- // 17 + 8 + 2 + 5 + 5 (spare bonus) = 37
- for ($i = 0; $i < 17; $i++) {
- $game->roll(1);
- }
- // test
- $score = $game->getScore();
- // assert
- self::assertEquals(37, $score);
- }
- public function testGetScore_withAStrike_willAddStrikeBonus()
- {
- $game = new BowlingGame();
- $game->roll(10);
- $game->roll(5);
- $game->roll(3);
- // 17 + 10 + 5 + 3 + 5 + 3 = 43
- for ($i = 0; $i < 16; $i++) {
- $game->roll(1);
- }
- // test
- $score = $game->getScore();
- // assert
- self::assertEquals(42, $score);
- }
- public function testGetScore_withPerfectGame_willReturn300()
- {
- $game = new BowlingGame();
- // 300
- for ($i = 0; $i < 12; $i++) {
- $game->roll(10);
- }
- // test
- $score = $game->getScore();
- // assert
- self::assertEquals(300, $score);
- }
- public function testGetScore_gameNotFinished_willReturnException()
- {
- $game = new BowlingGame();
- for ($i = 0; $i < 10; $i++) {
- $game->roll(5);
- }
- // test
- $this->expectException(Exception::class);
- $game->getScore();
- }
- public function testGetScore_rollsExceedLimit_willReturnException()
- {
- $game = new BowlingGame();
- for ($i = 0; $i < 21; $i++) {
- $game->roll(5);
- }
- // test
- $this->expectException(Exception::class);
- $game->getScore();
- }
- public function testGetScore_gameAlmostFinishedAndExceedTwelveRowsYetIsBelowMaxCountTwentyRolls_willReturnException()
- {
- $game = new BowlingGame();
- $game->roll(10);
- for ($i = 0; $i < 19; $i++) {
- $game->roll(4);
- }
- // Total 1 strike, which deducts from max and min roll count therefore 11 and 18
- // therefore acceptable interval is between 11 and 18 with having a strike
- // test
- $this->expectException(Exception::class);
- $game->getScore();
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement