Skip to content
[OPEN_POKER]

Pythonで作るモンテカルロ・ポーカー・エクイティ計算機

JJoão Carvalho||更新済み |15 min read

モンテカルロ法によるポーカーエクイティ計算は、ボットの判断を大きく改善する最初の数学的な強化策です。「ドローがあるからコールしてみる」と勘に頼るのではなく、勝つ確率を推定してポットオッズと比較し、割に合わないコールを避けられます。

関連ガイド: 2026年版 AIポーカーボット構築完全ガイド。フレームワーク、意思決定ロジック、フェアネス、テスト、対戦環境までを網羅しています。

重要なポイント

  • モンテカルロ法では、未知の相手ホールカードと今後のボードカードを無作為に配り、勝率を推定します。
  • Open Poker は your_turn で正確なポット額とコール額を渡すため、エクイティをポットオッズに基づく判断へそのまま使えます。
  • 1回の判断につき2,000回から5,000回の試行から始めます。試行を増やすほど推定は安定しますが、応答時間とのバランスが必要です。

モンテカルロ・エクイティで何が解決できるか

モンテカルロ・エクイティは、単純なハンド分類だけでは扱えない、残りカードによる勝敗の変化を数値化します。Open Pokerでは、ボットは120秒以内に行動し、各 your_turn メッセージでポット、ボード、有効なアクション、ターントークンを受け取ります(Open Pokerのアクション仕様)。この情報があれば、アクションを返す前にコールの期待値を見積もれます。

計算機が問うのは、「未知のカードを何度も配ったとき、自分のハンドはどのくらいの頻度で勝つか、ポットを分けるか」です。その数値がエクイティです。

ポットが 300 でコールが 100 の場合、ポット オッズは次のようになります。

100 / (300 + 100) = 25%

推定エクイティが36%なら、今後のベットを考慮しない単純計算でもコールは利益的です。18%なら、フォールドしてチップを守るべきです。推定値は厳密解ではありませんが、「フラッシュドローなら常にコール」のような固定ルールより、はるかに合理的です。

Open Pokerから受け取る入力

必要なのは、自分のホールカード、コミュニティカード、ポット額、コール額の4つです。Open Poker は your_turn でコミュニティカードとポットを送ります。自分のホールカードは、非公開の hole_cards メッセージを受信した時点でキャッシュしておきます。WebSocketプロトコルはJSONベースなので、実装言語は問いません(WebSocket仕様)。

def call_amount(valid_actions):
    for action in valid_actions:
        if action["action"] == "call":
            return action["amount"]
    return 0
 
 
def pot_odds(pot, call):
    if call <= 0:
        return 0
    return call / (pot + call)

サーバーがポット額を渡しているなら、ボット側で再計算しないでください。過去の player_action イベントから状態を復元しようとすると、再接続や拒否されたアクション、サイドポットの情報を取りこぼして不整合が起きます。ゲーム状態はサーバーを正とし、ボットは意思決定に集中させます。

最終的なハンドをどのように比較しますか

PokerKit の StandardHighHand.from_game() を使用して、最終的なホールデム ハンドを比較します。 PokerKit は標準のハイ ハンド評価と通常の比較演算子をサポートしており、ハンド評価ドキュメント (PokerKit ハンド評価) に例が記載されています。

from pokerkit import StandardHighHand
 
RANKS = "23456789TJQKA"
SUITS = "cdhs"
DECK = [r + s for r in RANKS for s in SUITS]
 
 
def compact(cards):
    return "".join(cards)
 
 
def best_hand(hole, board):
    return StandardHighHand.from_game(compact(hole), compact(board))

この小さなラッパーにより、計算機の残りの部分が読み取れるようになります。Open Poker カードはすでに AsTd7c のようになっているため、ボットがカードを異なる方法で保存しない限り、カスタム コンバーターは必要ありません。

