Code Katas - Notes

Make folder called Katas, inside make directory called src & tests then run

composer require phpunit/phpunit
vendor/bin/phpunit --generate-configuration

Troubleshoot

This test does not have a @covers annotation but is expected to have one
/** 
 * 
 * @test 
 * @covers \\src\\RomanNumerals -> find the one that it refers
	
 */
/**
* @dataProvider checks -> get the function checks
*/

Prime Numbers

<?php
namespace App;

class PrimeFactors {
    public function generate($number) {
        $factors = [];
        for ($divisor = 2; $number > 1;$divisor++ ) {
            for (;$number % $divisor === 0; $number /= $divisor) {
                $factors[] = $divisor;
            }
        }
        return $factors;
    }
}

<?php
use PHPUnit\\Framework\\TestCase;

class PrimeFactorsTest extends TestCase
{
    /** 
     * 
     * @test 
     * @dataProvider factors
     */
    function it_generate_prime_factors_for_1($number, $expected)
    {
        $factors = new \\App\\PrimeFactors;

        $this->assertEquals($expected, $factors->generate($number));
    }

    public function factors() {
        return [
            [1, []],
            [2, [2]],
            [3, [3]],
            [4, [2,2]],
            [5, [5]],
            [6, [2,3]],
            [8, [2,2,2]],
            [100, [2,2,5,5]],
            [999, [3,3,3,37]],
        ];
    }
}

Roman Numerals

<?php
use App\\RomanNumerals;
use PHPUnit\\Framework\\TestCase;

class RomanNumeralsTest extends TestCase
{
    /** 
     * 
     * @test 
     * @covers \\src\\RomanNumerals
     * @dataProvider checks
     */
    function it_generates_the_roman_numeral_for_1($number, $numeral)
    {
        $this->assertEquals($numeral, RomanNumerals::generate($number));
    }

    /** 
     * 
     * @test 
     * @covers \\src\\RomanNumerals
     */
    function it_cannot_generate_a_roman_numeral()
    {
        $this->assertFalse(RomanNumerals::generate(0));
    }

    public function checks() {
        return [
            [1, 'I'],
            [2, 'II'],
            [3, 'III'],
            [4, 'IV'],
            [5, 'V'],
            [6, 'VI'],
            [7, 'VII'],
            [8, 'VIII'],
            [9, 'IX'],
            [10, 'X'],
            [40, 'XL'],
            [50, 'L'],
            [90, 'XC'],
            [100, 'C'],
            [500, 'D'],
            [400, 'CD'],
            [900, 'CM'],
            [1000, 'M'],
        ];
    }
}

<?php

namespace App;

class RomanNumerals {
    const NUMERALS = [
        'M' => 1000,
        'CM' => 900,
        'D' => 500,
        'CD' => 400,
        'C' => 100,
        'XC' => 90,
        'L' => 50,
        'XL' => 40,
        'X' => 10,
        'IX' => 9,
        'V' => 5,
        'IV' => 4,
        'I' => 1
    ];
    public static function generate($number) {
        if ($number <= 0) {
            return false;
        }
        $result = '';
        
        foreach (static::NUMERALS as $numeral => $arabic) {
            for(;$number >= $arabic; $number -= $arabic) {
                $result .= $numeral;
            }
        }
        return $result;
    }
}

BowlingGame

<?php

namespace App;

class GameBowling {
    const FRAMES_PER_GAME = 10;
    protected array $rolls = [];

    public function roll(int $pins) {
        $this->rolls[] = $pins;
    }

    protected function isStrike($roll) {
        return $this->pinCount($roll)==10;
    }
    protected function isSpare($roll) {
        return $this->defaultFrameScore($roll) == 10;
    }
    protected function defaultFrameScore(int $roll): int {
        return $this->pinCount($roll) + $this->pinCount($roll+1);
    }
    protected function strikeBonus(int $roll): int {
        return $this->pinCount($roll + 1) + $this->pinCount($roll + 2);
    }
    protected function spareBonus(int $roll): mixed {
        return $this->pinCount($roll + 2);
    }
    protected function pinCount(int $roll): int {
        return $this->rolls[$roll];
    }

