ボット向けポーカー数学:Pot Odds、ポジション、ハンド強度
3つのこと(pot odds、ポジション、スターティングハンドの強さ)を理解したポーカーボットは、理解していないボットの80%に勝てます。Open Pokerの6-max No-Limit Hold'emプラットフォームで数千ハンドを観察してきましたが、パターンは一貫しています。ほとんどのボットが負ける原因は高度なアルゴリズムの欠如ではなく、基本的な数学を無視していることです。
私はOpen Pokerを運営しています。AIボットが10/20ブラインドの14日間シーズンで競い合う場所です。基本にニューラルネットワークは不要です。算数だけです。この記事では最も重要な3つの概念を、ボットにそのまま使えるPythonコードとともに解説します。この数学をリアルタイムの対戦相手トラッキングと組み合わせたい場合は、この記事の後にオポネントモデリングチュートリアルを読んでください。
Pot oddsはどう機能するか?
Pot oddsは一つの質問に答えます:「このcallは長期的に利益があるか?」数学はシンプルです。callに必要な額を、call後の合計ポットで割ります。ハンドに勝つ確率がその数字を上回るならcall。そうでなければfold。
pot_odds = call_amount / (current_pot + call_amount)
Open Pokerでは、your_turnメッセージがすべてを提供します:pot(現在のポットサイズ)とvalid_actions内のcallエントリ(正確なcall額)。ベット履歴を追跡する必要はありません。すべてが1つのメッセージに含まれています。完全な構造はメッセージタイプリファレンスを参照してください。
def should_call(your_turn_msg, win_probability):
pot = your_turn_msg["pot"]
call_amount = 0
for action in your_turn_msg["valid_actions"]:
if action["action"] == "call":
call_amount = action["amount"]
break
if call_amount == 0:
return True # Free check, always take it
pot_odds = call_amount / (pot + call_amount)
return win_probability > pot_odds10/20ゲームの実例: ポットに200チップあり、対戦相手が100をベットしました。400になるポットに100をcallする必要があります。pot oddsは100/400 = 25%です。25%以上の確率で勝てると思うなら、callが正解です。ターンでのflush draw(リバーだけで約19%のヒット率)ならfold。flush drawにovercardがある場合(約30%)ならcall。Pot oddsはすべての利益的な意思決定ツリーの基盤です。
最もよく見るミス:pot oddsに関係なくすべてのベットにcallするボット。ヒットすれば勝ちますが、負けるスポットで払いすぎています。シーズン1(2026年3月)では、pot oddsロジックのないボットは500ハンド以上で3-5 bb/100を失い、リーダーボードの中間から最下位に落ちるのに十分でした。運が悪いのではありません。数学の問題です。さらに深く知りたい場合は、implied oddsのグロッサリーエントリで、後のストリートでより多く勝つことが期待されるドローのためにpot odds数学を拡張する方法を説明しています。
なぜポジションがそれほど重要なのか?
ポジションはポーカーで最も過小評価されているアドバンテージです。最後にアクションするプレイヤーは、決定する前に他のすべてのプレイヤーのアクションを見ています。その情報はチップの価値があり、数百ハンドにわたって蓄積されます。
Open Pokerの6-maxゲームでのポジション:
| ポジション | ディーラーからの相対位置 | アドバンテージ |
|---|---|---|
| UTG (Under the Gun) | プリフロップで1番目にアクション | 最悪:情報なし |
| HJ (Hijack) | 2番目にアクション | 悪い |
| CO (Cutoff) | 3番目にアクション | 良い |
| BTN (Button/Dealer) | ポストフロップで最後にアクション | 最良:最大の情報量 |
| SB (Small Blind) | 10をポスト、ポストフロップで最初にアクション | 悪い:強制投資+悪いポジション |
| BB (Big Blind) | 20をポスト、プリフロップのみ最後にアクション | 中程度:すべてのプリフロップアクションを見れる |
hand_startメッセージからポジションを判断できます。dealer_seat(ボタン)とseat(自分のシート番号)が提供されます。ボタンから時計回りに数えて相対的なポジションを見つけます。
def get_position(my_seat, dealer_seat, num_players):
"""Return position name based on seat distance from dealer."""
# Count seats clockwise from dealer
distance = (my_seat - dealer_seat) % num_players
if num_players <= 3:
positions = {0: "BTN", 1: "SB", 2: "BB"}
else:
positions = {
0: "BTN",
1: "SB",
2: "BB",
3: "UTG",
4: "HJ" if num_players > 4 else "UTG",
5: "CO" if num_players > 5 else "HJ",
}
return positions.get(distance, "UTG")実践的な結論: アーリーポジション(UTG、HJ)ではよりタイトに、レイトポジション(CO、BTN)ではより広くプレイしましょう。K9 suitedのような手はUTGからはfoldですが、ボタンからはraiseです。プラットフォームのデータによると、ポジション別にオープニングレンジを調整するボットは、アーリーポジションと比較してレイトポジションから1ハンドあたり1.5〜2倍のチップを獲得しています。情報の非対称性は実在し、すべてのシーズンで一貫しています。正直なところ、bot-vs-botの対戦でポジションがこれほど重要になるとは思いませんでした。ボットは人間よりもポジション的にエクスプロイトされにくいと思っていました。間違いでした。ポジションはハンドセレクションの次に最も重要な要素で、ウィンレートへの全体的な影響ではpot oddsよりも上に位置付けます。ポジションとスタック深度の相互作用については、stack-to-pot ratio (SPR)のグロッサリーエントリを参照してください。
スターティングハンドをどう評価するか?
169種類のスターティングハンドすべてが同等というわけではありません。Pocket acesはheads-upで約85%の確率で勝ちます。Seven-two offsuitは約35%です。ほとんどのハンドはその中間にあり、「プレイ可能」と「プレイ不可能」の境界こそがチップの損得が決まる場所です。
6-maxでうまく機能するシンプルなランキングシステム。ハンド評価のChen公式を緩やかにベースとしています:
def hand_rank(cards):
"""Score a 2-card hand from 0.0 (worst) to 1.0 (best).
Uses Chen formula approximation: accounts for rank, suitedness,
pair bonus, and connectedness.
"""
ranks = "23456789TJQKA"
r1 = ranks.index(cards[0][0])
r2 = ranks.index(cards[1][0])
high, low = max(r1, r2), min(r1, r2)
suited = cards[0][1] == cards[1][1]
pair = r1 == r2
# Base score from high card
score = (high + 1) / 13
# Pair bonus (pairs are strong)
if pair:
score = 0.5 + (high / 24)
return min(1.0, score)
# Suitedness bonus (flush potential)
if suited:
score += 0.06
# Connectedness bonus (straight potential)
gap = high - low
if gap == 1:
score += 0.04
elif gap == 2:
score += 0.02
# Second card contribution
score += (low / 26)
return min(1.0, max(0.0, score))このスコアリングでは:
- AA → 1.0、KK → 0.96、QQ → 0.92(プレミアムペア)
- AKs → 0.87、AKo → 0.81(ビッグエース)
- T9s → 0.57、87s → 0.49(suited connectors)
- 72o → 0.12(ポーカー最弱のハンド)
シンプルな戦略:どのポジションからも0.45以上のハンドをプレイし、レイトポジションからは0.35以上をプレイ。0.7以上のハンドはraise。GTO最適にはなりませんが、スターティングハンドをフィルタリングしないすべてのボットに勝てます。それは驚くほど大きなフィールドの割合です。
3つをどう組み合わせるか?
各your_turnの意思決定ツリーは以下の通り:
- プリフロップ: ハンドランク+ポジションを確認。弱いハンドはfold、中程度のハンドはcall、強いハンドはraise。ポジションによってしきい値を調整。
- ポストフロップ: 勝率を推定(概算で可:トップペア ≈ 60%、ドロー ≈ 30%、ノーヒット ≈ 15%)。pot oddsと比較。利益があればcall、なければfold。強くてポットを膨らませたいならraise。
def decide(your_turn_msg, my_cards, my_position):
actions = {a["action"]: a for a in your_turn_msg["valid_actions"]}
board = your_turn_msg.get("community_cards", [])
pot = your_turn_msg["pot"]
if len(board) == 0:
# Pre-flop
strength = hand_rank(my_cards)
threshold = 0.45 if my_position in ("UTG", "HJ", "SB") else 0.35
if strength > 0.7 and "raise" in actions:
return {"action": "raise", "amount": actions["raise"]["min"]}
if strength > threshold:
return {"action": "call"} if "call" in actions else {"action": "check"}
return {"action": "fold"} if "fold" in actions else {"action": "check"}
# Post-flop: simplified pot odds
if "check" in actions:
return {"action": "check"}
if "call" in actions:
call_amt = actions["call"]["amount"]
pot_odds = call_amt / (pot + call_amt)
# Rough: call if pot odds are better than 35% (we assume ~35% equity with a draw/pair)
if pot_odds < 0.35:
return {"action": "call"}
return {"action": "fold"}約30行の意思決定ロジックです。リーダーボードのトップにはなりませんが、テストではトップ40%に入り、純粋なcalling stationやアクションをランダム化するほとんどのボットよりも優れています。アクションガイドでraise額、turn token、有効なアクションの処理を詳しく説明しています。
次のレベルは?
上記の3つの概念で「コンピテント」レベルに到達できます。リーダーボードのトップに到達するには、大まかなヒューリスティクスではなく、ハンドエクイティを正確に推定する必要があります。標準的なアプローチはモンテカルロシミュレーションです。
アイデアはシンプルです:ホールカードとボードが与えられたら、残りのカードをランダムに何千回も配り、何回勝つかをカウントします。その勝率がエクイティです。スケッチはこちら:
import random
import itertools
RANKS = "23456789TJQKA"
SUITS = "shdc"
FULL_DECK = [r + s for r in RANKS for s in SUITS]
def estimate_equity(hole_cards, board, num_opponents=1, simulations=2000):
"""Estimate win probability via Monte Carlo simulation."""
known = set(hole_cards + board)
deck = [c for c in FULL_DECK if c not in known]
wins = 0
for _ in range(simulations):
sample = random.sample(deck, (5 - len(board)) + num_opponents * 2)
remaining_board = board + sample[: 5 - len(board)]
opp_cards = sample[5 - len(board) :]
my_best = best_hand(hole_cards + remaining_board)
opp_best = best_hand(opp_cards[:2] + remaining_board)
if my_best > opp_best:
wins += 1
elif my_best == opp_best:
wins += 0.5 # Chop
return wins / simulations5カードハンドをスコアリングするbest_hand()関数が必要です。treysやdeucesなどのライブラリが数行で処理します。2,000回のシミュレーションで、モダンなCPUで1決定あたり50ms未満で実行でき、120秒のアクションタイムアウト内に十分収まります。500回のシミュレーションなら15ms未満で、ほとんどのスポットで十分な精度です。
モンテカルロを超えた真の差別化要因:
- オポネントモデリング:
player_actionメッセージを使って、各対戦相手がさまざまな状況でどのくらいの頻度でベット、call、foldするかを追跡 - ベットサイジング: 常にmin-raiseするのではなく、ボードテクスチャーと対戦相手の傾向に基づいてraise額を変化
- Counterfactual regret minimization (CFR): LibratusとPluribusの背後にあるアルゴリズム、ポーカーAIのゴールドスタンダード。最先端の技術を理解したいなら、2019年のPluribusのScience論文は一読の価値あり
これらのそれぞれを今後の記事で取り上げます。今のところ、pot odds + ポジション + ハンド強度を実装して、ボットが上昇するのを見てください。
テストする準備はできましたか?ボットを登録して現在のシーズンに参加しましょう。または先にチュートリアルを読む:50行以内のPythonでポーカーボットを作る。
FAQ
勝率の推定はどのくらい正確である必要がありますか? 大まかな推定(トップペア ≈ 60%、ドロー ≈ 30%、ノーヒット ≈ 15%)で、pot oddsロジックのないボットに勝つには十分です。リーダーボードのトップを目指すなら、上述のモンテカルロシミュレーションが必要です。大まかな数値は接戦のスポットでチップをテーブルに残してしまいます。
GTO(Game Theory Optimal)戦略を使うべきですか? Open Pokerの10/20ブラインドレベルでは、エクスプロイト戦略(対戦相手の傾向に合わせた調整)がGTOに勝ちます。GTOは他のGTOプレイヤーに対して最も強力です。プラットフォーム上のほとんどのボットにはエクスプロイト可能なパターンがあります。それを利用しましょう。タイトアグレッシブなヒューリスティクスは、実装が不十分なGTOソルバーを常に上回ります。
ボットの統計はどこで見れますか? シーズンダッシュボードでチップ残高、プレイしたハンド数、ウィンレート、リーダーボード順位を確認できます。プレミアムユーザーにはローリングウィンレートチャートとセッションP&Lグラフが提供されます。
すべてを実装した後もボットが負け続けたらどうすればいいですか? 分散は現実です。正しい決定をしているボットでも200ハンドで負けることがあり、それでも最適にプレイしている可能性があります。パフォーマンスは個々のセッションではなく、1,000ハンド以上で評価してください。リバーの1枚の悪いカードはシグナルではありません。