python練習問題〜シリーズ1第4回、レベル超初級の初級4〜

Python
この記事は約11分で読めます。

前回のおさらい

さて、前回は派生シリーズで、役の発生確率をみていきました。まぁlist作ってその中にwhileカウント施行回数ないで、1回1回カウントアップしていくだけなので、コードとしては難しくなかったかなと思います。

さて、今回はシリーズ自体に戻り、シリーズ最終回?カード5枚でポーカ手役判断です。

参考までに、前回(シリーズ1第3回本回)の記事は以下です。

連載回数カードの前提
第1回山札20枚、カード3枚ドロー、数値1〜5、カードマークなし
第2回山札52枚、カード4枚ドロー、数値1〜13、カードマークなし
第3回山札52枚、カード4枚ドロー、数値1〜13、カードマークあり
第3+α回第3回ベースで役の出現回数を把握しよう
第4回山札52枚、カード5枚ドロー、数値1〜13、カードマークあり

ゴール(役判定結果の表示)

今まで同様こんな感じの内容がアウトプットされれば成功です(山札やドローが毎回変わりますので、どんなカードが配られるか、手役が何かは当然違いますが)

カードが5枚に増えてますね。

第4回 役判定してみよう(山札52枚、カード5枚ドロー、カードマークあり)

さて、第4回の前提条件はタイトルにあるように山札52枚、カード5枚ドロー、カードマークあり。第3回との違いはドローカードがついに5枚になるところです。

5枚になるので、ついにフルハウスおよびストレート系の手役が追加されます。

  • 3カード
  • 4カード
  • フラッシュ
  • 1ペア
  • 2ペア
  • フルハウス
  • ストレート
  • ストレートフラッシュ
  • ロイヤルストレートフラッシュ
  • ブタ(high card)

さて、実際のコードは以下。

import random
import poker_hand_check4card_mark as pkg

class card:
    def __init__(self, mark, num):
        self.num = num
        self.mark = mark

def straight_check(hand_nums):
    #ストレートの判定

    #1がある場合だけ、14とみなしてストレート判定(数値跨ぎ想定)
    if 1 in hand_nums and 10 in hand_nums:
        hand_nums.remove(1)
        hand_nums.append(14)

    #最大値と最小値の差が4になる、平均値の±1の値のカードが含まれる
    max_num = max(hand_nums)
    min_num = min(hand_nums)
    ave_num = (max_num + min_num) / 2

    if max_num - min_num == 4 and ave_num in hand_nums and ave_num - 1 in hand_nums and ave_num + 1 in hand_nums:
        return True

def check_roll(cards):
    hand = [card for card in cards]
    hand_nums = [c.num for c in hand]
    hand_marks = [c.mark for c in hand]

    if pkg.mark_check(hand_marks) == 5 and straight_check(hand_nums) != True:
        return "flash!!"
    elif len(set(hand_nums)) == 4 and pkg.pair_check(hand_nums) == 2:
        return "1ペア"
    elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 3:
        return "3カード"
    elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 2:
        return "2ペア"
    elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 4:
        return "4カード"
    elif straight_check(hand_nums) and pkg.mark_check(hand_marks) != 5:
        return "Straight!!"
    elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 3:
        return "フルハウス"
    elif straight_check(hand_nums) and pkg.mark_check(hand_marks) == 5:
        if 1 in hand_nums and 10 in hand_nums:
            return "ロイヤルストレートフラッシュ"
        else:
            return "ストレートフラッシュ"
    else:
        high = max(hand_nums)
        roll = "high card:{}".format(high)
        return roll


if __name__ == '__main__':
    bill = []
    marks = ('♣', '♦', '♥', '♠')

    bill = [card(k, j) for k in marks for j in range(1, 14)]
    random.shuffle(bill)


    hand = bill[:5]
    hand_nums = [c.num for c in hand]
    hand_marks = [c.mark for c in hand]

    for i in range(len(hand_nums)):
        print("カード",i+1,":",hand_nums[i], hand_marks[i])
    print("手役:", check_roll(hand))

簡易コード解説

さて、今回はストレート系の手役ってどうやって考えるとコード構築ができるか、また、4回目にもなってきたのでコードの使い回しをできるようになろうということで、
①ストレートってどう考えればいいのか(コードでどう表現するか)っていう思考の部分
②過去いろんなpython fileでコード書いてきたので、それをパッケージとして取り込んで関数を利用しよう
という、2つを解説していきます。

ストレートってどう考えればいいのだろう?連続した数の表現

ストレートが手役で増えました。これまでは、数値やマークの重複だけだったんですが、ついに毛色の違う手役が登場しました。

では、そもそもストレートってどういう手役なんでしょう?定義として、天下のwikipedia様を引用すると以下になります。

ストレートは、Q♣ J♠ 10♠ 9♥ 8♥ のように数字が連続した5枚のカードによって構成される役である。5枚のスートがすべて同じ場合は、ストレートフラッシュという上位の役となる。

wikipedia ポーカのハンド一覧  https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%BC%E3%82%AB%E3%83%BC%E3%83%BB%E3%83%8F%E3%83%B3%E3%83%89%E3%81%AE%E4%B8%80%E8%A6%A7#%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%83%88

