paloma blog

NWエンジニアやってます。主に自宅環境のお遊びを書きます。Pythonもちょっと。タイトルは好きなカクテルから。

スリーカードポーカーゲームを作りたい なんとか動く物作れたよ編

ここ数年はGWはキャンプに行っていたのですが、今年の予定地が休場になってしまい家で過ごしました。

家にいるなら勉強しなきゃってことで、考えていたポーカーゲームがなんとか動くものになりましたのでおさらいをします。

ルール

  • デッキはジョーカーなしの52枚
  • 3枚のカードでポーカーを行う
  • 勝負はディーラーと1対1
  • カードの交換はできない
  • 手札を見て勝負か降りるかを決める
    • 今回は実装していません
  • ディーラーはQ以上のハイカードじゃないと勝負できない
    • 強制的にプレーヤーの勝ち
  • 勝敗とは別の配当(ペアプラス)があります
    • 今回は実装していません

手役

3枚なので少ないです。 通常のポーカーと異なり、ストレートのほうがフラッシュより強いです。

  1. ストレートフラッシュ
  2. スリーカード
  3. ストレート
  4. フラッシュ
  5. ワンペア
  6. イカード

の順となります。
同役なら数字が高いほうが勝ちです。

ゲーム作成の方針

  • GW中に動くものを作成する
  • 最低限ゲームの勝敗判別ができること

というわけで最小機能で何とか間に合いました。

環境

メイン機のWSL上で動かしてます。

masashi@DESKTOP-986MNSO:~/game$ lsb_release -a ;uname -a ; python3 --version
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.4 LTS
Release:        16.04
Codename:       xenial
Linux DESKTOP-986MNSO 4.4.0-18362-Microsoft #476-Microsoft Fri Nov 01 16:53:00 PST 2019 x86_64 x86_64 x86_64 GNU/Linux
Python 3.5.2

ゲーム画像

画像というかコンソールでプレイしますのでログになりますが…

$ python3 pokerapp.py

上記ファイルを実行すると、カードが配られプレーヤーとディーラーが勝手に勝負します。
掛け金、ベット、フォールドの選択はできませんw

こんな感じです。

masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['D12', 'C2', 'D10'] ['High card!']
Dealer Hand: ['C7', 'C13', 'H5'] ['High card!']

Dealer WIN!
masashi@DESKTOP-986MNSO:~/game$
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['S3', 'H4', 'D13'] ['High card!']
Dealer Hand: ['D10', 'D4', 'H6'] ["Dealer can't play"]

Player WIN!
masashi@DESKTOP-986MNSO:~/game$
masashi@DESKTOP-986MNSO:~/game$ python3 pokerapp.py
Player Hand: ['H10', 'H2', 'D14'] ['High card!']
Dealer Hand: ['H6', 'H3', 'S3'] ['One Pair!']

Dealer WIN!

選択の余地が全くないのでゲームというかシミュレーターっぽくなってしまいましたね。

デッキ作成、シャッフル

トランプゲームのデッキ作成、シャッフルの記事はたくさんありますが、
コピペだけだと面白くないので独自に作ってみました。

各13枚 * 4種類を掛ければ1組作れますね。

ポーカーではエースが一番強いのですが、シンプルに14番に割り振りました。
ストレートの判別が簡単になります。
スートも記号で表現したかったのですが、とりあえず今回は諦めます。

class Deck:

    def __init__(self):
        self.deck = []
        self.build()

    def build(self):
        self.value = [x for x in range(2,15)] # Ace is 14
        self.suit = ['S', 'H', 'D', 'C']

        for s in self.suit:
            for v in self.value:
                self.deck.append(''.join([s, str(v)]))

        return self

    def show(self):
        for c in self.deck:
            print(c)

    def shuffle(self):
        random.shuffle(self.deck)


    def draw(self):
        return self.deck.pop(0)

本件で初めて自作クラスを作成しました。
作ってみて何となく慣れてきましたが、難しいですね。

勉強と思って作りましたが関数だけでも作れたかもしれません。
(まだクラス作成のメリットがあんまり…)

動かしてみるとこんな感じ。

>>> import pokerapp
>>>
>>> card = pokerapp.Deck()
>>> card.show()
S2
S3
S4
S5

…

シャッフル。ちゃんと混ざります。

>>> card.shuffle()
>>> card.show()
D4
C13
H14
C5
S6

…

シャッフル後のデッキのリストを頭から引いていきます。

カードを引く関数は別で作成しました。

def Handout(card):
    hand = []
    for i in range(3):
        hand.append(card.draw())

    return hand

手役判定

本当はビット演算とかして判定したかったのですが、
3枚しかないので大人しくバラして判定することにしました。

対戦型なので、役にランク値を持たせて上下を決める流れになります。 役判定の1つ目の戻り値から値だけ抜いて、判定のためにリスト化してます。

