私は現在、ポーカーと眼球運動に関する研究をしています。その研究で必要な実験環境を整えるために、自分でポーカープログラムを開発しました。
仕様
- 基本的にはテキサスホールデムポーカーのルール
- コンピュータとプレイヤーの1対1による対戦
- ベットは常にコンピュータが先
- コンピュータ自身が見えていないカードを乱数で決定し、モンテカルロシミュレーションによって計算した勝率によって、コンピュータは行動選択を行う。
- 使用言語はPython
コード
porker_play.py
# ポーカーのデッキ作成やラウンド進行、結果発表を行うプログラムです。
import random
from odds_calculate import computer_handler
from determine_winner import determine_winner
from player_option import first_option, option_to_reraise, option_to_allin
suits = ['♠', '♣', '♢', '♡']
ranks = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A']
class Card:
def __init__(self, suit, rank):
self.suit = suit
self.rank = rank
def __str__(self):
return f'{self.rank}{self.suit}'
def create_deck():
deck = [Card(suit, rank) for suit in suits for rank in ranks]
random.shuffle(deck)
return deck
def deal_hands(deck):
player_hand = [deck.pop(), deck.pop()]
computer_hand = [deck.pop(), deck.pop()]
return player_hand, computer_hand
def deal_community_cards(deck):
return [deck.pop() for _ in range(5)]
def show_hands(player_hand, community_cards, revealed, computer_hand=None):
print(f"あなたの手札: {', '.join(str(card) for card in player_hand)}")
print(f"コミュニティカード: {', '.join(str(card) if i < revealed else 'X' for i, card in enumerate(community_cards))}")
if computer_hand:
print(f"コンピュータの手札: {', '.join(str(card) for card in computer_hand)}")
else:
print("コンピュータの手札: 非公開")
def betting_round(player_chips, computer_chips, pot, game_over, folder):
# コンピュータのベット
# プリフロップの場合
if pot == 0:
bet_amount = 10
print(f"コンピュータは{bet_amount}チップをベットしました。")
computer_behavior = 10
# フロップ以降
else:
computer_behavior = computer_handler(False, 0, 0, computer_chips, pot, computer_hand_str, community_cards_str)
if computer_behavior == "チェック":
bet_amount = 0
print("コンピュータはチェックしました。")
elif computer_behavior == "オールイン":
bet_amount = computer_chips
print("コンピュータはオールインしました。")
else:
bet_amount = computer_behavior
print(f"コンピュータは{bet_amount}チップをベットしました。")
computer_chips -= bet_amount
pot += bet_amount
print(f"現在のポット: {pot}チップ")
# レイズ変数の設定
raised = False
# プレイヤーの行動選択の入力
player_bet = first_option(computer_behavior, bet_amount, player_chips, pot)
# プレイヤーがフォールドした場合
if player_bet == 'f':
computer_chips += pot
pot = 0
game_over = True
folder = "player"
print("あなたがフォールドしました。コンピュータが勝ちました。")
return player_chips, computer_chips, pot, game_over, folder
# プレイヤーがオールインした場合
elif player_bet == 'a':
pot += player_chips
player_chips = 0
current_bet = pot - bet_amount
print("あなたはオールインしました。")
# コンピュータの行動選択
computer_behavior = computer_handler(True, current_bet, current_bet - bet_amount, computer_chips, pot, computer_hand_str, community_cards_str)
if computer_behavior != "フォールド":
computer_behavior = "オールイン"
if computer_behavior == "フォールド":
player_chips += pot
pot = 0
game_over = True
folder = "computer"
print("コンピュータがフォールドしました。あなたの勝ちです。")
return player_chips, computer_chips, pot, game_over, folder
# オールインの場合
else:
pot += computer_chips
computer_chips = 0
game_over = True
print("コンピュータはオールインしました。")
return player_chips, computer_chips, pot, game_over, folder
# プレイヤーがコールした場合
elif player_bet == 'c':
player_chips -= bet_amount
pot += bet_amount
print("あなたはコールしました。次のラウンドに進みます。")
return player_chips, computer_chips, pot, game_over, folder
# プレイヤーがレイズした場合
else:
player_chips -= player_bet
pot += player_bet
print(f"現在のポット: {pot}チップ")
raise_amount = player_bet - bet_amount
raised = True
# コンピュータの行動選択
while raised:
computer_behavior = computer_handler(True, player_bet, raise_amount, computer_chips, pot, computer_hand_str, community_cards_str)
if computer_behavior == "フォールド":
player_chips += pot
pot = 0
game_over = True
folder = "computer"
print("コンピュータがフォールドしました。あなたの勝ちです。")
break
elif computer_behavior == "コール":
computer_chips -= raise_amount
pot += raise_amount
print("コンピュータはコールしました。")
break
elif computer_behavior == "オールイン":
pot += computer_chips
computer_chips = 0
print("コンピュータはオールインしました。")
# プレイヤーの行動選択の入力
player_bet = option_to_allin()
if player_bet == 'a':
pot += player_chips
player_chips = 0
game_over = True
print("あなたはオールインしました。")
break
# フォールドの場合
else:
computer_chips += pot
pot = 0
game_over = True
folder = "player"
print("あなたがフォールドしました。コンピュータの勝ちです。")
break
# リレイズの処理
else:
new_bet = computer_behavior
computer_chips -= new_bet - bet_amount
pot += new_bet - bet_amount
bet_amount = new_bet
print(f"コンピュータは{new_bet}チップにリレイズしました。")
# プレイヤーの行動選択の入力
raised_player_bet = option_to_reraise(new_bet, player_bet, player_chips, False)
while True:
try:
if raised_player_bet == 'a':
pot += player_chips
current_bet = player_bet + player_chips
player_chips = 0
print("あなたがオールインしました。")
# コンピュータの行動選択
computer_behavior = computer_handler(True, current_bet, current_bet - new_bet, computer_chips, pot, computer_hand_str, community_cards_str)
if computer_behavior != "フォールド":
computer_behavior = "オールイン"
if computer_behavior == "フォールド":
player_chips += pot
pot = 0
game_over = True
folder = "computer"
print("コンピュータがフォールドしました。あなたの勝ちです。")
# オールインの場合
else:
pot += computer_chips
computer_chips = 0
game_over = True
print("コンピュータはオールインしました。")
break
elif raised_player_bet == 'f':
game_over = True
folder = "player"
computer_chips += pot
pot = 0
print("あなたがフォールドしました。コンピュータが勝ちました。")
break
elif raised_player_bet == 'c':
player_chips -= new_bet - player_bet
pot += new_bet - player_bet
raised = False
print("あなたがコールしました")
break
# レイズの場合
else:
raised_player_bet = int(raised_player_bet)
if raised_player_bet < new_bet * 2 or raised_player_bet > player_bet + player_chips - 1:
raise ValueError
else:
player_chips -= raised_player_bet - player_bet
pot += raised_player_bet - player_bet
player_bet = raised_player_bet
break
except ValueError:
raised_player_bet = option_to_reraise(new_bet, player_bet, player_chips, True)
return player_chips, computer_chips, pot, game_over, folder
def main():
# 初期設定
player_chips = 1000
computer_chips = 1000
pot = 0
game_over = False
folder = "None"
deck = create_deck()
# グローバル変数の宣言
global player_hand, computer_hand, community_cards, revealed
player_hand, computer_hand = deal_hands(deck)
community_cards = deal_community_cards(deck)
global player_hand_str, computer_hand_str, community_cards_str
player_hand_str = [str(card) for card in player_hand]
computer_hand_str = [str(card) for card in computer_hand]
full_community_cards_str = [str(card) for card in community_cards]
# 各ラウンドの実施
print("\nプリフロップベッティングラウンド")
revealed = 0
community_cards_str = full_community_cards_str[0:revealed]
show_hands(player_hand, community_cards, revealed)
player_chips, computer_chips, pot, game_over, folder = betting_round(player_chips, computer_chips, pot, game_over, folder)
# game_over が False の場合のみ次のラウンドを実行
if not game_over:
print("\nフロップ")
revealed = 3
community_cards_str = full_community_cards_str[0:revealed]
show_hands(player_hand, community_cards, revealed)
player_chips, computer_chips, pot, game_over, folder = betting_round(player_chips, computer_chips, pot, game_over, folder)
if not game_over:
print("\nターン")
revealed = 4
community_cards_str = full_community_cards_str[0:revealed]
show_hands(player_hand, community_cards, revealed)
player_chips, computer_chips, pot, game_over, folder = betting_round(player_chips, computer_chips, pot, game_over, folder)
if not game_over:
print("\nリバー")
revealed = 5
community_cards_str = full_community_cards_str[0:revealed]
show_hands(player_hand, community_cards, revealed)
player_chips, computer_chips, pot, game_over, folder = betting_round(player_chips, computer_chips, pot, game_over, folder)
if folder == "None":
print("\nショーダウン")
revealed = 5
show_hands(player_hand, community_cards, revealed, computer_hand)
else:
print("\nショーダウンは行われませんでした。")
# 勝者判定
if folder == "None":
result = determine_winner(player_hand_str, computer_hand_str, full_community_cards_str)
print(result)
if result == "プレイヤーの勝ち":
player_chips += pot
pot = 0
elif result == "コンピュータの勝ち":
computer_chips += pot
pot = 0
else:
player_chips += int(pot / 2)
computer_chips += int(pot / 2)
pot = 0
# 結果発表
print(f"最終結果: プレイヤー {player_chips}チップ, コンピュータ {computer_chips}チップ")
if __name__ == "__main__":
main()
odds_calculate.py
# コンピュータが行動選択を行うためのプログラムです。
import random
from collections import Counter
from itertools import combinations
# コンピュータの希望追加ベット額を期待値から求める関数
def simulate_holdem(hole_cards, community_cards, current_bet, raised_chip, pot, num_opponents=1, num_trials=3000):
deck = [r + s for r in "23456789TJQKA" for s in "♠♣♢♡" if r + s not in hole_cards + community_cards]
def hand_strength(hand):
hand = sorted(hand, key=lambda c: "23456789TJQKA".index(c[0]), reverse=True)
counts = Counter([card[0] for card in hand])
count_vals = list(reversed(sorted(counts.values())))
groups = sorted(list(counts.items()), key=lambda c: (c[1], "23456789TJQKA".index(c[0])), reverse=True)
if len(set([card[1] for card in hand])) == 1 and "AKQJT" in "".join([card[0] for card in hand]):
return (9, ) + tuple("23456789TJQKA".index(r) for r, _ in groups)
elif len(set([card[1] for card in hand])) == 1 and "".join([card[0] for card in hand]) in "AKQJT98765432":
return (8, ) + tuple("23456789TJQKA".index(r) for r, _ in groups)
elif count_vals == [4, 1]:
primary, kicker = groups[0][0], groups[1][0]
return (7, "23456789TJQKA".index(primary), "23456789TJQKA".index(kicker))
elif count_vals == [3, 2]:
primary, secondary = groups[0][0], groups[1][0]
return (6, "23456789TJQKA".index(primary), "23456789TJQKA".index(secondary))
elif len(set([card[1] for card in hand])) == 1:
return (5, ) + tuple("23456789TJQKA".index(r) for r, _ in groups)
elif "".join([card[0] for card in hand]) in "AKQJT98765432":
return (4, ) + tuple("23456789TJQKA".index(r) for r, _ in groups)
elif "A5432" in "".join([card[0] for card in hand]):# Aを最低ランクとして使える場合の例外処理
return (4, 3, 2, 1, 0, -1)
elif count_vals == [3, 1, 1]:
primary, kicker1, kicker2 = groups[0][0], groups[1][0], groups[2][0]
return (3, "23456789TJQKA".index(primary), "23456789TJQKA".index(kicker1), "23456789TJQKA".index(kicker2))
elif count_vals == [2, 2, 1]:
primary, secondary, kicker = groups[0][0], groups[1][0], groups[2][0]
return (2, "23456789TJQKA".index(primary), "23456789TJQKA".index(secondary), "23456789TJQKA".index(kicker))
elif count_vals == [2, 1, 1, 1]:
primary, kicker1, kicker2, kicker3 = groups[0][0], groups[1][0], groups[2][0], groups[3][0]
return (1, "23456789TJQKA".index(primary), "23456789TJQKA".index(kicker1), "23456789TJQKA".index(kicker2), "23456789TJQKA".index(kicker3))
else:
return (0, ) + tuple("23456789TJQKA".index(r) for r, _ in groups)
def best_hand(cards):
return max(combinations(cards, 5), key=hand_strength)
def trial():
random.shuffle(deck)
my_cards = hole_cards + community_cards + deck[:5 - len(community_cards)]
my_best_hand = best_hand(my_cards)
opponent_hands = [deck[i:i+2] for i in range(5 - len(community_cards), 5 - len(community_cards) + 2 * num_opponents, 2)]
opponent_best_hands = [best_hand(opponent_hand + community_cards + deck[:5 - len(community_cards)]) for opponent_hand in opponent_hands]
num_better_hands = sum(1 for opponent_best_hand in opponent_best_hands if hand_strength(opponent_best_hand) > hand_strength(my_best_hand))
return num_better_hands == 0
num_wins = sum(1 for _ in range(num_trials) if trial())
odds = num_wins / num_trials
# コンピュータの確率表示
# print(str(odds * 100) + "%")
desired_bet = int(odds * pot / (1 - odds)) - (current_bet - raised_chip)
return desired_bet
# 希望追加ベット額と場の状況からコンピュータの意思決定を行う関数
def computer_handler(raised, current_bet, raised_chip, computer_chips, pot, hole_cards, community_cards):
# 最初のベットの場合
if not raised:
desired_bet = simulate_holdem(hole_cards, community_cards, 0, 0, pot)
if desired_bet < pot / 2:
behavior = "チェック"
elif desired_bet >= pot / 2 and desired_bet < computer_chips:
behavior = desired_bet
# 希望追加ベット額が所持チップよりも大きい場合
else:
behavior = "オールイン"
# プレイヤーのレイズ以降
if raised:
desired_bet = simulate_holdem(hole_cards, community_cards, current_bet, raised_chip, pot)
if desired_bet < raised_chip:
behavior = "フォールド"
elif desired_bet >= raised_chip and desired_bet < current_bet + raised_chip:
behavior = "コール"
elif desired_bet >= current_bet + raised_chip and desired_bet < computer_chips:
behavior = desired_bet
# 希望ベット額が所持チップよりも大きい場合
else:
behavior = "オールイン"
return behavior
player_option.py
# プレイヤーに行動選択の入力を求めるプログラムです。
import math
# 各ラウンドで最初にプレイヤーが行動選択を入力する関数
def first_option(computer_behavior, bet_amount, player_chips, pot):
if computer_behavior == "チェック":
player_bet = input(f"あなたのターン:チェックする場合は 'c', オールインする場合は 'a', ベットする場合は金額({math.ceil(pot / 2)}-{player_chips})を入力: ")
elif bet_amount * 2 < player_chips:
player_bet = input(f"あなたのターン:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a', レイズする場合は金額({bet_amount * 2}-{player_chips - 1})を入力: ")
else:
player_bet = input("あなたのターン:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a'を入力: ")
while True:
try:
if player_bet in ['c', 'f', 'a']:
break
# レイズの場合
else:
player_bet = int(player_bet)
if player_bet < bet_amount * 2 or player_bet > player_chips - 1:
raise ValueError
break
except ValueError:
if computer_behavior == "チェック":
player_bet = input(f"無効な入力です:チェックする場合は 'c', オールインする場合は 'a', ベットする場合は金額({math.ceil(pot / 2)}-{player_chips})を入力: ")
elif bet_amount * 2 < player_chips:
player_bet = input(f"無効な入力です:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a', レイズする場合は金額({bet_amount * 2}-{player_chips - 1})を入力: ")
else:
player_bet = input("無効な入力です:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a'を入力: ")
return player_bet
# リレイズに対してプレイヤーが行動選択を入力する関数
def option_to_reraise(new_bet, player_bet, player_chips, errored):
if not errored:
if new_bet * 2 < player_bet + player_chips:
raised_player_bet = input(f"あなたのターン:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a', レイズする場合は金額({new_bet * 2}-{player_bet + player_chips - 1})を入力: ")
else:
raised_player_bet = input("あなたのターン:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a'を入力: ")
else:
if new_bet * 2 < player_bet + player_chips:
raised_player_bet = input(f"無効な入力です:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a', レイズする場合は金額({new_bet * 2}-{player_bet + player_chips - 1})を入力: ")
else:
raised_player_bet = input("無効な入力です:コールする場合は 'c', フォールドする場合は 'f', オールインする場合は 'a'を入力: ")
return raised_player_bet
# オールインに対してプレイヤーが行動選択を入力する関数
def option_to_allin():
player_bet = input("あなたのターン:フォールドする場合は 'f', オールインする場合は 'a': ")
# 例外処理
while True:
if player_bet not in ['f', 'a']:
player_bet = input("無効な入力です:フォールドする場合は 'f', オールインする場合は 'a': ")
else:
break
return player_bet
determine_winner.py
# 勝者を判定するプログラムです。
from itertools import combinations
from collections import Counter
# 役・キッカーから手札の強さを評価する関数
from collections import Counter
def hand_strength(hand):
hand = sorted(hand, key=lambda c: "23456789TJQKA".index(c[0]), reverse=True)
counts = Counter([card[0] for card in hand])
count_vals = list(reversed(sorted(counts.values())))
groups = sorted(list(counts.items()), key=lambda c: (c[1], "23456789TJQKA".index(c[0])), reverse=True)
if len(set([card[1] for card in hand])) == 1 and "AKQJT" in "".join([card[0] for card in hand]):
return (9, ) + tuple("23456789TJQKA".index(r) for r, _ in groups), "ロイヤルストレートフラッシュ"
elif len(set([card[1] for card in hand])) == 1 and "".join([card[0] for card in hand]) in "AKQJT98765432":
return (8, ) + tuple("23456789TJQKA".index(r) for r, _ in groups), "ストレートフラッシュ"
elif count_vals == [4, 1]:
primary, kicker = groups[0][0], groups[1][0]
return (7, "23456789TJQKA".index(primary), "23456789TJQKA".index(kicker)), "フォーカード"
elif count_vals == [3, 2]:
primary, secondary = groups[0][0], groups[1][0]
return (6, "23456789TJQKA".index(primary), "23456789TJQKA".index(secondary)), "フルハウス"
elif len(set([card[1] for card in hand])) == 1:
return (5, ) + tuple("23456789TJQKA".index(r) for r, _ in groups), "フラッシュ"
elif "".join([card[0] for card in hand]) in "AKQJT98765432":
return (4, ) + tuple("23456789TJQKA".index(r) for r, _ in groups), "ストレート"
elif "A5432" in "".join([card[0] for card in hand]):# Aを最低ランクとして使える場合の例外処理
return (4, 3, 2, 1, 0, -1), "ストレート"
elif count_vals == [3, 1, 1]:
primary, kicker1, kicker2 = groups[0][0], groups[1][0], groups[2][0]
return (3, "23456789TJQKA".index(primary), "23456789TJQKA".index(kicker1), "23456789TJQKA".index(kicker2)), "スリーカード"
elif count_vals == [2, 2, 1]:
primary, secondary, kicker = groups[0][0], groups[1][0], groups[2][0]
return (2, "23456789TJQKA".index(primary), "23456789TJQKA".index(secondary), "23456789TJQKA".index(kicker)), "ツーペア"
elif count_vals == [2, 1, 1, 1]:
primary, kicker1, kicker2, kicker3 = groups[0][0], groups[1][0], groups[2][0], groups[3][0]
return (1, "23456789TJQKA".index(primary), "23456789TJQKA".index(kicker1), "23456789TJQKA".index(kicker2), "23456789TJQKA".index(kicker3)), "ワンペア"
else:
return (0, ) + tuple("23456789TJQKA".index(r) for r, _ in groups), "ハイカード"
# 最も強い手札を選ぶ関数
def best_hand(cards):
return max(combinations(cards, 5), key=lambda hand: hand_strength(hand)[0])
# 出力時のフォーマット修正を行う関数
def format_hand(hand):
hand_str = ", ".join([card[0] + card[1] for card in hand])
return "({})".format(hand_str)
# 勝敗判定を行い、役名と最強の5枚のカードを表示する関数
def determine_winner(player_hand, computer_hand, community_cards):
player_best_hand = best_hand(player_hand + community_cards)
computer_best_hand = best_hand(computer_hand + community_cards)
player_strength, player_hand_name = hand_strength(player_best_hand)
computer_strength, computer_hand_name = hand_strength(computer_best_hand)
result = ""
if player_strength > computer_strength:
result = "プレイヤーの勝ち"
elif player_strength < computer_strength:
result = "コンピュータの勝ち"
else:
result = "引き分け"
print("プレイヤーの手札: {} 役: {}".format(format_hand(player_best_hand), player_hand_name))
print("コンピュータの手札: {} 役: {}".format(format_hand(computer_best_hand), computer_hand_name))
return result
長くなりましたが、以上の関数を用いてポーカーを遊ぶことが出来ます。
モンテカルロシミュレーションを用いているので、コンピュータの行動選択には数秒かかります。
ブラフ等をすることはないので、そういった点を変更する場合は更に変数を加える必要があるでしょう。
コンピュータとポーカーを練習したい人、他のカードゲームに応用してみたい人は是非このコードで遊んでみてください!