用 Python 构建蒙特卡罗扑克胜率计算器
蒙特卡罗扑克胜率计算器,是最先能真正改善决策的数学升级。机器人不再靠“我有听牌,也许能跟注”这种昂贵猜测,而是估算获胜概率,再与底池赔率比较。
本篇属于:2026 年 AI 扑克机器人完整构建指南,该核心指南涵盖框架、决策逻辑、胜率、测试与参赛平台。
核心要点
- 蒙特卡罗模拟通过抽样未知对手底牌与后续公共牌来估算胜率。
- Open Poker 会在
your_turn中提供准确的底池与跟注金额,因此胜率可直接用于底池赔率决策。- 每次决策可从 2,000 至 5,000 次试验起步。试验越多结果越稳定,但也要控制行动延迟。
蒙特卡罗胜率解决什么问题?
蒙特卡罗胜率弥补了当前牌型标签与未来发牌结果之间的差距。在 Open Poker 中,机器人有 120 秒行动时间,并在每条 your_turn 消息中收到底池、公共牌、合法动作与回合令牌(Open Poker 动作文档)。这些信息足以在发送动作前判断跟注是否有利可图。
计算器要回答的是:如果把未知牌重复发许多次,我的牌获胜或平分底池的比例是多少?这个比例就是当前胜率。
如果底池为 300,跟注为 100,则底池赔率为:
100 / (300 + 100) = 25%如果您的抽样赢率是 36%,那么在未来下注影响之前跟注是有利可图的。如果你的胜率是 18%,弃牌可以节省筹码。这个数字并不完美,但它比硬编码的“跟注任何同花听牌”要好得多。
您需要从 Open Poker 获得哪些信息?
你需要四个值:己方底牌、公共牌、底池大小和跟注金额。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 支持标准高牌规则与常规比较运算符,示例见其牌力评估文档。
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 已使用 As、Td、7c 这类格式表示牌,因此除非机器人采用不同存储格式,否则无需自定义转换器。
如果性能成为问题,请缓存解析的卡。 PokerKit 的文档警告说,重复的字符串解析代价高昂。对于第一个机器人,直接字符串就可以了。对于在多个托管机器人中每个操作运行 10,000 次试验的机器人来说,分析变得值得。
你如何估计对一个对手的胜率?
抽样尚未发出的牌与对手底牌,再比较最终牌力。运行 5,000 次试验后,简单的 Python 计算器通常已足够稳定,可用于 Open Poker 实时决策。PokerKit 的基准测试显示,在笔记本电脑 CPU 上每秒可完成超过一百万次标准高牌制牌力评估,因此评估器通常不会最先成为瓶颈(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这个函数会正确计算平分底池时的份额。若你与一名对手并列获胜,胜率计入二分之一;三方并列则计入三分之一。在成对公共牌面或顺子公共牌面上,这种处理尤其重要。
如何把胜率转化为行动?
把胜率与底池赔率比较,再留出一小段安全余量。Open Poker 采用 6 人桌、固定 10/20 盲注,买入范围为 1,000 至 5,000 筹码(赛季文档),一次错误跟注仍可能损失相当可观的筹码。
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 允许每个连接每秒发送 20 条 WebSocket 消息,每次行动时限为 120 秒,但机器人仍应尽快行动。缓慢计算若阻塞事件循环,会让机器人错失更多机会。
不同下注轮使用不同试验次数:
| 下注轮 | 建议试验次数 | 原因 |
|---|---|---|
| 翻牌前 | 1,000 | 足以进行粗略范围检查 |
| 翻牌 | 5,000 | 还剩下两张牌,差异很大 |
| 转 | 3,000 | 还剩一张卡,预估稳定更快 |
| 河 | 0 | 无需模拟,评估成手牌 |
对抗随机牌的翻牌前赢率本身是不够的。您还需要位置范围,因为对手不会随机出牌。使用 扑克机器人位置范围 缩小机器人在翻牌前打开的手牌范围。
有哪些陷阱?
最大的陷阱,是把“当前胜率”误当成“最终利润”。胜率没有计入后续下注。同花听牌在翻牌圈可能有 35% 胜率,但若转牌未击中且对手持续施压,你可能必须弃牌,根本无法实现这部分胜率。
第二个陷阱是对手数量。随机一手牌的赢率并不等于五名活跃玩家的赢率。多路底池会压垮弱听牌,因为其他人更有可能提高。当牌局中还有更多玩家时,增加“对手”参数。
第三个陷阱是桌子纹理。如果你的机器人面对的是非常松散的对手,那么随机手牌的假设会更接近。对抗紧手的机器人,他们的跟注范围比随机的强,而且你的胜率估计过于乐观。这就是对手建模开始收回成本的地方。
如何让计算器保持快速运行?
河牌阶段少做无用计算,才能维持速度。实时机器人不需要为每次边缘跟注追求实验室级精度,只需足够快、足够稳定地给出正确答案,并避免阻塞 WebSocket 循环。这意味着使用缓存、按下注轮调整试验次数,并尽早退出。
使用三个简单的规则:
| 优化 | 为什么它有帮助 |
|---|---|
| 跳过河牌模拟 | 五张公共牌均已知 |
| 在小盆中进行较低的试验 | 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 扑克决策中的模糊空间。
常见问题解答
蒙特卡罗权益准确吗?
不。这是抽样估计值。许多点都可以进行精确枚举,但蒙特卡洛更容易连接到实时机器人,并且速度足够快,适合 Open Poker 的 120 秒操作超时。
我的机器人应该在翻牌前运行赢率吗?
只能作为辅助信号。翻牌前决策应从位置范围开始,因为按钮位的 A9o 与枪口位的 A9o 处境完全不同。对随机底牌计算的胜率忽略了这一背景。
我可以在没有 PokerKit 的情况下使用它吗?
是的,但是你需要另一个手牌评估器。 PokerKit 可以节省时间,因为它直接支持 Hold'em 手牌构建和比较。编写自己的评估器是一个有趣的项目,也是一个糟糕的第一个生产决策。
我如何测试这是否可以改善我的机器人?
运行两个版本至少几百手:一种使用旧的跟注逻辑,另一种使用权益门控的跟注。通过公开扑克赛季统计数据和排行榜跟踪净筹码、玩过的牌局和失败的场次。