パフォーマンスが問題になる場合は、解析されたカードをキャッシュします。 PokerKit のドキュメントでは、文字列解析を繰り返すとコストがかかると警告しています。最初のボットの場合は、直接文字列で問題ありません。ホストされている複数のボットにわたってアクションごとに 10,000 回のトライアルを実行するボットの場合、プロファイリングは価値があります。

1人の相手に対するエクイティの推定

不足しているボードカードと相手のホールカードを無作為に配り、完成したハンドを比較します。5,000回試行すれば、単純なPython実装でも通常はOpen Pokerのライブ判断に十分な安定性が得られます。PokerKitのベンチマークでは、一般的なノートPCのCPUで標準ハイハンドを毎秒100万回以上評価できると報告されているため、最初に疑うべきボトルネックは評価処理ではありません(PokerKitドキュメント)。

from random import sample
 
 
def estimate_equity(hero, board=None, opponents=1, trials=5000):
    board = board or []
    known = set(hero + board)
    missing_board = 5 - len(board)
    equity = 0.0
 
    for _ in range(trials):
        deck = [card for card in DECK if card not in known]
        draw_count = opponents * 2 + missing_board
        draw = sample(deck, draw_count)
 
        opp_holes = [
            draw[i * 2 : i * 2 + 2]
            for i in range(opponents)
        ]
        runout = board + draw[opponents * 2 :]
 
        hero_hand = best_hand(hero, runout)
        all_hands = [hero_hand] + [
            best_hand(opp, runout)
            for opp in opp_holes
        ]
 
        winning_hand = max(all_hands)
        winners = [hand for hand in all_hands if hand == winning_hand]
 
        if hero_hand == winning_hand:
            equity += 1 / len(winners)
 
    return equity / trials

この関数はスプリットポットも正しく按分します。相手1人と引き分けなら0.5勝、3人で引き分けなら3分の1勝として加算します。ペアボードや全員が同じストレートを使うボードでは、この処理が意外に大きく効きます。

エクイティをアクションへ変換する

エクイティとポット オッズを比較し、小さな安全マージンを追加します。Open Poker ゲームは 6 マックスで、固定 10/20 ブラインドとテーブル バイインは 1,000 ~ 5,000 チップ (シーズン ドキュメント) なので、1 回の悪いコールでもスタックのかなりの部分を失う可能性があります。

def equity_decision(msg, hole_cards, opponents=1):
    actions = {a["action"]: a for a in msg["valid_actions"]}
 
    if "check" in actions:
        return {"action": "check"}
 
    if "call" not in actions:
        return {"action": "fold"}
 
    call = actions["call"]["amount"]
    pot = msg["pot"]
    board = msg.get("community_cards", [])
 
    equity = estimate_equity(
        hero=hole_cards,
        board=board,
        opponents=opponents,
        trials=3000,
    )
 
    required = pot_odds(pot, call)
    margin = 0.03
 
    if equity > required + margin:
        return {"action": "call"}
    return {"action": "fold"}

このロジックはまだ受け身です。利益的な場面ではコールできますが、強いメイドハンドでのバリューレイズや、強いドローでのセミブラフは行いません。計算機が動いたら、ポーカーボットのベッティング戦略 と組み合わせてください。

ライブボットは何回トライアルを実行する必要がありますか

フロップとターンで 3,000 回のトライアルから始めて、レイテンシーが問題になる場合はそれを減らします。 Open Poker では、接続ごとに 1 秒あたり 20 の WebSocket メッセージが許可され、アクションあたり 120 秒が与えられますが、ボットはとにかく迅速に動作する必要があります。遅いボットは、イベント ループをブロックすると、より多くのエッジを逃します。

ストリートごとに試行回数を変えます。

ストリート推奨されるトライアルなぜ
プリフロップ1,000大まかな範囲のチェックには十分
フロップ5,000残り 2 枚のカード、差異は大きい
ターン3,0001 枚のカードが残り、推定値がより早く安定します
リバー0シミュレーションは不要。完成したハンドを評価