    public function score() {
        $score = 0;
        $roll = 0;
        foreach (range(1, self::FRAMES_PER_GAME) as $frame) {

            if ($this->isStrike($roll)) {
                $score += $this->pinCount($roll) + $this->strikeBonus($roll);
                $roll += 1;
                continue;
            }
            
            $score += $this->defaultFrameScore($roll);

            if ($this->isSpare($roll)) {
                $score += $this->spareBonus($roll);
                $roll += 2;
                continue;
            }
            
            $roll += 2;
            
        }
        return $score;
    }
}

<?php
use App\\GameBowling;
use PHPUnit\\Framework\\TestCase;

class GameBowlingTest extends TestCase
{
    /** 
     * 
     * @test 
     * @covers \\src\\GameBowling
     */
    function it_scores_a_gutter_game_as_zero() {
        $game = new GameBowling();
        foreach (range(1,20) as $roll) {
            $game->roll(0);
        }
        $this->assertSame(0, $game->score());
    }
    /** 
     * 
     * @test 
     * @covers \\src\\GameBowling
     */
    function it_scores_all_ones()
    {
        $game = new GameBowling();

        foreach (range(1, 20) as $roll) {
            $game->roll(1);
        }

        $this->assertSame(20, $game->score());
    }
    /** 
     * 
     * @test 
     * @covers \\src\\GameBowling
     */
    function it_awards_a_one_roll_bonus_for_every_spare() {
        $game = new GameBowling();

        $game->roll(5);
        $game->roll(5);

        $game->roll(8);

        foreach (range(1,17) as $roll) {
            $game->roll(0);
        }
        $this->assertSame(26, $game->score());
    }

    /** 
     * 
     * @test 
     * @covers \\src\\GameBowling
     */
    function it_awards_a_two_roll_bonus_for_every_strike() {
        $game = new GameBowling();

        $game->roll(10);
        $game->roll(5);
        $game->roll(2);

        foreach (range(1,16) as $roll) {
            $game->roll(0);
        }
        $this->assertSame(24, $game->score());
    }

    /** 
     * 
     * @test 
     * @covers \\src\\GameBowling
     */
    function a_spare_on_the_final_frame_grants_one_extra_balls() {
        $game = new GameBowling();

        foreach (range(1,18) as $roll) {
            $game->roll(0);
        }
        $game->roll(5);
        $game->roll(5);
        $game->roll(5);
        $this->assertSame(15, $game->score());
    }

    /** 
     * 
     * @test 
     * @covers \\src\\GameBowling
     */
    function a_strike_on_the_final_frame_grants_two_extra_balls() {
        $game = new GameBowling();

        foreach (range(1,18) as $roll) {
            $game->roll(0);
        }
        $game->roll(10);
        $game->roll(10);
        $game->roll(10);
        $this->assertSame(30, $game->score());
    }

    /** 
     * 
     * @test 
     * @covers \\src\\GameBowling
     */
    function it_scores_a_perfect_game() {
        $game = new GameBowling();

        foreach (range(1,12) as $roll) {
            $game->roll(10);
        }
        $this->assertSame(300, $game->score());
    }
}

String Calculator Kata

<?php
use App\\StringCalculator;
use PHPUnit\\Framework\\TestCase;

class StringCalculatorTest extends TestCase
{
    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function it_evaluates_an_empty_string_as_0() {
        $calculator = new StringCalculator;
        $this->assertEquals(0, $calculator->add(''));
    }

    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function it_finds_the_sum_of_a_single_number() {
        $calculator = new StringCalculator;
        $this->assertEquals(5, $calculator->add('5'));
    }
    
    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function it_finds_the_sum_of_two_numbers() {
        $calculator = new StringCalculator;
        $this->assertEquals(10, $calculator->add('5,5'));
    }

    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function it_finds_the_sum_of_any_amount_of_numbers() {
        $calculator = new StringCalculator;
        $this->assertEquals(19, $calculator->add('5,5,5,4'));
    }

    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function it_accept_a_new_line_character_as_a_delimiter_too() {
        $calculator = new StringCalculator;
        $this->assertEquals(10, $calculator->add("5\\n5"));
    }

    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function negative_numbers_are_not_allowed() {
        $calculator = new StringCalculator;
        $this->expectException(\\Exception::class);
        $this->assertEquals(10, $calculator->add("5,-4"));
    }

    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function number_greater_than_1000_are_ignored() {
        $calculator = new StringCalculator;
        $this->assertEquals(5, $calculator->add("5,1001"));
    }

