PHP
オブジェクト指向
ネタ
More than 1 year has passed since last update.

この記事について

ヒマでやることなかったときに、たまたま Twitter で、じゃんけんのドメインモデルって…みたいなツイートを見かけて、ヒマでやることなかったので、やってみました。

前に書いた下記の記事と同じで、ネタです。

PHP で PPAP - Qiita

やや過剰にオブジェクト指向で書きました。

シンプルさを求めたら下記のようにも書けるようです(このアルゴリズムすごいですね!)。
じゃんけんアルゴリズムをちょっと応用 - Qiita

環境

PHP 7.1.4

コード

あいこの場合は再帰で繰り返します。

<?php
declare(strict_types=1);

namespace Janken;

interface Action
{
    public function __toString(): string;
    public function wins(Action $action): bool;
}

class Gu implements Action
{
    public function __toString(): string
    {
        return 'グー';
    }

    public function wins(Action $action): bool
    {
        return ($action instanceof Choki);
    }
}

class Choki implements Action
{
    public function __toString(): string
    {
        return 'チョキ';
    }

    public function wins(Action $action): bool
    {
        return ($action instanceof Pah);
    }
}

class Pah implements Action
{
    public function __toString(): string
    {
        return 'パー';
    }

    public function wins(Action $action): bool
    {
        return ($action instanceof Gu);
    }
}

class DecisionRule
{
    public function compare(Action $a, Action $b): Action
    {
        return ($a->wins($b)) ? $a : $b;
    }
}

class Actions
{
    private $actions;

    public function __construct(array $actions)
    {
        $this->actions = $actions;
    }

    public function draw(): bool
    {
        return in_array(count(array_unique($this->actions)), [1, 3]);
    }

    public function won(): Action
    {
        list($a, $b) = array_values(array_unique($this->actions));

        return (new DecisionRule())->compare($a, $b);
    }

    public function result(Players $players): Result
    {
        if ($this->draw()) {
            return new Draw();
        }
        return new End($this->won(), $players);
    }
}

interface ActionDispatcher
{
    public function dispatch(array $actions): Action;
}

class RandomActionDispatcher implements ActionDispatcher
{
    private $actions;

    public function __construct(array $actions)
    {
        $this->actions = $actions;
    }

    public function dispatch(array $actions): Action
    {
        $n = array_rand($this->actions);
        $action = $this->actions[$n];
        if (!in_array($action, $actions)) {
            throw new \RuntimeException('invalid action - ' . $action);
        }
        return $action;
    }
}

class FixedActionDispatcher implements ActionDispatcher
{
    private $actions;

    public function __construct(array $actions)
    {
        $this->actions = $actions;
    }

    public function dispatch(array $actions): Action
    {
        $action = array_shift($this->actions);
        if (is_null($action)) {
            throw new \RuntimeException('insufficient to dispatch');
        }
        if (!in_array($action, $actions)) {
            throw new \RuntimeException('invalid action - ' . $action);
        }
        return $action;
    }
}

class Player
{
    private $name;
    private $availableActions;
    private $action;

    public function __construct(string $name, ActionDispatcher $actionDispatcher)
    {
        $this->name = $name;
        $this->actionDispatcher = $actionDispatcher;
        $this->availableActions = [new Gu(), new Choki(), new Pah()];
    }

    public function __toString(): string
    {
        return $this->name;
    }

    public function doAction(): Action
    {
        $this->action = $this->actionDispatcher->dispatch($this->availableActions);
        return $this->action;
    }

    public function action(): Action
    {
        return $this->action;
    }
}

class Players
{
    private $players;

    public function __construct()
    {
        $this->players = [];
    }

    public function join(Player $player)
    {
        $this->players[] = $player;
    }

    public function empty(): bool
    {
        return empty($this->players);
    }

    public function doAction()
    {
        $actions = [];
        foreach ($this->players as $i => $player) {
            $actions[$i] = $player->doAction();
            echo $player . ': ' . $actions[$i] . PHP_EOL;
        }
        return $actions;
    }

    public function match(Action $action): array
    {
        return array_filter($this->players, function ($player) use ($action) {
            return $player->action() == $action;
        });
    }
}

interface Result
{
    public function __toString(): string;
    public function winners(): array;
}

class End implements Result
{
    private $wonAction;
    private $players;

    public function __construct(Action $wonAction, Players $players)
    {
        $this->wonAction = $wonAction;
        $this->players = $players;
    }

    public function __toString(): string
    {
        return $this->wonAction . 'の勝ち!';
    }

    public function winners(): array
    {
        return $this->players->match($this->wonAction);
    }
}

class Draw implements Result
{
    public function __toString(): string
    {
        return 'あいこ';
    }

    public function winners(): array
    {
        return [];
    }
}

class Game
{
    private $players;

    public function __construct()
    {
        $this->players = new Players();
    }

    public function join(Player $player)
    {
        $this->players->join($player);
    }

    public function start()
    {
        if ($this->players->empty()) {
            throw new RuntimeException('no players joined');
        }

        $this->doBattle('じゃんけん、ぽい!');
    }

    public function doBattle(string $call)
    {
        echo '----- ' . $call . ' -----' . PHP_EOL;
        $result = $this->result();
        echo $result . PHP_EOL;

        $winners = $result->winners();
        if (empty($winners)) {
            $this->doBattle('あいこで、しょ!');
        } else {
            echo '----- Winners are: -----' . PHP_EOL;
            foreach ($winners as $winner) {
                echo $winner . PHP_EOL;
            }
        }
    }

    public function result(): Result
    {
        $actions = new Actions($this->players->doAction());
        return $actions->result($this->players);
    }
}

//// Random
$playerNames = ['A', 'B', 'C'];
$actionDispatcher = new RandomActionDispatcher([new Gu, new Choki, new Pah]);

$game = new Game();
foreach ($playerNames as $name) {
    $game->join(new Player($name, $actionDispatcher));
}
$game->start();

//// Fixed
$playerNames = ['A', 'B', 'C'];
$dispatchers = [
    new FixedActionDispatcher([new Gu, new Choki, new Gu]),
    new FixedActionDispatcher([new Choki, new Choki, new Pah]),
    new FixedActionDispatcher([new Pah, new Choki, new Pah]),
];

$game = new Game();
foreach ($playerNames as $i => $name) {
    $game->join(new Player($name, $dispatchers[$i]));
}
$game->start();

ActionDispatcher とか DecisionRule とか、微妙なクラスや依存関係もあるし、勝ち負けの判定を Gu, Choki, Pah 自身にやらせてるのもどうなんだろうなー、と思うので、レビューしてやってもいい、という奇特親切な方がいたら、コメント欄でコメントいただけると助かります :bow: