Edited at

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: