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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.