Skip to content
[OPEN_POKER]

Bot 的扑克数学:Pot Odds、位置与手牌强度

JJoão Carvalho||14 min read

一个理解三件事(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_odds

10/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的决策树变为:

  1. 翻牌前: 检查手牌排名+位置。弱手牌fold,中等手牌call,强手牌raise。按位置调整阈值。
  2. 翻牌后: 估计赢的概率(粗略即可:顶对 ≈ 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张牌的手牌进行评分;像treysdeuces这样的库只需几行代码即可完成。在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牌不是信号。

继续阅读