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