Advertisement
iKrans

K.Berzins Bowl

May 18th, 2020
1,396
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 6.55 KB | None | 0 0
  1. class BowlingGame
  2. {
  3.     private array $rolls = [];
  4.  
  5.     public function roll(int $count): void
  6.     {
  7.         $this->rolls[] = $count;
  8.     }
  9.    
  10.     public function getScore()
  11.     {
  12.         $score = 0;
  13.         $roll = 0;
  14.  
  15.         $this->validateRollCount();
  16.  
  17.         $strikeCount = 0;
  18.         $hitLastFrame = false;
  19.  
  20.         for ($frame = 0; $frame < 10; $frame++) {
  21.             // Because we stop at 10, therefore 9 is last
  22.             if ($frame == 9) {
  23.                 $hitLastFrame = true;
  24.             }
  25.  
  26.             if ($this->isStrike($roll)) {
  27.                 ++$strikeCount;
  28.                 $score += $this->getStrikeScore($roll);
  29.                 ++$roll;
  30.                 continue;
  31.             }
  32.  
  33.             if ($this->isSpare($roll)) {
  34.                 $score += $this->getSpareBonus($roll);
  35.             }
  36.  
  37.             $score += $this->getNormalScore($roll);
  38.             $roll += 2;
  39.         }
  40.  
  41.         $this->validateRollInterval($strikeCount, $hitLastFrame);
  42.  
  43.         return $score;
  44.     }
  45.  
  46.     /**
  47.      * @param int $roll
  48.      * @return int
  49.      */
  50.     private function getNormalScore(int $roll): int
  51.     {
  52.         return $this->rolls[$roll] + $this->rolls[$roll + 1];
  53.     }
  54.  
  55.     /**
  56.      * @param int $roll
  57.      * @return bool
  58.      */
  59.     private function isSpare(int $roll): bool
  60.     {
  61.         return $this->getNormalScore($roll) === 10;
  62.     }
  63.  
  64.     /**
  65.      * @param int $roll
  66.      * @return int
  67.      */
  68.     private function getSpareBonus(int $roll): int
  69.     {
  70.         return $this->rolls[$roll + 2];
  71.     }
  72.  
  73.     private function isStrike(int $roll): bool
  74.     {
  75.         return $this->rolls[$roll] === 10;
  76.     }
  77.  
  78.     private function getStrikeScore(int $roll)
  79.     {
  80.         return $this->rolls[$roll + 1] + $this->rolls[$roll + 2] + 10;
  81.     }
  82.  
  83.     private function validateRollCount()
  84.     {
  85.         $rollCount = count($this->rolls);
  86.         // Too much rolls
  87.         if ($rollCount > 20) {
  88.             throw new \Exception('Max possible roll count per game exceeded.');
  89.         }
  90.  
  91.         // Too few rolls
  92.         if ($rollCount < 12) {
  93.             throw new \Exception('Game is not finished yet.');
  94.         }
  95.  
  96.         return true;
  97.     }
  98.  
  99.     private function validateRollInterval($strikeCount, $hitLastFrame)
  100.     {
  101.         $rollCount = count($this->rolls);
  102.         // depending on how many strikes we can calculate approx interval in which game should be settled
  103.         // therefore I have some algorithms for any further classes so they are more exciting
  104.         // if I dont calculate scores I can assume interval in which score should be known, if roll count is not withing
  105.         // this interval well, game is not finished
  106.         // 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
  107.         // unless it is last frame that's been a strike
  108.         $intervalMin = 12 - $strikeCount;
  109.         // Why -2? Because count does not include index 0
  110.         $oneBeforeLast = $this->rolls[$rollCount - 2];
  111.         $twoBeforeLast = $this->rolls[$rollCount - 3];
  112.  
  113.         $intervalMax = 20 - $strikeCount;
  114.         if ($hitLastFrame && ($oneBeforeLast == 10 || ($oneBeforeLast + $twoBeforeLast) == 10)) {
  115.             // if one of the last rolls were strike or sum of 10, we get additional roll
  116.             $intervalMax = $intervalMax + 2;
  117.         }
  118.  
  119.         if ($rollCount < $intervalMin || $rollCount > $intervalMax) {
  120.             throw new \Exception('Insufficient roll count');
  121.         }
  122.     }
  123. }
  124.  
  125. class BowlingGameTest extends \PHPUnit\Framework\TestCase
  126. {
  127.     // roll || int count
  128.     // getScore || return int
  129.  
  130.     public function testGetScore_withZeros_scoreZero()
  131.     {
  132.         // setup
  133.         $game = new BowlingGame();
  134.  
  135.         for ($i = 0; $i < 20; $i++) {
  136.             $game->roll(0);
  137.         }
  138.  
  139.         // test
  140.         $score = $game->getScore();
  141.  
  142.         // assert
  143.         self::assertEquals(0, $score);
  144.     }
  145.  
  146.     public function testGetScore_withOnes_scoreTwenty()
  147.     {
  148.         $game = new BowlingGame();
  149.  
  150.         for ($i = 0; $i < 20; $i++) {
  151.             $game->roll(1);
  152.         }
  153.  
  154.         // test
  155.         $score = $game->getScore();
  156.  
  157.         // assert
  158.         self::assertEquals(20, $score);
  159.     }
  160.  
  161.     public function testGetScore_withASpare_willAddSpareBonus()
  162.     {
  163.         $game = new BowlingGame();
  164.  
  165.         $game->roll(8);
  166.         $game->roll(2);
  167.         $game->roll(5);
  168.         // 17 + 8 + 2 + 5 + 5 (spare bonus) = 37
  169.         for ($i = 0; $i < 17; $i++) {
  170.             $game->roll(1);
  171.         }
  172.  
  173.         // test
  174.         $score = $game->getScore();
  175.  
  176.         // assert
  177.         self::assertEquals(37, $score);
  178.     }
  179.  
  180.     public function testGetScore_withAStrike_willAddStrikeBonus()
  181.     {
  182.         $game = new BowlingGame();
  183.  
  184.         $game->roll(10);
  185.         $game->roll(5);
  186.         $game->roll(3);
  187.         // 17 + 10 + 5 + 3 + 5 + 3 = 43
  188.         for ($i = 0; $i < 16; $i++) {
  189.             $game->roll(1);
  190.         }
  191.  
  192.         // test
  193.         $score = $game->getScore();
  194.  
  195.         // assert
  196.         self::assertEquals(42, $score);
  197.     }
  198.  
  199.     public function testGetScore_withPerfectGame_willReturn300()
  200.     {
  201.         $game = new BowlingGame();
  202.  
  203.         // 300
  204.         for ($i = 0; $i < 12; $i++) {
  205.             $game->roll(10);
  206.         }
  207.  
  208.         // test
  209.         $score = $game->getScore();
  210.  
  211.         // assert
  212.         self::assertEquals(300, $score);
  213.     }
  214.  
  215.     public function testGetScore_gameNotFinished_willReturnException()
  216.     {
  217.         $game = new BowlingGame();
  218.  
  219.         for ($i = 0; $i < 10; $i++) {
  220.             $game->roll(5);
  221.         }
  222.  
  223.         // test
  224.         $this->expectException(Exception::class);
  225.         $game->getScore();
  226.     }
  227.  
  228.     public function testGetScore_rollsExceedLimit_willReturnException()
  229.     {
  230.         $game = new BowlingGame();
  231.  
  232.         for ($i = 0; $i < 21; $i++) {
  233.             $game->roll(5);
  234.         }
  235.  
  236.         // test
  237.         $this->expectException(Exception::class);
  238.         $game->getScore();
  239.     }
  240.  
  241.     public function testGetScore_gameAlmostFinishedAndExceedTwelveRowsYetIsBelowMaxCountTwentyRolls_willReturnException()
  242.     {
  243.         $game = new BowlingGame();
  244.  
  245.         $game->roll(10);
  246.  
  247.         for ($i = 0; $i < 19; $i++) {
  248.             $game->roll(4);
  249.         }
  250.         // Total 1 strike, which deducts from max and min roll count therefore 11 and 18
  251.         // therefore acceptable interval is between 11 and 18 with having a strike
  252.  
  253.         // test
  254.         $this->expectException(Exception::class);
  255.         $game->getScore();
  256.     }
  257. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement