3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GMOアドマーケティングAdvent Calendar 2021

Day 4

簡易ブラックジャックを作ってPHP8.1の列挙型ENUMを試す

Last updated at Posted at 2021-12-04

はじめに

2021/11/25に PHP8.1 がリリースされました。
今回は8.1で追加された新機能の 列挙型ENUM を試してみたいと思います。

今回作ったもの

ドキュメントにトランプの例が載っていたのでブラックジャックでも作ってみることにしました。
今回は 52枚のデッキを作る ことと ブラックジャックのカードの数え方 しか重要じゃないので肝心のブラックジャック部分はめちゃめちゃ雑に作りましたのでご容赦ください。
コード全体は長いのでページ下部に記載します。

ENUMについて

ENUMについてはドキュメントなどの方がかなり詳細に書かれているので僕の方では作ったものの中で重要な部分のみ取り扱います。

52枚のデッキを作る

ENUMはスカラー値をセットできます。全てのcaseでスカラー値を持つものをBacked Enumと呼び、読み取り専用のプロパティvalueを持ちます。関連するデータのないcaseでできたものをPure Enumと呼びます。
Pure Enum と Backed Enumは ともにインターフェース UnitEnum を実装しています。UnitEnumは static メソッド cases() を実装していて、定義された case を順に配列を返します。
トランプの13*4のデッキを用意しました。ループして配列にぶち込んだだけです。ジョーカーはそこまで重要じゃないので無視しました。

enum Suit: string
{
    case Hearts = 'H';
    case Diamonds = 'D';
    case Clubs = 'C';
    case Spades = 'S';
}

enum Rank: int {
    case Deuce = 2;
    case Three = 3;
    case Four = 4;
    case Five = 5;
    case Six = 6;
    case Seven = 7;
    case Eight = 8;
    case Nine = 9;
    case Ten = 10;
    case Jack = 11;
    case Queen = 12;
    case King = 13;
    case Ace = 1;
}

function newDeck()
{
    foreach (Suit::cases() as $suit_row) {
        foreach (Rank::cases() as $rank_row) {
            $deck[] = [
                'suit' => $suit_row,
                'rank' => $rank_row,
            ];
        }
    }
}

print Suit::Clubs->value;
// "C"

ブラックジャックのカードの数え方

ブラックジャックの数値は
2~10 → その数値
J, Q, K → 10
A → 1 or 11
でカウントします。
ENUM はメソッドを含めることができます。またインターフェースを実装することもできます。
ブラックジャックのカウント方法を雑に実装してみます。

interface Counter
{
    public function count($bust): int;
}

enum Suit: string
{
    case Hearts = 'H';
    case Diamonds = 'D';
    case Clubs = 'C';
    case Spades = 'S';
}

enum Rank: int implements Counter {
    case Deuce = 2;
    case Three = 3;
    case Four = 4;
    case Five = 5;
    case Six = 6;
    case Seven = 7;
    case Eight = 8;
    case Nine = 9;
    case Ten = 10;
    case Jack = 11;
    case Queen = 12;
    case King = 13;
    case Ace = 1;
    
    public function count($bust): int
    {
        return match($this) {
            Rank::Deuce, Rank::Three, Rank::Four, Rank::Five, Rank::Six, Rank::Seven, Rank::Eight, Rank::Nine, Rank::Ten => $this->value,
            Rank::Jack, Rank::Queen, Rank::King => 10,
            Rank::Ace => $bust ? 1 : 11,
        };
    }
}

コード全体

ENUMを使用することに関しては上記でほぼ終えているので冒頭でも触れましたが、ブラックジャック部分はめちゃめちゃ雑に作りました。

<?php

interface Counter
{
    public function count($bust): int;
}

enum Suit: string
{
    case Hearts = 'H';
    case Diamonds = 'D';
    case Clubs = 'C';
    case Spades = 'S';
}

enum Rank: int implements Counter {
    case Deuce = 2;
    case Three = 3;
    case Four = 4;
    case Five = 5;
    case Six = 6;
    case Seven = 7;
    case Eight = 8;
    case Nine = 9;
    case Ten = 10;
    case Jack = 11;
    case Queen = 12;
    case King = 13;
    case Ace = 1;
    
    public function count($bust): int
    {
        return match($this) {
            Rank::Deuce, Rank::Three, Rank::Four, Rank::Five, Rank::Six, Rank::Seven, Rank::Eight, Rank::Nine, Rank::Ten => $this->value,
            Rank::Jack, Rank::Queen, Rank::King => 10,
            Rank::Ace => $bust ? 1 : 11,
        };
    }
}


class BlackJack {
    private array $deck = [];
    private array $player = [];
    private array $dealer = [];

    public function play()
    {
        $this->newDeck();
        shuffle($this->deck);
        $this->deal();
        $this->showHands(false);
        $this->checkPlayer();
        $this->hit();
        $this->showHands(true);
        $this->checkPlayer();
        $this->checkDealer();
    }

    private function newDeck()
    {
        foreach (Suit::cases() as $suit_row) {
            foreach (Rank::cases() as $rank_row) {
                $this->deck[] = [
                    'suit' => $suit_row,
                    'rank' => $rank_row,
                ];
            }
        }
    }

    private function deal()
    {
        $this->player = array_splice($this->deck, 0, 2);
        $this->dealer = array_splice($this->deck, 0, 2);
    }

    private function hit()
    {
        echo 'hit' . PHP_EOL;
        $this->player[] = array_splice($this->deck, 0, 1)[0];
    }

    private function blackJackCount($hands)
    {
        $sum = 0;
        foreach ($hands as $row) {
            $sum += $row['rank']->count(false);
        }
        if ($sum > 21) {
            foreach ($hands as $row) {
                $sum += $row['rank']->count(true);
            }
        }
        return $sum;
    }

    private function isBust($sum): bool
    {
        return $sum > 21;
    }

    private function isBlackJack($sum): bool
    {
        return $sum === 21;
    }

    private function checkPlayer()
    {
        if ($this->isBust($this->blackJackCount($this->player))) {
            echo 'Bust! You Lose' . PHP_EOL;
            exit(0);
        }

        if ($this->isBlackJack($this->blackJackCount($this->player))) {
            echo 'Check Dealer Hands' . PHP_EOL;
            $this->showHands(true);
            $this->checkDealer();
        }
    }

    private function checkDealer()
    {
        if ($this->isBlackJack($this->blackJackCount($this->dealer))) {
            echo 'Draw' . PHP_EOL;
            exit(0);
        }

        if ($this->isBust($this->blackJackCount($this->dealer))) {
            echo 'Dealer Bust! You Win' . PHP_EOL;
            exit(0);
        }

        echo match ($this->blackJackCount($this->dealer)) {
            $this->blackJackCount($this->dealer) > $this->blackJackCount($this->player) => 'You Lose' . PHP_EOL,
            $this->blackJackCount($this->dealer) === $this->blackJackCount($this->player) => 'Draw' . PHP_EOL,
            default => 'You Win' . PHP_EOL,
        };
    }

    private function showHands($stand)
    {
        echo 'Dealer Hands' . PHP_EOL;
        if ($stand) {
            foreach ($this->dealer as $row) {
                echo $this->padding($row['suit']->value . $row['rank']->value) . PHP_EOL;
            }
        } else {
            echo $this->padding($this->dealer[0]['suit']->value . $this->dealer[0]['rank']->value) . PHP_EOL;
            echo $this->padding('?') . PHP_EOL;
        }

        echo 'Player Hands' . PHP_EOL;
        foreach ($this->player as $row) {
            echo $this->padding($row['suit']->value . $row['rank']->value) . PHP_EOL;
        }
        echo PHP_EOL;
    }

    private function padding($str): string
    {
        return str_pad($str, 5, ' ', STR_PAD_LEFT);
    }
}

$tramp = new BlackJack();
$tramp->play();

#最後に
PHPでENUMを利用できるようになりましたので徐々に使用していきたいです。

明日は、 @tfactory さんによる「vertex ai使ってみた」です。
引き続き、GMOアドマーケティング Advent Calendar 2021 をお楽しみください!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?