<repo_name>monitotxi/yahtzee_kata_round_1<file_sep>/src/YahtzeeFactory.php
<?php

namespace Yahtzee;

class YahtzeeFactory
{
    /**
     * @param ConsoleInterface   $console
     * @param DieRollerInterface $dieRoller
     *
     * @return Yahtzee
     */
    public static function makeYahtzee(ConsoleInterface $console, DieRollerInterface $dieRoller)
    {
        $consoleNotifier = new ConsoleNotifier($console);

        return new Yahtzee(
            self::makeCategories(),
            new Scores(),
            $consoleNotifier,
            self::makeRound($console, $consoleNotifier, $dieRoller)
        );
    }

    /**
     * @return array
     */
    private static function makeCategories()
    {
        $categoriesList = [
            1 => 'Ones',
            2 => 'Twos',
            3 => 'Threes',
        ];
        $categories = [];
        foreach ($categoriesList as $categoryValue => $categoryName) {
            $categories[] = new Category(new ScoreCalculator(), $categoryName, $categoryValue);
        }

        return $categories;
    }

    /**
     * @param ConsoleInterface   $console
     * @param ConsoleNotifier    $consoleNotifier
     * @param DieRollerInterface $dieRoller
     *
     * @return Round
     */
    private static function makeRound(
        ConsoleInterface $console,
        ConsoleNotifier $consoleNotifier,
        DieRollerInterface $dieRoller
    ) {
        return new Round(
            self::makeDice($dieRoller),
            new InputLine($console),
            $consoleNotifier
        );
    }

    /**
     * @param DieRollerInterface $dieRoller
     *
     * @return Dice
     */
    public static function makeDice(DieRollerInterface $dieRoller)
    {
        $dice = [];
        for ($i = 1; $i <= 5; ++$i) {
            $dice[$i] = new PlayerDie($dieRoller);
        }

        return new Dice($dice);
    }
}
<file_sep>/tests/acceptance/YahtzeeTest.php
<?php

use Yahtzee\DieRollerInterface;
use Yahtzee\YahtzeeFactory;

class YahtzeeTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function play()
    {
        $userInput = [
            'D1 D2 D4',
            'D2 D4',
            'D2 D5',
            'D3 D4 D5',
            'D1 D2 D3 D4 D5',
            'D1 D2 D4',
        ];
        $console = new FakeConsole($userInput);
        $dieRoller = $this->getMock(DieRollerInterface::class);
        $dieRoller->method('roll')->willReturnOnConsecutiveCalls(
            2, 4, 1, 6, 1,
            1, 5, 2,
            1, 5,
            2, 4, 1, 6, 1,
            2, 3,
            6, 1, 2,
            2, 4, 1, 6, 1,
            5, 1, 3, 2, 3,
            6, 2, 4
        );
        $yahtzee = YahtzeeFactory::makeYahtzee($console, $dieRoller);
        $yahtzee->play();
        $output = $console->getOutput();
        $expected = [
            'Category: Ones',
            'Dice: D1:2 D2:4 D3:1 D4:6 D5:1',
            '[1] Dice to re-run:',
            'Dice: D1:1 D2:5 D3:1 D4:2 D5:1',
            '[2] Dice to re-run:',
            'Dice: D1:1 D2:1 D3:1 D4:5 D5:1',
            'Category Ones score: 4',
            'Category: Twos',
            'Dice: D1:2 D2:4 D3:1 D4:6 D5:1',
            '[1] Dice to re-run:',
            'Dice: D1:2 D2:2 D3:1 D4:6 D5:3',
            '[2] Dice to re-run:',
            'Dice: D1:2 D2:2 D3:6 D4:1 D5:2',
            'Category Twos score: 3',
            'Category: Threes',
            'Dice: D1:2 D2:4 D3:1 D4:6 D5:1',
            '[1] Dice to re-run:',
            'Dice: D1:5 D2:1 D3:3 D4:2 D5:3',
            '[2] Dice to re-run:',
            'Dice: D1:6 D2:2 D3:3 D4:4 D5:3',
            'Category Threes score: 2',
            'Yahtzee score',
            'Ones: 4',
            'Twos: 3',
            'Threes: 2',
            'Final score: 9',
        ];
        $this->assertEquals($expected, $output);
    }
}
<file_sep>/tests/helpers/FakeConsole.php
<?php

use Yahtzee\Console;

class FakeConsole extends Console
{
    /**
     * @var array
     */
    private $output = [];
    /**
     * @var array
     */
    private $input;
    /**
     * @var int
     */
    private $position = 0;

    /**
     * FakeConsole constructor.
     *
     * @param array $input
     */
    public function __construct(array $input = [])
    {
        $this->input = $input;
    }

