Natürlich möchte ich euch die Lösung, die ich zusammen mit meinen Azubis entworfen haben, nicht vorenthalten.
Natürlich aufgrund unseres TDD-Vorgehens Test First:
<?php declare(strict_types=1);
namespace Kata;
use Kata\Exception\NotInOurGardenException;
use PHPUnit\Framework\TestCase;
class EasterEggTest extends TestCase
{
/**
* @test
* @dataProvider createSampleGardens
* @throws NotInOurGardenException
*/
public function whereAreEasterEggs(
string $filename,
int $row, int $col,
string $expectedResult
): void
{
$easterEgg = new EasterEggs($filename);
self::assertSame($expectedResult,
$easterEgg->whereAreEasterEggs($row, $col)
);
}
public function createSampleGardens(): iterable
{
return [
'Alle Eier gefunden:' => [
'Resources/single_egg.txt',
1, 1,
'Das Kind hat alle 1 Eier in 1 Schritten gefunden.'
],
'Nicht alle Eier gefunden:' => [
'Resources/two_eggs.txt',
1, 1,
'Du hast ein Ei gefunden.'
],
'Ein Ei rechts:' => [
'Resources/one_egg_near_by.txt',
1, 1,
'Du hast 1 Eier in Deiner Nähe.'
],
'Zwei Eier - links und rechts:' => [
'Resources/two_eggs_near_by.txt',
1, 2,
'Du hast 2 Eier in Deiner Nähe.'
],
'Ein Ei über mir:' => [
'Resources/one_egg_above.txt',
2, 1,
'Du hast 1 Eier in Deiner Nähe.'
],
'Ein Ei schräg rechts und drunter:' => [
'Resources/multi_eggs_in_multi_rows.txt',
3, 1,
'Du hast 2 Eier in Deiner Nähe.'
],
'Eier überall:' => [
'Resources/eggs_all_around.txt',
2, 2,
'Du hast 8 Eier in Deiner Nähe.'
],
];
}
/**
* @throws NotInOurGardenException
*/
public function foundAllEggsAfterSeveralSteps()
{
$easteregg = new EasterEggs('Resources/mikes_garden.txt');
$easteregg->whereAreEasterEggs(1, 4);
$easteregg->whereAreEasterEggs(2, 2);
$easteregg->whereAreEasterEggs(4, 4);
self::assertSame(
'Das Kind hat alle 4 Eier in 4 Schritten gefunden.',
$easteregg->whereAreEasterEggs(4, 5)
);
}
/**
* @test
* @throws NotInOurGardenException
*/
public function outsideGarden(): void
{
$easteregg = new EasterEggs('Resources/mikes_garden.txt');
$this->expectException(NotInOurGardenException::class);
$easteregg->whereAreEasterEggs(-1, 3);
}
}
Daraus ergab sich dann step-by-step folgende Implementierung:
<?php
namespace Kata;
use Kata\Exception\NotInOurGardenException;
class EasterEggs
{
const UP = -1;
const DOWN = 1;
const RIGHT = 1;
const LEFT = -1;
private array $garden;
private int $eggsCount;
private int $eggsFound = 0;
private int $steps = 0;
public function __construct(string $filename)
{
$content = file_get_contents($filename);
$content = str_replace("\r", '', $content);
$this->eggsCount = substr_count($content, '*');
$gardenRows = explode(PHP_EOL, $content);
foreach ($gardenRows as $row) {
$this->garden[] = explode(' ', $row);
}
}
/**
* @throws NotInOurGardenException
*/
public function whereAreEasterEggs(int $row, int $column): string
{
list($currentCol, $currentRow) =
$this->validateUserInput($row, $column);
$this->countOneMoreStep();
if ($this->eggFound($currentRow, $currentCol)) {
$this->countOneMoreEggFound();
if ($this->allEggsFound($this->eggsFound)) {
$return = $this->finishedEggSearchStats();
} else {
$return = $this->eggFoundMessage();
}
} else {
$return
= $this->noEggFoundHint($currentRow, $currentCol);
}
return $return;
}
private function eggFound(int $row, int $column): bool
{
return $this->garden[$row][$column] === '*';
}
private function eggNearbyFound(
int $row, int $column,
int $rowDirection, int $columnDirection
): bool
{
if ($this->fieldDoesNotExist(
$row + $rowDirection,
$column + $columnDirection)
)
return false;
return $this->eggFound(
$row + $rowDirection,
$column + $columnDirection
);
}
private function allEggsFound(int $eggsFound): bool
{
return $eggsFound === $this->eggsCount;
}
private function lookAroundAndCount(int $row, int $column): int
{
$eggsNearBy = 0;
for ($rowDirection = self::UP;
$rowDirection <= self::DOWN;
$rowDirection++) {
for ($columnDirection = self::LEFT;
$columnDirection <= self::RIGHT;
$columnDirection++) {
if ($this->eggNearbyFound(
$row, $column,
$rowDirection, $columnDirection)
) {
$eggsNearBy++;
}
}
}
return $eggsNearBy;
}
private function fieldDoesNotExist(int $row, int $column): bool
{
if ($this->rowDoesNotExist($row)
|| $this->columnDoesNotExist($row, $column))
return true;
return false;
}
private function rowDoesNotExist(int $row): bool
{
return !array_key_exists($row, $this->garden);
}
private function columnDoesNotExist(int $row, int $column): bool
{
return !array_key_exists($column, $this->garden[$row]);
}
/**
* @throws NotInOurGardenException
*/
private function validateUserInput(int $row, int $column): array
{
$currentCol = $column - 1;
$currentRow = $row - 1;
if ($this->fieldDoesNotExist($currentRow, $currentCol)) {
throw new NotInOurGardenException(
'This is not in our garden.'
);
}
return array($currentCol, $currentRow);
}
private function countOneMoreStep(): void
{
$this->steps++;
}
private function countOneMoreEggFound(): void
{
$this->eggsFound++;
}
private function finishedEggSearchStats(): string
{
return 'Das Kind hat alle ' .
$this->eggsCount .
' Eier in ' .
$this->steps .
' Schritten gefunden.';
}
private function eggFoundMessage(): string
{
return 'Du hast ein Ei gefunden.';
}
private function noEggFoundHint(
$currentRow,
$currentCol
): string
{
return 'Du hast ' .
$this->lookAroundAndCount($currentRow, $currentCol) .
' Eier in Deiner Nähe.';
}
}
Ein Superbeispiel für gelungenes Single-Level-of-Abstraction Prinzip.
Frohe Ostern!!!
Coding Kata: Ostereier – Lösung