前回のおさらい
さて、前回は延長戦第1回となる、ドロー5枚で手役の発生確率計算でした。
確率計算自体は本体シリーズでも作成したものでしたので、特段難しくはなかったかと思います。
実際の手役の発生確率50万回で試走させても、なかなかロイヤルストレートフラッシュは出ないという事実と、いわゆる確率論における組み合わせ確率と近似してくることが確認できたかと思います。
連載回数 | 実施内容 |
第5回 | ドロー5枚で手役の発生確率を計算してみよう |
第6回 | コードが汚い、さぁリファクタリングだ 〜初心者でも最低限やれるリファクタリング〜 |
第7回 | 仮想プレーヤーと対戦してみよう |
本来今回はリファクタリングの説明をしようかと思っていましたが、もう少しコードを汚してからリファクタリングした方が効果が感じやすいと思うので、今回は第7回想定でした仮想プレーヤーとの対戦を実施していきます。ただし、完全な対戦ではなく、少しトラップ残りです(トラップの内容は最後に説明します)
ゴールを確認しよう(実行結果の表示)

さて、対戦なので、player1とplayer2に手札を5枚ずつ配って、それぞれの手役の強さを判断してどっちが勝ったかを表示していきます。
実行結果1行目:勝敗結果
実行結果2~9行目:player1の手札内容と手役
実行結果10~17行目:player2の手札内容と手役
実際のコード
import random
import poker_hand_check4card_mark as pkg
class player:
def __init__(self, hand):
self.hand = hand
def hand_result(self):
return list(check_roll(self.hand).values())
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!!": 6}
elif len(set(hand_nums)) == 4 and pkg.pair_check(hand_nums) == 2:
return {"1ペア": 2}
elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 3:
return {"3カード": 4}
elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 2:
return {"2ペア": 3}
elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 4:
return {"4カード": 8}
elif straight_check(hand_nums) and pkg.mark_check(hand_marks) != 5:
return {"Straight!!": 5}
elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 3:
return {"フルハウス": 7}
elif straight_check(hand_nums) and pkg.mark_check(hand_marks) == 5:
if 1 in hand_nums and 10 in hand_nums:
return {"ロイヤルストレートフラッシュ": 10}
else:
return {"ストレートフラッシュ": 9}
else:
high = max(hand_nums)
roll = "high card:{}".format(high)
return {roll: 1}
def battle(player1, player2, *args, **kwargs):
if player1 > player2:
return "win: player1"
elif player1 < player2:
return "win:player2"
else:
return "draw"
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]
hand2 = bill[6:11]
##playerを2人作りハンドの強さ(dict_value)を取得
player1 = player(hand).hand_result()
player2 = player(hand2).hand_result()
##手役強弱結果表示
result = battle(player1, player2)
print(result)
##表示用データ作成
hand_nums = [c.num for c in hand]
hand_marks = [c.mark for c in hand]
hand_nums2 = [c.num for c in hand2]
hand_marks2 = [c.mark for c in hand2]
##手札表示
print("player1手札")
print("-"*20)
for i in range(len(hand_nums)):
print("カード",i+1,":",hand_nums[i], hand_marks[i])
print("手役:", check_roll(hand).keys())
print("player2手札")
print("-" * 20)
for i in range(len(hand_nums2)):
print("カード", i + 1, ":", hand_nums2[i], hand_marks2[i])
print("手役:", check_roll(hand2).keys())
変化点を中心に少しずつ解説をしていこうと思います。
player classを作成しよう
対戦するということはplayerが少なくとも2人必要になりますね。ということは、同じ処理を二回書くことになるので、player class(非常に簡単なものですが)を作成していきます。
class player:
def __init__(self, hand):
self.hand = hand
def hand_result(self):
return list(check_roll(self.hand).values())
def __init__はいつもの通りのおまじないです。呼び出し時にハンドを引数で渡して、そのハンドをいつものcheck_roll関数で手役ランクを判定していきます。この後、説明追加していますが、check_roll関数の戻り値には{key->手役名:values->手役ランク}を設定しています。そのvaluesを取得して、大小比較できるようにするため、list化しています。
手役判定ロジックを少しカスタマイズしよう
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!!": 6}
elif len(set(hand_nums)) == 4 and pkg.pair_check(hand_nums) == 2:
return {"1ペア": 2}
elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 3:
return {"3カード": 4}
elif len(set(hand_nums)) == 3 and pkg.pair_check(hand_nums) == 2:
return {"2ペア": 3}
elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 4:
return {"4カード": 8}
elif straight_check(hand_nums) and pkg.mark_check(hand_marks) != 5:
return {"Straight!!": 5}
elif len(set(hand_nums)) == 2 and pkg.pair_check(hand_nums) == 3:
return {"フルハウス": 7}
elif straight_check(hand_nums) and pkg.mark_check(hand_marks) == 5:
if 1 in hand_nums and 10 in hand_nums:
return {"ロイヤルストレートフラッシュ": 10}
else:
return {"ストレートフラッシュ": 9}
else:
high = max(hand_nums)
roll = "high card:{}".format(high)
return {roll: 1}
さて、大きく変化はしていないのですが、それぞれ判定後の戻り値を変更しています。元々はstrで手役名を戻り値としていましたが、今回は{“手役名”: 手役ランク}で返すようにしています。これは、この後説明するbattle関数で手役ランクの大小でどちらが勝っているのか判断するためです。
battleさせよう
def battle(player1, player2, *args, **kwargs):
if player1 > player2:
return "win: player1"
elif player1 < player2:
return "win:player2"
else:
return "draw"
先ほど、手役にランクを付けましたので、その手役ランクをclass呼び出し時に各playerに付与して大小を比較しています。シンプルな大小比較なので、これについては説明不要かと思います。
今回使ってはいないのですが、関数の引数に*args, **kwargsを入れています。pythonの関数やクラス設定ではよく使う2つのkeywordなので、入れる癖がついてしまっていますが、使わなくても処理できることも多いですね。
main部分の変更点
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]
hand2 = bill[6:11]
##playerを2人作りハンドの強さ(dict_value)を取得
player1 = player(hand).hand_result()
player2 = player(hand2).hand_result()
##手役強弱結果表示
result = battle(player1, player2)
print(result)
プレーヤー2人分のドローを作成しているのが、handとhand2です。まぁ交互に配る設定にはせず、プレーヤー1が1〜5枚目まで、プレーヤー2が6〜10枚目までをドローする設定で作成しています。listの使い方を変更すれば、交互ドローとかも可能ですね。交互ドローの場合は以下でしょうか。
hand = bill[0:11:2]
hand2 = bill[1:11:2]
そして、プレーヤーのハンドの強さをclassからそのまま呼び出している部分が以下ですね。
##playerを2人作りハンドの強さ(dict_value)を取得
player1 = player(hand).hand_result()
player2 = player(hand2).hand_result()
player classにそれぞれのhandを渡して初期化、そして、hand_result()関数を呼び出して、手役ランクをlist形式で取得します。
まとめ
やっと勝負ができました。そして、コードが100行をこえ、だいぶ汚くなってきましたね。なお、実際何度か試していると、以下のような結果を生じさせます。

うん、同じ手役だからドローだよね。。。じゃ、ないですよね?
ポーカーの場合、同じ手役でも、カードの大小で勝ち負けを判断しますよね(公式ルールベース)。非公式だと最大値が同じでも、スーツベース(マークベース)で判断する場合まであります。
ということは、現状ではまだ不足している、ドローのケースで、カードの最大値を比較して判断に組み入れるロジックを次回は作っていきましょう。
そこまで行くと、コードが本当に汚いままになるので、その後リファクタリングをしていければと思います。どこまでこのシリーズやるんだというクレームはおいておいて、pythonポーカを楽しんでいきましょう。