    /**
     * @param string $line
     */
    public function printLine($line)
    {
        $this->output[] = $line;
    }

    /**
     * @return array
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * @return string
     */
    public function readLine()
    {
        $line = $this->input[$this->position];
        ++$this->position;

        return $line;
    }
}
<file_sep>/src/ConsoleNotifier.php
<?php

namespace Yahtzee;

class ConsoleNotifier
{
    /**
     * @var ConsoleInterface
     */
    private $console;

    /**
     * ConsoleNotifier constructor.
     *
     * @param ConsoleInterface $console
     */
    public function __construct(ConsoleInterface $console)
    {
        $this->console = $console;
    }

    /**
     * @param array  $categories
     * @param Scores $scores
     */
    public function scores(array $categories, Scores $scores)
    {
        $this->printLine('Yahtzee score');
        $this->printCategoriesScores($categories, $scores);
        $this->printLine('Final score: '.$scores->total());
    }

    /**
     * @param $line
     */
    private function printLine($line)
    {
        $this->console->printLine($line);
    }

    /**
     * @param array  $categories
     * @param Scores $scores
     */
    private function printCategoriesScores(array $categories, Scores $scores)
    {
        $allScores = $scores->all();
        foreach ($categories as $category) {
            $this->printLine($category->getName().': '.$allScores[$category->getName()]);
        }
    }

    /**
     * @param $categoryName
     *
     * @return mixed
     */
    public function categoryName($categoryName)
    {
        $this->printLine('Category: '.$categoryName);
    }

    /**
     * @param Dice $dice
     */
    public function dice(Dice $dice)
    {
        $results = $dice->getResults();
        $line = 'Dice: ';
        foreach ($results as $key => $value) {
            $str = 'D'.$key.':'.$value.' ';
            $line .= $str;
        }
        $this->printLine(trim($line));
    }

    /**
     * @param $categoryName
     * @param $score
     */
    public function categoryScore($categoryName, $score)
    {
        $this->printLine('Category '.$categoryName.' score: '.$score);
    }

    /**
     * @param $time
     */
    public function diceReRun($time)
    {
        $this->printLine("[$time] Dice to re-run:");
    }
}
<file_sep>/play_yahtzee.php
<?php

require __DIR__.'/vendor/autoload.php';

use Yahtzee\DieRoller;
use Yahtzee\Console;
use Yahtzee\YahtzeeFactory;

$yahtzee = YahtzeeFactory::makeYahtzee(new Console(), new DieRoller());
$yahtzee->play();
<file_sep>/src/Round.php
<?php

namespace Yahtzee;

class Round
{
    /**
     * @var Dice
     */
    private $dice;
    /**
     * @var InputLine
     */
    private $inputLine;
    /**
     * @var ConsoleNotifier
     */
    private $consoleNotifier;

    /**
     * Round constructor.
     *
     * @param Dice            $dice
     * @param InputLine       $inputLine
     * @param ConsoleNotifier $consoleNotifier
     */
    public function __construct(Dice $dice, InputLine $inputLine, ConsoleNotifier $consoleNotifier)
    {
        $this->dice = $dice;
        $this->inputLine = $inputLine;
        $this->consoleNotifier = $consoleNotifier;
    }

    /**
     * @return Dice
     */
    public function run()
    {
        $this->dice->rollAll();
        $this->consoleNotifier->dice($this->dice);
        $this->reRun();

        return $this->dice;
    }

    private function reRun()
    {
        $i = 1;
        do {
            $diceToReRun = $this->getDiceToReRun($i);
            if ($diceToReRun) {
                $this->reRunDice($diceToReRun);
            }
            ++$i;
        } while ($diceToReRun && $i <= 2);
    }

    /**
     * @param $i
     *
     * @return array
     */
    private function getDiceToReRun($i)
    {
        $this->consoleNotifier->diceReRun($i);

        return $this->inputLine->readLine();
    }

    /**
     * @param $diceToReRoll
     */
    private function reRunDice($diceToReRoll)
    {
        $this->dice->roll($diceToReRoll);
        $this->consoleNotifier->dice($this->dice);
    }
}
<file_sep>/tests/unit/InputLineTest.php
<?php

use Yahtzee\ConsoleInterface;
use Yahtzee\InputLine;

class InputLineTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function read_user_input()
    {
        $console = $this->getMock(ConsoleInterface::class);
        $console->method('readLine')->willReturn('D2 D4');
        $inputLine = new InputLine($console);
        $dice = $inputLine->readLine();
        $this->assertEquals([2, 4], $dice);
    }

    /**
     * @test
     */
    public function read_empty_user_input()
    {
        $console = $this->getMock(ConsoleInterface::class);
        $console->method('readLine')->willReturn('');
        $inputLine = new InputLine($console);
        $dice = $inputLine->readLine();
        $this->assertEmpty($dice);
    }
}
<file_sep>/src/Console.php
<?php

