前回のおさらい
さて、前回は派生シリーズで、役の発生確率をみていきました。まぁ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(そのためにはリファクタリングが必須や。。)