PHP

PHPでブラックジャックを実装してみた

はじめに

プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし
上記の記事があったので、今更ながらPHPでブラックジャックを実装してみました。

ブラックジャックのルール

今回は以下のルールに則って実装しています。
以下引用

  • 初期カードは52枚。引く際にカードの重複は無いようにする
  • プレイヤーとディーラーの2人対戦。プレイヤーは実行者、ディーラーは自動的に実行
  • 実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
  • その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバースト、その時点でゲーム終了
  • プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
  • プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
  • プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
  • JとQとKは10として扱う
  • Aはとりあえず「1」としてだけ扱う。「11」にはしない
  • ダブルダウンなし、スプリットなし、サレンダーなし、その他特殊そうなルールなし

開発

開発する際はできるだけオブジェクト指向エクササイズ(別記事参照)に従って書く努力をしています。
が、全部は従えていません。。。
コードを全部載せると長くなってしまいそうなのでgithubのURLだけで貼ります。
https://github.com/gigosa/php_blackjack

以下、臭いコードだけ抽出して書きます。

  1. class Player implements PlayerInterface
    {
    /**
     * @var Hand
     */
    private $hand;
    private $name;
    
    public function __construct(Hand $hand, string $name)
    {
        $this->hand = $hand;
        $this->name = $name;
    }
    
    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
    

    まず一点、ゲッターメソッドを作ってしまったところ。
    オブジェクト指向エクササイズ的には名前のクラスを作るべきなのでしょうが、クラスにしてもあまり旨味がなさそうでやめてしまいました。
    なので結局ゲッターを作ることに落ち着いてしまったのですが、どうなんでしょう。。。

  2. class GameMaster
    {
    /**
     * @var Deck
     */
    private $deck;
    /**
     * @var Dealer
     */
    private $dealer;
    /**
     * @var Player
     */
    private $player;
    
    public function __construct(DeckInterface $deck, PlayerInterface $dealer, PlayerInterface $player)
    {
        $this->deck = $deck;
        $this->dealer = $dealer;
        $this->player = $player;
    }
    
    /**
     * ブラックジャックを実行
     */
    public function start()
    {
        $this->deck->shuffle();
        $this->dealCard($this->player);
        $this->dealCard($this->player);
        $this->dealCard($this->dealer);
        $this->dealCard($this->dealer);
        $canHit = true;
        while (!$this->player->isBust() && $this->askDraw()) {
            $this->dealCard($this->player);
        }
        if ($this->player->isBust()) {
            echo $this->player->getName() . 'はバーストしました' . "\n";
            return;
        }
        while (!$this->dealer->isBust() && $this->dealer->wantsToDraw()) {
            $this->dealCard($this->dealer);
        }
        if ($this->dealer->isBust()) {
            echo $this->dealer->getName() . 'はバーストしました' . "\n";
            return;
        }
        echo $this->player->getName() . 'の得点は' . $this->player->currentScore() . 'です' . "\n";
        echo $this->dealer->getName() . 'の得点は' . $this->dealer->currentScore() . 'です' . "\n";
        $this->judgeWinner();
        return;
    }
    }
    

    スタートメソッドが長い。
    また、ゲームマスタークラスが表示に関わるところも持ってしまっている。
    途中のwhile文あたりはもう少し抽象化すればいいような気がするのですが特に良い方法が思いつかず。
    表示周りを外だしするにもこちらも良い方法が思いつかず。。。
    結局ここにごちゃごちゃ書くことに落ち着いてしまいました。
    たぶん今回の開発の一番の悔い。

上記以外にもちょいちょいインターフェイスに定義していないメソッドを呼び出したり、プロパティを3つ以上入れてしまったりしてしまいましたがここらで一旦出そうと思いました。
いつまでも手元に置いていても仕方ないですしね。

おわりに

はじめてオブジェクト指向っぽく書いて見ました。できているか不安ですが。
なんとなくオブジェクト指向で書くってこういうことかな? という感じが掴めた気がしたのでだいぶ収穫があったと思います。
まだまだ修行中の身ですのでアドバイス等いただけますと幸いです。