    /** 
     * 
     * @test 
     * @covers \\src\\StringCalculator
     */
    function it_supports_custom_delimiters() {
        $calculator = new StringCalculator;
        $this->assertEquals(20, $calculator->add("//:\\n5:4:11"));
    }

}

<?php

namespace App;

class StringCalculator {

    const MAX_NUMBER_ALLOWED = 1000;
    protected string $delimiter = ",|\\n";

    public function add(string $numbers) {
        $this->disallowNegatives($numbers = $this->parseString($numbers));
        return array_sum($this
            ->ignoreGreaterThan1000($numbers)
        );
    }

    protected function parseString(string $numbers) {
        $customDelimiter = '\\/\\/(.)\\n';
        if (preg_match("/{$customDelimiter}/",$numbers, $matches)) {
            $this->delimiter = $matches[1];
            $numbers = str_replace($matches[0], '', $numbers);
        }
        
        return preg_split("/{$this->delimiter}/", $numbers);
    }

    protected function disallowNegatives(array $numbers): void {
        foreach ($numbers as $number) {
            if (!empty($number) && $number < 0) {
                throw new \\Exception('Negative numbers are disallowed');
            }
        }
    }
    
    protected function ignoreGreaterThan1000(array $numbers): array {
        return array_filter(
            $numbers, fn($number) => $number <= self::MAX_NUMBER_ALLOWED
        );
    }

}

Tennis Match

<?php

namespace App;

class TennisMatch {

    protected Player $playerOne;
    protected Player $playerTwo;

    public function __construct(Player $playerOne, Player $playerTwo) {
        $this->playerOne = $playerOne;
        $this->playerTwo = $playerTwo;
    }

    public function score() {

        if ($this->hasWinner()) {
            return 'Winner: '.$this->leader()->name;
        }

        if ($this->hasAdvantage()) {
            return 'Advantage: ' . $this->leader()->name;
        }

        if ($this->isDeuce()) {
            return 'deuce';
        }

        return sprintf(
            '%s-%s',
            $this->playerOne->toTerm(),
            $this->playerTwo->toTerm(),
        );
    }
    
    protected function hasWinner(): bool {
        if (max([$this->playerOne->points, $this->playerTwo->points]) < 4) {
            return false;
        }
        return abs($this->playerOne->points - $this->playerTwo->points) >= 2 ;
    }

    protected function leader(): Player {
        return $this->playerOne->points > $this->playerTwo->points 
            ? $this->playerOne 
            : $this->playerTwo;
    }
    protected function isDeuce(): bool {
        return $this->canBeWon() && $this->playerOne->points === $this->playerTwo->points;
    }

    protected function hasAdvantage(): bool {
        if ($this->canBeWon()) {
            return ! $this->isDeuce();
        }
        return false;
    }

    protected function canBeWon(): bool {
        return $this->playerOne->points >= 3 && $this->playerTwo->points >= 3;
    }
}
<?php

namespace App;

class Player
{
    public string $name;
    public int $points = 0;

    public function __construct(string $name) {
        $this->name = $name;
    }

    public function score() {
        $this->points++;
    }

    public function toTerm() {
        switch($this->points) {
            case '0':
                return 'love';
            case '1':
                return 'fifteen';
            case '2':
                return 'thirty';
            case '3':
                return 'forty';
        }
    }
}
<?php

namespace App;

class TennisMatch {

    protected Player $playerOne;
    protected Player $playerTwo;

    public function __construct(Player $playerOne, Player $playerTwo) {
        $this->playerOne = $playerOne;
        $this->playerTwo = $playerTwo;
    }

    public function score() {

        if ($this->hasWinner()) {
            return 'Winner: '.$this->leader()->name;
        }

        if ($this->hasAdvantage()) {
            return 'Advantage: ' . $this->leader()->name;
        }

        if ($this->isDeuce()) {
            return 'deuce';
        }

        return sprintf(
            '%s-%s',
            $this->playerOne->toTerm(),
            $this->playerTwo->toTerm(),
        );
    }
    
    protected function hasWinner(): bool {
        if (max([$this->playerOne->points, $this->playerTwo->points]) < 4) {
            return false;
        }
        return abs($this->playerOne->points - $this->playerTwo->points) >= 2 ;
    }