valueを抜いただけだと辞書型で返ってしまうので上下の判定ができません。
リスト型は万能ですねえ。

ディーラーはQハイカードより下の役は負けになるので、そのフラグ建てが苦労しました。

  • 手札チェック

同役の場合はカードの数での勝負になるので戻り値がハンドランクと手役の最大値の2つを返しています。

class Handcheck:

    def __init__(self, hand, flag=False):
        self.hand = hand
        self.flag = flag

        if self.flag == True:
            self.player = Judge(self.hand, dealer=True)
        else:
            self.player = Judge(self.hand)

    def get_role(self):
        return list(self.player[0].keys()), self.player[1]

    def result(self):
        return list(self.player[0].values()), self.player[1]
  • 役判定

手札の値をバラしてから役の判定をしています。
1つの関数で完結できました。
3枚で行うゲームだからできる技ですね。

ストレートの処理が大変そうだと思ってましたが、ソートすれば簡単ですね。

def Judge(hands, dealer=False):

    # Devide hands into suits and numbers
    suit = [ hands[0][0] , hands[1][0] , hands[2][0] ]
    rank = [int(x[1:]) for x in hands]

    # Sort of numbers for role evaluation
    rank.sort()

    # Evaluation of hand
    if len(set(suit)) == 1 and rank[1] == rank[0] + 1 and rank[2] == rank[1] + 1:
        return {'Straight Flash': 6}, max(rank)
    elif len(set(rank)) == 1:
        return {'Three of a kind!': 5}, max(rank)
    elif rank[1] == rank[0] + 1 and rank[2] == rank[1] + 1:
        return {'Straight!': 4}, max(rank)
    elif len(set(suit)) == 1:
        return {'Flash!': 3}, max(rank)
    elif len(set(rank)) == 2:
        return {'One Pair!': 2}, max(rank)
    elif max(rank) < 12 and dealer == True:
        return {'Dealer can\'t play': 0}, max(rank)
    elif max(rank):
        return {'High card!': 1}, max(rank)

勝負判定

戻り値の値から上下関係を決めるだけです。 同役同士の引き分けはプレイヤーの勝ちにしました。 GTAオンライン内のルールに則って作成しています。公式ルールはどうかわかりません。

def Match(p1, p2):

    if p1[0] > p2[0]:
        print('Player WIN!')
    elif p1[0] < p2[0]:
        print('Dealer WIN!')
    # Compare number in case draw rank hand
    elif p1[0] == p2[0]:
        if p1[1] > p2[1]:
            print('Player WIN!')
        elif p1[1] < p2[1]:
            print('Dealer WIN!')
        # Player wins in case of draw
        else:
            print('Player WIN!')

メイン処理

ポーカーゲームの流れに沿って関数を呼び出すだけです。
処理が簡潔に書ける分、クラスや関数を作るメリットはありますね。

チップを掛ける処理を作っていないので、今時点ではカードを引くだけのゲームになってます…。

def main():

    # Ante

    # Game start
    card = Deck()
    card.shuffle()

    player = Handout(card)
    dealer = Handout(card)

    p_hand = Handcheck(player).result()
    d_hand = Handcheck(dealer, flag=True).result()

    p_role = Handcheck(player).get_role()
    d_role = Handcheck(dealer, flag=True).get_role()

    print("Player Hand: {} {}\nDealer Hand: {} {}\n".format( \
        player, p_role[0], \
        dealer, d_role[0] ))

    Match(p_hand, d_hand)

    # bet, fold

    # pay off

今後の展開

一回手を付けたからにはもう少しゲームらしくしたいですよね。 というわけでタスクです。

  • 手札の清書
    • スートを記号(♠,♥,♦,♣)に変換する
    • エース、絵柄を文字(A,K,Q,J)に変換する
  • チップの処理追加
    • アンティ(場代)
    • アプラス
    • ベット or フォールド
    • 払い戻し
  • シミュレータの作成
    • 出やすい役の統計、勝率とか
    • 作れたらGTAオンラインで実践します
  • テストコード作成
    • 今回は自分で何回も動かしました
  • GUIゲーム化

あとは適宜コードのリファクタリングですね。
どう書くのがいいコードなのかはまだまだ経験積まないとわからないです。

完走した感想

いや、完走はしてないのですがとりあえずポーカーゲームっぽい動くものが作れました。

これにかかったのは30時間くらいですかね。
GWの4~6まで3日まるまる費やすつもりでしましたが、ゲームやったりお酒飲んだりで思ったより手を付けられませんでした。
家には誘惑がいっぱいです。

関連書籍を読みだけはしていた自作クラスにも初挑戦でしたが、どこの定義がどの変数に当たってるのか初めのうちは苦労しました。 自作クラス作れるとステップアップした気がしますねw

でも関数とクラスの使い分けもまだよくわからないのでこの子を育てて身に着けていこうと思います。

本件のリポジトリです。

github.com

参考サイト