これをコードっぽく考えると以下の要素となります。

  • 連続した数である(min:M and N、N+1…N+4の値)
  • 最大と最小の差は常に4になる(Aを使う、数値跨ぎは別の形で対応)
  • 中央値およびその±1の数値がカードに入る

という感じでしょうか。最大と最小の差が4となるカードの組み合わせで、最大最小のカードパターンをカバーして、平均値(N+2)に±1した数値のカードが含まれる、って規定すれば、5枚のカードが全てコードで表現できます。

実際は以下の感じにしました。

def straight_check(hand_nums):
    #ストレートの判定

    #1と10がある場合だけ、14とみなしてストレート判定(数値跨ぎ想定)
    if 1 in hand_nums and 10 in hand_nums:
        hand_nums.remove(1)
        hand_nums.append(14)

    #最大値と最小値の差が4になる、平均値の±1の値のカードが含まれる
    max_num = max(hand_nums)
    min_num = min(hand_nums)
    ave_num = (max_num + min_num) / 2

    if max_num - min_num == 4 and ave_num in hand_nums and ave_num - 1 in hand_nums and ave_num + 1 in hand_nums:
        return True

基本は上記で説明した考え方の通りなのですが、Aが含まれる場合、(A,2,3,4,5)のパターンであれば、1として考えればいいのですが、Aの場合、(A,10,J,Q,K)でもストレートの手役になります。

ということで、ハンドのカード内にAと10が含まれる場合だけ、Aを14と読み替えて(10,J,Q,K,A(14))としてストレートの手役判断をしています。

きっと、もっといい考え方があるかとは思うのですが、イメージしやすかったのでこれでw

他のファイルで作ったコードを再利用してみよう

pythonは他のファイルで作った関数等を再利用可能です。よくpythonファイルの最初に以下のような記載をみますよね、これって他のコードで作成した関数系を呼び出すための宣言なんです。

from .... import ...
import...
import... as ...

今回は前回までで作ってきたpythonファイルで作成した関数defを再利用してみます。

import random
import poker_hand_check4 as pkg

私の場合は、pycharmないの同一ディレクトリに量産していってますので、上記の形になります。ファイル名の.pyの部分はimport文で記載する必要はありません。今回は呼び出し名としてpkg(パッケージの略)を設定してあります。

上記のpkgを利用して、pkg.pair_check()やpkg.mark_check()とすることで、他のファイルで作成した関数を呼び出すことが可能です。毎回同じの書かなくていいので楽ですよね、間違えるリスクも減りますし。

 if pkg.mark_check(hand_marks) == 5 and straight_check(hand_nums) != True:
        return "flash!!"
    elif len(set(hand_nums)) == 4 and pkg.pair_check(hand_nums) == 2:
        return "1ペア"
    elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 3:
        return "3カード"
    elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 2:
        return "2ペア"
    elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 4:
        return "4カード"
    elif straight_check(hand_nums) and pkg.mark_check(hand_marks) != 5:
        return "Straight!!"
    elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 3:
        return "フルハウス"
    elif straight_check(hand_nums) and pkg.mark_check(hand_marks) == 5:
        if 1 in hand_nums and 10 in hand_nums:
            return "ロイヤルストレートフラッシュ"
        else:
            return "ストレートフラッシュ"

まとめ

ついに5枚カードがドローできるようになり、最低限ポーカの手役判断っぽくなったのではないでしょうか?

ただ。。。コードが汚いw そして読みにくい。。 自分で書いておいていうのもなんなんですが。。

そして、せっかく手役判定できるようになったんだから、仮想プレーヤー作って対戦したり、色々追加でやりたいですよね?やりたいってなっていてもらえることを期待しつつ、追加で以下のことをしていきます。

連載回数実施内容
第5回ドロー5枚で手役の発生確率を計算してみよう
第6回コードが汚い、さぁリファクタリングだ〜初心者でも最低限やれるリファクタリング〜
第7回仮想プレーヤーと対戦してみよう

カードドローごとでの期待値の算出や、仮想ポイントを持って対戦すること、ドローの引き直しなど色々練習がてらやっていこうと思います。

いつかノンプロ研のpython練習題材になることを目指してw(そのためにはリファクタリングが必須や。。)

連載目次: python練習問題~poker手役判定編~

  1. ポーカ手役判定第1回〜山札20枚、カード3枚ドロー、数値1〜5、カードマークなし〜
  2. ポーカ手役判定第2回〜山札52枚、カード4枚ドロー、数値1〜13、カードマークなし〜
  3. ポーカ手役判定第3回〜山札52枚、カード4枚ドロー、数値1〜13、カードマークあり〜
  4. ポーカ手役判定第3回派生〜第3回ベースで役の出現回数を把握しよう〜
  5. ポーカ手役判定第4回〜山札52枚、カード5枚ドロー、数値1〜13、カードマークあり〜
  6. ポーカ手役判定第5回〜ドロー5枚で手役の発生確率を計算してみよう〜
  7. ポーカ手役判定第6回〜仮想プレーヤーと対戦してみよう〜
  8. ポーカ手役判定第7回〜仮想プレーヤーと対戦してみよう(その2)〜
タイトルとURLをコピーしました