<?php declare(strict_types=1); namespace App; use Assert\Assert; use Tightenco\Collect\Support\Collection; final class BowlingGame { private const MAX_PINS_PER_FRAME = 10; private const NUMBER_OF_FRAMES = 10; private Collection $rolls; public function __construct() { $this->rolls = new Collection(); } public function roll(int $pins): void { Assert::that($pins) ->greaterOrEqualThan(0, 'You cannot knock down a negative number of pins. Knocked down %d.') ->lessOrEqualThan(self::MAX_PINS_PER_FRAME, 'You can only knock down a maximum of 10 pins in a roll. Knocked down 12.'); $this->rolls->push($pins); } public function getScore(): int { $score = 0; $roll = 0; foreach (range(1, self::NUMBER_OF_FRAMES) as $frame) { if ($this->isStrike($roll)) { $score += $this->rolls[$roll]; $score += $this->bonusForStrike($roll); $roll += 1; continue; } if ($this->isSpare($roll)) { $score += $this->defaultFrameScore($roll); $score += $this->bonusForSpare($roll); $roll += 2; continue; } $score += $this->defaultFrameScore($roll); $roll += 2; } return $score; } private function bonusForSpare(int $roll): int { return $this->rolls->get($roll + 2); } private function bonusForStrike(int $roll): int { return $this->rolls[$roll + 1] + $this->rolls[$roll + 2]; } private function defaultFrameScore(int $roll): int { return $this->rolls[$roll] + $this->rolls[$roll + 1]; } private function isSpare(int $roll): bool { return $this->defaultFrameScore($roll) == self::MAX_PINS_PER_FRAME; } private function isStrike(int $roll): bool { return $this->rolls[$roll] == self::MAX_PINS_PER_FRAME; } }