ランダムなハンドに対するプリフロップ エクイティだけでは十分ではありません。対戦相手はランダムなカードをプレイしていないため、ポジションの範囲も必要です。 ポーカー ボットのポジション別レンジ を使用して、ボットがフロップ前にオープンするハンドを絞り込みます。

注意すべき落とし穴

最大の罠は、現在のエクイティを利益の保証とみなすことです。エクイティは将来のベッティングを考慮しません。フロップでフラッシュドローに35%のエクイティがあっても、ターンを外して相手にジャムされれば、そのエクイティを実現する前にフォールドせざるを得ないことがあります。

2つ目は相手の人数です。ランダムな1ハンドに対するエクイティは、5人のアクティブプレイヤーに対するエクイティとは異なります。マルチウェイポットでは誰かがさらに強いハンドを作る確率が上がるため、弱いドローの価値が下がります。複数人が残っているなら opponents を増やしてください。

3つ目の罠はボードテクスチャです。非常にルーズな相手ならランダムハンドという仮定に近づきますが、タイトなボットのコールレンジはランダムより強く、エクイティを楽観視しすぎる原因になります。ここで、対戦相手モデリング が効いてきます。

どうすれば計算機の速度を維持できるでしょうか

リバーまでの計算量を抑えて応答を速くします。ライブボットは、すべての際どいコールに研究用途ほどの精度を求める必要はありません。WebSocketループを塞がず、実用上十分な精度の答えを素早く返すことが重要です。そのためにキャッシュ、ストリート別の試行回数、早期終了を使います。

次の 3 つの単純なルールを使用します。

最適化なぜ役立つのか
リバーのシミュレーションを省略ボードカード5枚がすべて判明している
小さなポットでの低試行20 チップのミスには 10,000 回の試行の価値はない
同じ入力をキャッシュ再試行や再同期で同じホールカードとボードを処理することがある
equity_cache = {}
 
 
def cached_equity(hero, board, opponents, trials):
    key = (tuple(sorted(hero)), tuple(board), opponents, trials)
    if key not in equity_cache:
        equity_cache[key] = estimate_equity(hero, board, opponents, trials)
    return equity_cache[key]

キャッシュを永続化する必要はありません。ハンドごとに消去するか、数百件を上限にします。目的は同じ計算の重複を避けることであり、考え得るすべてのホールデム状態を保存することではありません。

ボットの判断にLLMを使う場合は、LLMを呼ぶ前にエクイティを計算し、その割合をプロンプトへ渡します。具体的な数値があれば、LLMの曖昧なポーカー判断を減らせます。

よくある質問

モンテカルロ・エクイティは正確ですか

いいえ、これはサンプル推定値です。多くのスポットで正確な列挙が可能ですが、モンテカルロ法はライブ ボットに接続するのが簡単で、Open Pokerの 120 秒のアクション タイムアウトに十分な速さです。

ボットはプリフロップでもエクイティを計算すべきですか

補助指標としてのみ使います。ボタンの A9o とUTGの A9o では価値が異なるため、プリフロップ判断はまずポジション別レンジに基づくべきです。ランダムハンドに対するエクイティだけでは、この文脈を扱えません。

PokerKit なしでこれを使用できますか

はい。ただし、別のハンド評価器が必要です。PokerKitはホールデムハンドの構築と比較を直接扱えるため、実装時間を短縮できます。評価器の自作は面白い題材ですが、最初の本番ボットで選ぶ必要はありません。

これによってボットが改善されるかどうかをテストするにはどうすればよいですか

少なくとも数百のハンドに対して 2 つのバージョンを実行します。1 つは古いコール ロジックを使用し、もう 1 つはエクイティ ゲート コールを使用します。Open Poker シーズンの統計とリーダーボードを通じて、ネット チップ、プレイされたハンド、失敗したセッションを追跡します。

続きを読む