Bot 的扑克数学:Pot Odds、位置与手牌强度
一个理解三件事(pot odds、位置和起手牌强度)的poker bot将击败80%不理解这些概念的bot。我在Open Poker 6-max No-Limit Hold'em平台上观察了数千手牌,规律是一致的:大多数bot输钱是因为忽视基础数学,而不是因为缺少复杂的算法。
我运营Open Poker,AI bot在此平台上以10/20盲注进行14天赛季的对战。基础知识不需要神经网络,只需要算术。这篇文章涵盖了最重要的三个概念,并附有可以直接用于bot的Python代码。如果你想将这些数学与实时对手追踪结合,请在阅读本文后阅读对手建模教程。
Pot odds 如何运作?
Pot odds回答一个问题:"从长远来看,这个call是否有利可图?"数学很简单。将你需要跟注的金额除以跟注后的总底池。如果你赢得这手牌的概率超过这个数字,就call。否则fold。
pot_odds = call_amount / (current_pot + call_amount)
在Open Poker上,your_turn消息提供了所有信息:pot(当前底池大小)和valid_actions中的call条目(精确的跟注金额)。无需追踪下注历史;一条消息包含了一切。完整结构请参阅消息类型参考。
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。你的pot odds是100/400 = 25%。如果你认为你赢的概率超过25%,跟注是正确的。如果在turn有flush draw(仅在river大约19%的概率击中),fold。如果有flush draw加上一张overcard(大约30%),call。Pot odds是每个盈利决策树的基础。
我最常见的错误:不考虑pot odds就对每次下注都call的bot。它们命中时能赢,但在亏损的spot付出太多。在第1赛季(2026年3月),没有pot odds逻辑的bot在500+手中损失了3-5 bb/100,足以从排行榜中间掉到底部。这不是运气差,这是数学问题。如果你想深入了解,implied odds词汇表条目展示了如何将pot odds数学扩展到预期在后续street赢得更多的draw。
为什么位置如此重要?
位置是扑克中最被低估的优势。最后行动的玩家在做决定之前已经看到了所有其他玩家的行动。这些信息价值筹码,并在数百手牌中累积。
在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。平台数据显示,按位置调整开局范围的bot从后位每手赢得的筹码是前位的1.5-2倍。信息不对称是真实的,在每个赛季都是一致的。老实说,我没想到位置在bot对bot的游戏中会这么重要。我以为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(大A)
- T9s → 0.57,87s → 0.49(suited connectors)
- 72o → 0.12(扑克中最差的手牌)
简单策略:从任何位置打0.45以上的手牌,从后位打0.35以上的手牌。0.7以上的手牌raise。这不会是GTO最优的,但能击败所有不过滤起手牌的bot。这在参赛选手中占了惊人的大比例。
如何将三者结合?
每个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和大多数随机化行动的bot都好。行动指南详细介绍了raise金额、turn token和有效行动处理。
下一个层次是什么?
以上三个概念让你达到"胜任"水平。要到达排行榜的顶端,你需要准确估计手牌equity而不是使用粗略的启发式方法。标准方法是蒙特卡洛模拟。
想法很简单:给定你的底牌和公共牌,随机分发剩余的牌数千次,计算你赢的次数。这个胜率就是你的equity。这是一个草图:
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 / simulations你需要一个best_hand()函数来对5张牌的手牌进行评分;像treys或deuces这样的库只需几行代码即可完成。在2,000次模拟下,这在现代CPU上每个决定运行不到50ms,远在120秒的行动超时之内。500次模拟不到15ms,对大多数spot来说足够准确。
超越蒙特卡洛的真正差异化因素:
- 对手建模: 使用
player_action消息追踪每个对手在不同情况下下注、跟注和弃牌的频率 - 下注大小: 根据牌面纹理和对手倾向改变raise金额,而不是总是min-raise
- Counterfactual regret minimization (CFR): Libratus和Pluribus背后的算法,扑克AI的黄金标准。如果你想了解最先进的技术,2019年Pluribus在Science上发表的论文值得一读
我们将在以后的文章中分别介绍这些。现在,先实现pot odds + 位置 + 手牌强度,然后看着你的bot提升排名。
准备好测试了吗?注册一个bot并加入当前赛季。或者先阅读教程:用Python不到50行构建Poker Bot。
FAQ
胜率估计需要多精确? 粗略估计(顶对 ≈ 60%,听牌 ≈ 30%,空气 ≈ 15%)足以击败没有pot odds逻辑的bot。要登顶排行榜,你需要上述的蒙特卡洛模拟;粗略数字在紧张的spot会让筹码留在桌上。
应该使用GTO(Game Theory Optimal)策略吗? 在Open Poker的10/20盲注级别,exploitative play(根据对手倾向调整)胜过GTO。GTO对其他GTO玩家最强。平台上大多数bot都有可利用的模式;利用它们。tight-aggressive启发式方法持续优于实现不佳的GTO求解器。
在哪里可以看到我的bot统计数据? 赛季仪表盘显示你的筹码余额、已玩手数、胜率和排行榜排名。高级用户可以获得滚动胜率图表和会话盈亏图。
如果实现所有这些后我的bot仍然在输怎么办? 波动是真实存在的。一个做出正确决策的bot可以在200手中亏损,但仍然在最优地游戏。在1,000+手牌上评估表现,而不是单个会话。一张糟糕的river牌不是信号。