    protected function leader(): Player {
        return $this->playerOne->points > $this->playerTwo->points 
            ? $this->playerOne 
            : $this->playerTwo;
    }
    protected function isDeuce(): bool {
        return $this->canBeWon() && $this->playerOne->points === $this->playerTwo->points;
    }

    protected function hasAdvantage(): bool {
        if ($this->canBeWon()) {
            return ! $this->isDeuce();
        }
        return false;
    }

    protected function canBeWon(): bool {
        return $this->playerOne->points >= 3 && $this->playerTwo->points >= 3;
    }
}

Gilded rose Kata (best)

https://github.com/laracasts/gilded-rose-with-phpunit

We’ll be refactoring this.

<?php

namespace App;

class GildedRose
{
    public $name;

    public $quality;

    public $sellIn;

    public function __construct($name, $quality, $sellIn)
    {
        $this->name = $name;
        $this->quality = $quality;
        $this->sellIn = $sellIn;
    }

    public static function of($name, $quality, $sellIn)
    {
        return new static($name, $quality, $sellIn);
    }

    public function tick()
    {
        if ($this->name != 'Aged Brie' and $this->name != 'Backstage passes to a TAFKAL80ETC concert') {
            if ($this->quality > 0) {
                if ($this->name != 'Sulfuras, Hand of Ragnaros') {
                    $this->quality = $this->quality - 1;
                }
            }
        } else {
            if ($this->quality < 50) {
                $this->quality = $this->quality + 1;

                if ($this->name == 'Backstage passes to a TAFKAL80ETC concert') {
                    if ($this->sellIn < 11) {
                        if ($this->quality < 50) {
                            $this->quality = $this->quality + 1;
                        }
                    }
                    if ($this->sellIn < 6) {
                        if ($this->quality < 50) {
                            $this->quality = $this->quality + 1;
                        }
                    }
                }
            }
        }

        if ($this->name != 'Sulfuras, Hand of Ragnaros') {
            $this->sellIn = $this->sellIn - 1;
        }

        if ($this->sellIn < 0) {
            if ($this->name != 'Aged Brie') {
                if ($this->name != 'Backstage passes to a TAFKAL80ETC concert') {
                    if ($this->quality > 0) {
                        if ($this->name != 'Sulfuras, Hand of Ragnaros') {
                            $this->quality = $this->quality - 1;
                        }
                    }
                } else {
                    $this->quality = $this->quality - $this->quality;
                }
            } else {
                if ($this->quality < 50) {
                    $this->quality = $this->quality + 1;
                }
            }
        }
    }
}

to

<?php

namespace App;

class GildedRose
{
    private static $items = [
        'normal' => Item::class,
        'Aged Brie' => Brie::class,
        'Sulfuras, Hand of Ragnaros' => Sulfuras::class,
        'Backstage passes to a TAFKAL80ETC concert' => BackstagePasses::class,
        'Conjured Mana Cake' => Conjured::class,
    ];

    public static function of($name, $quality, $sellIn)
    {
        if (! array_key_exists($name, self::$items)) {
            throw new \\InvalidArgumentException('Item type does not exist.');
        }
        $class = self::$items[$name];
        return new $class($quality, $sellIn);
    }
}
<?php

namespace App;

class Item {
    public $sellIn;
    public $quality;

    public function __construct($quality, $sellIn) {
        $this->sellIn = $sellIn;
        $this->quality = $quality;
    }

    public function tick() {
        $this->sellIn-= 1;
        $this->quality-= 1;

        if ($this->sellIn <= 0 && $this->quality > 0) {
            $this->quality -= 1;
        }

        if ($this->quality <= 0) {
            $this->quality = 0;
        }
    }
}
<?php

namespace App;

class Brie extends Item {
    public function tick() {
        $this->sellIn -= 1;
        $this->quality += 1;

        if ($this->sellIn <= 0) {
            $this->quality += 1;
        }
        if ($this->quality > 50) {
            $this->quality = 50;
        }
    }
}
<?php

namespace App;

class Sulfuras extends Item {
    public function tick() {
        
    }
}
<?php

namespace App;