namespace Yahtzee;

class Console implements ConsoleInterface
{
    /**
     * @param string $line
     */
    public function printLine($line)
    {
        echo $line."\n";
    }

    /**
     * @return string
     */
    public function readLine()
    {
        return readline();
    }
}
<file_sep>/tests/unit/ScoreCalculatorTest.php
<?php

use Yahtzee\Dice;
use Yahtzee\DieRollerInterface;
use Yahtzee\ScoreCalculator;
use Yahtzee\YahtzeeFactory;

class ScoreCalculatorTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function calculate_score_for_categories()
    {
        $scoreCalculator = new ScoreCalculator();
        $dice = $this->getDice();
        $scoreOnes = $scoreCalculator->calculate(1, $dice);
        $this->assertEquals(2, $scoreOnes);
        $scoreTwos = $scoreCalculator->calculate(2, $dice);
        $this->assertEquals(1, $scoreTwos);
    }

    /**
     * @return Dice
     */
    private function getDice()
    {
        $dieRoller = $this->getMock(DieRollerInterface::class);
        $dieRoller->method('roll')->willReturnOnConsecutiveCalls(2, 1, 5, 1, 3);
        $dice = YahtzeeFactory::makeDice($dieRoller);
        $dice->rollAll();

        return $dice;
    }
}
<file_sep>/tests/unit/ScoresTest.php
<?php

use Yahtzee\Scores;

class ScoresTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function calculate_total_score()
    {
        $scores = $this->createScores();
        $this->assertEquals(2 + 5, $scores->total());
    }

    /**
     * @return Scores
     */
    private function createScores()
    {
        $scores = new Scores();
        $scores->add('ones', 2);
        $scores->add('twos', 5);

        return $scores;
    }

    /**
     * @test
     */
    public function get_scores()
    {
        $scores = $this->createScores();
        $expected = [
            'ones' => 2,
            'twos' => 5,
        ];
        $this->assertEquals($expected, $scores->all());
    }
}
<file_sep>/src/Scores.php
<?php

namespace Yahtzee;

class Scores
{
    /**
     * @var array
     */
    private $scores = [];

    /**
     * @param string $categoryName
     * @param int    $score
     */
    public function add($categoryName, $score)
    {
        $this->scores[$categoryName] = $score;
    }

    /**
     * @return array
     */
    public function all()
    {
        return $this->scores;
    }

    /**
     * @return int
     */
    public function total()
    {
        return array_sum($this->scores);
    }
}
<file_sep>/src/ScoreCalculator.php
<?php

namespace Yahtzee;

class ScoreCalculator
{
    /**
     * @param int  $value
     * @param Dice $dice
     *
     * @return int
     */
    public function calculate($value, Dice $dice)
    {
        $score = 0;
        $results = $dice->getResults();
        foreach ($results as $dieValue) {
            if ($dieValue === $value) {
                ++$score;
            }
        }

        return $score;
    }
}
<file_sep>/src/DieRoller.php
<?php

namespace Yahtzee;

class DieRoller implements DieRollerInterface
{
    /**
     * @return int
     */
    public function roll()
    {
        return mt_rand(1, 6);
    }
}
<file_sep>/src/ConsoleInterface.php
<?php

namespace Yahtzee;

interface ConsoleInterface
{
    /**
     * @param string $line
     */
    public function printLine($line);

    /**
     * @return string
     */
    public function readLine();
}
<file_sep>/src/PlayerDie.php
<?php

namespace Yahtzee;

class PlayerDie
{
    /**
     * @var DieRollerInterface
     */
    private $dieRoller;
    /**
     * @var int
     */
    private $lastResult;

    /**
     * UserDie constructor.
     *
     * @param DieRollerInterface $dieRoller
     */
    public function __construct(DieRollerInterface $dieRoller)
    {
        $this->dieRoller = $dieRoller;
    }

    public function roll()
    {
        $this->lastResult = $this->dieRoller->roll();
    }

    /**
     * @return int
     */
    public function getResult()
    {
        return $this->lastResult;
    }
}
<file_sep>/src/InputLine.php
<?php

namespace Yahtzee;

class InputLine
{
    /**
     * @var ConsoleInterface
     */
    private $console;

    /**
     * InputLine constructor.
     *
     * @param ConsoleInterface $console
     */
    public function __construct(ConsoleInterface $console)
    {
        $this->console = $console;
    }

