Bowling
This commit is contained in:
parent
7887455c41
commit
ae5f58c8c7
|
@ -6,6 +6,7 @@ Includes:
|
||||||
|
|
||||||
- **Anagrams**: select an anagram for a word from a list of condidates.
|
- **Anagrams**: select an anagram for a word from a list of condidates.
|
||||||
- **Bob**: returns different responses based on input.
|
- **Bob**: returns different responses based on input.
|
||||||
|
- **Bowling**: calculate the score for a game of bowling.
|
||||||
- **Grade School**: given students' names along with the grade that they are in, create a roster for the school.
|
- **Grade School**: given students' names along with the grade that they are in, create a roster for the school.
|
||||||
- **Roman numerals**: convert a number into its roman numeral value.
|
- **Roman numerals**: convert a number into its roman numeral value.
|
||||||
|
|
||||||
|
|
88
src/BowlingGame.php
Normal file
88
src/BowlingGame.php
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
134
tests/BowlingGameTest.php
Normal file
134
tests/BowlingGameTest.php
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\BowlingGame;
|
||||||
|
use Assert\Assert;
|
||||||
|
use Assert\AssertionFailedException;
|
||||||
|
|
||||||
|
beforeEach(function (): void {
|
||||||
|
$this->game = new BowlingGame();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts an empty game', function (): void {
|
||||||
|
foreach (range(1, 20) as $roll) {
|
||||||
|
$this->game->roll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSame(0, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts all single pins', function (): void {
|
||||||
|
foreach (range(1, 20) as $roll) {
|
||||||
|
$this->game->roll(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSame(20, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds a roll one bonus for a spare', function (): void {
|
||||||
|
$this->game->roll(5);
|
||||||
|
$this->game->roll(5); // Spare
|
||||||
|
|
||||||
|
$this->game->roll(2);
|
||||||
|
|
||||||
|
foreach (range(1, 17) as $roll) {
|
||||||
|
$this->game->roll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSame(14, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds a two roll bonus for a strike', function (): void {
|
||||||
|
$this->game->roll(10); // Strike
|
||||||
|
|
||||||
|
$this->game->roll(3);
|
||||||
|
$this->game->roll(6);
|
||||||
|
|
||||||
|
foreach (range(1, 16) as $roll) {
|
||||||
|
$this->game->roll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSame(28, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds an extra ball for a spare on the final frame', function (): void {
|
||||||
|
foreach (range(1, 18) as $roll) {
|
||||||
|
$this->game->roll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->game->roll(7);
|
||||||
|
$this->game->roll(3); // Spare
|
||||||
|
|
||||||
|
$this->game->roll(5);
|
||||||
|
|
||||||
|
assertSame(15, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds an two extra balls for a strike on the final frame', function (): void {
|
||||||
|
foreach (range(1, 18) as $roll) {
|
||||||
|
$this->game->roll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->game->roll(10); // Strike
|
||||||
|
|
||||||
|
$this->game->roll(4);
|
||||||
|
$this->game->roll(2);
|
||||||
|
|
||||||
|
assertSame(16, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scores a perfect game', function (): void {
|
||||||
|
foreach (range(1, 12) as $roll) {
|
||||||
|
$this->game->roll(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSame(300, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scores a normal game', function (): void {
|
||||||
|
$this->game->roll(4);
|
||||||
|
$this->game->roll(3);
|
||||||
|
|
||||||
|
$this->game->roll(10);
|
||||||
|
|
||||||
|
$this->game->roll(4);
|
||||||
|
$this->game->roll(1);
|
||||||
|
|
||||||
|
$this->game->roll(0);
|
||||||
|
$this->game->roll(2);
|
||||||
|
|
||||||
|
$this->game->roll(3);
|
||||||
|
$this->game->roll(7);
|
||||||
|
|
||||||
|
$this->game->roll(8);
|
||||||
|
$this->game->roll(1);
|
||||||
|
|
||||||
|
$this->game->roll(10);
|
||||||
|
|
||||||
|
$this->game->roll(0);
|
||||||
|
$this->game->roll(7);
|
||||||
|
|
||||||
|
$this->game->roll(1);
|
||||||
|
$this->game->roll(5);
|
||||||
|
|
||||||
|
$this->game->roll(10);
|
||||||
|
$this->game->roll(6);
|
||||||
|
$this->game->roll(1);
|
||||||
|
|
||||||
|
assertSame(103, $this->game->getScore());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot knock down a negative pins', function (): void {
|
||||||
|
$this->game->roll(-1);
|
||||||
|
})->throws(
|
||||||
|
AssertionFailedException::class,
|
||||||
|
'You cannot knock down a negative number of pins. Knocked down -1.'
|
||||||
|
);
|
||||||
|
|
||||||
|
it('cannot knock down more than 10 pins in a roll', function (): void {
|
||||||
|
$this->game->roll(12);
|
||||||
|
})->throws(
|
||||||
|
AssertionFailedException::class,
|
||||||
|
'You can only knock down a maximum of 10 pins in a roll. Knocked down 12.'
|
||||||
|
);
|
Loading…
Reference in a new issue