class BackstagePasses extends Item {
    public function tick() {
        $this->quality += 1;

        if ($this->sellIn <= 10) {
            $this->quality += 1;
        }

        if ($this->sellIn <= 5) {
            $this->quality += 1;
        }
        if ($this->sellIn <= 0) {
            $this->quality = 0;
        }

        if ($this->quality > 50) {
            $this->quality = 50;
        }
        $this->sellIn-= 1;
    }
}
<?php

namespace App;

class Conjured extends Item {
    public function tick() {
        
        $this->sellIn-= 1;
        $this->quality-= 2;

        if ($this->sellIn <= 0 && $this->quality > 0) {
            $this->quality -= 2;
        }

        if ($this->quality <= 0) {
            $this->quality = 0;
        }
    }
}

99 Bottles Kata

<?php

namespace App;

class Song {
    public function sing() {
        return $this->verses(99,0);
    }

    public function verses($start, $end) {
        return implode("\\n", array_map(
            fn($number) => $this->verse($number), 
            range($start, $end)
        ));
    }

    public function verse($number) {

        switch ($number) {
            case 2:
                return 
                    "2 bottles of beer on the wall\\n".
                    "2 bottles of beer\\n".
                    "Take one down and pass it around\\n".
                    "1 bottle of beer on the wall\\n";
            case 1:
                return 
                    "1 bottle of beer on the wall\\n".
                    "1 bottle of beer\\n".
                    "Take one down and pass it around\\n".
                    "No more bottles of beer on the wall\\n";
            case 0:
                return 
                    "No more bottles of beer on the wall\\n".
                    "No more bottles of beer\\n".
                    "Go to the store and buy some more\\n".
                    "99 bottles of beer on the wall";
            default:
                return 
                    $number . " bottles of beer on the wall\\n".
                    $number . " bottles of beer\\n".
                    "Take one down and pass it around\\n".
                    $number - 1 . " bottles of beer on the wall\\n";
        }
    }
}
<?php
use App\\Song;
use PHPUnit\\Framework\\TestCase;

class SongTest extends TestCase
{

    /** 
     * 
     * @test 
     * @covers \\src\\Song
     */
    function ninety_nine_bottle_first() {
        $expected = <<<EOT
            99 bottles of beer on the wall
            99 bottles of beer
            Take one down and pass it around
            98 bottles of beer on the wall

            EOT;
        $result = (new Song)->verse(99);
        $this->assertEquals($expected, $result);
    }
     /** 
     * 
     * @test 
     * @covers \\src\\Song
     */
    function ninety_eight_bottle_verse() {
        $expected = <<<EOT
            98 bottles of beer on the wall
            98 bottles of beer
            Take one down and pass it around
            97 bottles of beer on the wall

            EOT;
        $result = (new Song)->verse(98);
        $this->assertEquals($expected, $result);
    }

     /** 
     * 
     * @test 
     * @covers \\src\\Song
     */
    function two_bottle_verse() {
        $expected = <<<EOT
            2 bottles of beer on the wall
            2 bottles of beer
            Take one down and pass it around
            1 bottle of beer on the wall

            EOT;
        $result = (new Song)->verse(2);
        $this->assertEquals($expected, $result);
    }

     /** 
     * 
     * @test 
     * @covers \\src\\Song
     */
    function one_bottle_verse() {
        $expected = <<<EOT
            1 bottle of beer on the wall
            1 bottle of beer
            Take one down and pass it around
            No more bottles of beer on the wall
            
            EOT;
        $result = (new Song)->verse(1);
        $this->assertEquals($expected, $result);
    }
/** 
     * 
     * @test 
     * @covers \\src\\Song
     */
    function no_more_bottle_verse() {
        $expected = <<<EOT
            No more bottles of beer on the wall
            No more bottles of beer
            Go to the store and buy some more
            99 bottles of beer on the wall
            EOT;
        $result = (new Song)->verse(0);
        $this->assertEquals($expected, $result);
    }

    /** 
     * 
     * @test 
     * @covers \\src\\Song
     */
    function it_gets_the_lyrics() {
        //$this->assertEquals(true, true);
        $expected = file_get_contents(__DIR__.'/stubs/lyrics.stub');
        $result = (new Song)->sing();

        $this->assertEquals($expected, $result);
    }
}

GitHub - madindo/laracast-code-katas
Contribute to madindo/laracast-code-katas development by creating an account on GitHub.