    /**
     * @return array
     */
    public function readLine()
    {
        $input = $this->console->readLine();

        return $this->stringToDice($input);
    }

    /**
     * @param string $input
     *
     * @return array
     */
    private function stringToDice($input)
    {
        $result = [];
        if (preg_match_all('/D(\\d)+/', $input, $matches) != 0) {
            $result = $matches[1];
        }

        return $result;
    }
}
<file_sep>/src/Yahtzee.php
<?php

namespace Yahtzee;

class Yahtzee
{
    /**
     * @var ConsoleNotifier
     */
    private $consoleNotifier;
    /**
     * @var Round
     */
    private $round;
    /**
     * @var Scores
     */
    private $scores;
    /**
     * @var array
     */
    private $categories;

    /**
     * Yahtzee constructor.
     *
     * @param array           $categories
     * @param Scores          $scores
     * @param ConsoleNotifier $consoleNotifier
     * @param Round           $round
     */
    public function __construct(array $categories, Scores $scores, ConsoleNotifier $consoleNotifier, Round $round)
    {
        $this->categories = $categories;
        $this->scores = $scores;
        $this->consoleNotifier = $consoleNotifier;
        $this->round = $round;
    }

    public function play()
    {
        foreach ($this->categories as $category) {
            $this->playCategory($category);
        }
        $this->consoleNotifier->scores($this->categories, $this->scores);
    }

    /**
     * @param Category $category
     */
    private function playCategory(Category $category)
    {
        $categoryName = $category->getName();
        $this->consoleNotifier->categoryName($categoryName);
        $dice = $this->round->run();
        $score = $category->getScore($dice);
        $this->scores->add($categoryName, $score);
        $this->consoleNotifier->categoryScore($categoryName, $score);
    }
}
<file_sep>/src/DieRollerInterface.php
<?php

namespace Yahtzee;

interface DieRollerInterface
{
    /**
     * @return int
     */
    public function roll();
}
<file_sep>/tests/unit/DiceRollerTest.php
<?php

use Yahtzee\DieRollerInterface;
use Yahtzee\YahtzeeFactory;

class DiceTest extends PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function have_5_dice()
    {
        $dieRoller = $this->getMock(DieRollerInterface::class);
        $dice = YahtzeeFactory::makeDice($dieRoller);
        $dice->rollAll();
        $this->assertCount(5, $dice->getResults());
    }

    /**
     * @test
     */
    public function re_roll_dice_specified_and_preserve_others()
    {
        $dieRoller = $this->getMock(DieRollerInterface::class);
        $dieRoller->method('roll')->will($this->onConsecutiveCalls(3, 4, 6, 5, 1, 2, 5));
        $dice = YahtzeeFactory::makeDice($dieRoller);
        $dice->rollAll();
        $dice->roll([1, 3]);
        $expected = [
            1 => 2,
            2 => 4,
            3 => 5,
            4 => 5,
            5 => 1,
        ];
        $this->assertEquals($expected, $dice->getResults());
    }
}
<file_sep>/src/Category.php
<?php

namespace Yahtzee;

class Category
{
    /**
     * @var string
     */
    private $name;
    /**
     * @var ScoreCalculator
     */
    private $scoreCalculator;
    /**
     * @var int
     */
    private $value;

    /**
     * Category constructor.
     *
     * @param ScoreCalculator $scoreCalculator
     * @param $name
     * @param int $value
     */
    public function __construct(ScoreCalculator $scoreCalculator, $name, $value)
    {
        $this->name = $name;
        $this->scoreCalculator = $scoreCalculator;
        $this->value = $value;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param Dice $dice
     *
     * @return mixed
     */
    public function getScore(Dice $dice)
    {
        return $this->scoreCalculator->calculate($this->value, $dice);
    }
}
<file_sep>/src/Dice.php
<?php

namespace Yahtzee;

class Dice
{
    /**
     * @var array
     */
    private $currentDice;

    /**
     * @var array
     */
    private $all;

    /**
     * Dice constructor.
     *
     * @param array $dice
     */
    public function __construct(array $dice)
    {
        $this->currentDice = $dice;
        $this->all = array_keys($dice);
    }

    /**
     * @return array
     */
    public function rollAll()
    {
        return $this->roll($this->all);
    }

    /**
     * @param array $diceToRoll
     *
     * @return array
     */
    public function roll(array $diceToRoll)
    {
        foreach ($diceToRoll as $dieNumber) {
            $this->currentDice[$dieNumber]->roll();
        }
    }

    /**
     * @return array
     */
    public function getResults()
    {
        $results = [];
        foreach ($this->all as $dieNumber) {
            $results[$dieNumber] = $this->currentDice[$dieNumber]->getResult();
        }

        return $results;
    }
}
