介绍
在本文中,我们展示了如何在Python中表示基本的扑克元素,例如“手”和“组合”,以及如何计算扑克赔率,即在无限额德州扑克中获胜/平局/失败的可能性。
我们根据《拉斯维加斯威尼斯之夜》中的真实故事提供实用的分析。
在内华达州拉斯维加斯的威尼斯人的一天。
我们将使用poker包来表示手牌,连击和范围。 我已经扩展了来自Kevin Tseng的扑克赔率计算器,因此它除了能够计算单个手牌之外,还可以基于范围(可能的手牌)来计算扑克概率。
from poker import Range
from poker.hand import Combo
import holdem_calc
import holdem_functions
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.core.display import display, HTML
hero_odds = []
hero_range_odds = []
翻牌圈
我的手牌为K和J(K♠J♣),我使用来自poker.hand的Combo类构造我的手牌。
# my hand = King of spades and Jack of clubs
hero_hand = Combo('KsJc')
print(hero_hand)
我记不清翻牌前发生的事情以及我的位置。 但是,我确实记得翻牌前有加注,而翻牌后只剩下两名选手:我和对方。
我们现在要注意。 翻牌圈出现梅花Q,红桃10和梅花J。 是的,我翻到了顺子!
让我们假设没有对方扑克的先验知识来计算翻牌后的赔率,即在翻牌后,我们将计算出我的牌胜过随机的一对牌的可能性。
flop = ["Qc", "Th", "9s"] # the flop
board = flop # the board equals the flop
villan_hand = None # no prior knowledge about the villan
exact_calculation = True # calculates exactly by simulating the set of all possible hands
verbose = True # returns odds of making a certain poker hand, e.g., quads, set, straight
num_sims = 1 # ignored by exact_calculation = True
read_from_file = None # we are not reading hands from file
odds = holdem_calc.calculate_odds_villan(board, exact_calculation,
num_sims, read_from_file ,
hero_hand, villan_hand,
verbose, print_elapsed_time = True)
Holdem_calc中的函数calculate_odds_villan可以计算出特定的德州扑克赢手的概率。 通过运行蒙特卡洛方法可以估算出该概率,也可以通过模拟所有可能的情况来准确地计算出该概率,快速计算翻牌后的确切赔率。因此在这里我们不需要蒙特卡洛近似值。 这是我们的赔率:
odds[0]
{'tie': 0.04138424018164999, 'win': 0.9308440557284221, 'lose': 0.027771704089927955}
此时,我感觉还不错。 在随机的情况下,我只有2.77%的机会输,获胜的机会超过93%。 这很乐观。
考虑到翻牌前有加注,而只有我和对方在翻牌后才离开,所以对方有一些手牌,对吧? 我们称这种可能的手为范围。 这是我们根据几个因素(包括对方的举止,位置,下注大小等)做出的推论。该推论导致我们假设对方可能拥有一组手牌。 在这一点上,我认为对方有:
- 一对7或更好
- A /10或更好
- K/J或更好
我们可以使用“类别范围”来表示该范围,如下所示:
villan_range = Range('77+, AT+, KJ+')
display(HTML(villan_range.to_html()))
print("#combo combinations:" + str(len(villan_range.combos)))
这使对方手牌组合从总共51 * 52–1 = 2651个可能减少到144种可能。 现在假设对方手牌的范围来计算我的赔率。
items = [holdem_calc.calculate_odds_villan(board, exact_calculation,
num_sims, read_from_file ,
hero_hand, villan_hand,
verbose, print_elapsed_time = False) for villan_hand in villan_range.combos]
odds = {}
[odds.update({odd_type: np.mean([res[0][odd_type] for res in items if res])}) for odd_type in ["tie", "win", "lose"]]
{'tie': 0.11423324150596878, 'win': 0.8030711151923272, 'lose': 0.08269564330170391}
在假定的范围内,我的获胜几率从93%下降至80%。 但是,我仍然很可能损失8.2%。 在这一点上,我很明确。 但是我应该继续吗? 我绝对希望对方继续比赛并且不弃牌。 但是他在翻牌后有个好牌的可能性有多大? 让我们看看如果我们继续玩到最后,他伸手的几率是多少。
for hand_ranking in holdem_functions.hand_rankings:
print(hand_ranking +": " + str(np.mean([res[1][1][hand_ranking] for res in items if res])))
High Card: 0.06978879706152433
Pair: 0.3662891541679421
Two Pair: 0.23085399449035812
Three of a Kind: 0.09733700642791548
Straight: 0.18498112437506367
Flush: 0.0040608101214161816
Full House: 0.04205693296602388
Four of a Kind: 0.004560759106213652
Straight Flush: 2.0406081012141617e-05
Royal Flush: 5.101520253035404e-05
如果我们继续玩,对方很有可能做出一对(36%)或两对(23%)。 他极有可能直接命中(18%)甚至打出盘(9.7%)或满堂(4%)。 由于对方很有可能拥有合理的手牌,因此我决定下高注,大约底池的2/3。
转牌
到转牌了,是方片2(2♦)。 基本上,这是一张空白牌,也就是说,它对我们的游戏没有太大影响。
turn= ["2d"]
board = flop + turn
villan_hand = None
odds = holdem_calc.calculate_odds_villan(board, exact_calculation,
num_sims, read_from_file ,
hero_hand, villan_hand,
verbose, print_elapsed_time = True)
hero_odds.append(odds[0]['win'])
print(odds[0])
{'tie': 0.0233201581027668, 'win': 0.9677206851119895, 'lose': 0.008959156785243741}
假设对方的牌是随机的,那么我现在有96%的获胜几率。
但是,考虑到我假定的对方手牌范围,我的获胜几率现在从翻牌时的80%上升到86%。 我再次下注,对方跟注,河牌来了。
items = [holdem_calc.calculate_odds_villan(board, exact_calculation,
num_sims, read_from_file ,
hero_hand, villan_hand,
verbose, print_elapsed_time = False) for villan_hand in villan_range.combos]
odds = {}
[odds.update({odd_type: np.mean([res[0][odd_type] for res in items if res])}) for odd_type in ["tie", "win", "lose"]]
{'tie': 0.10123966942148759, 'win': 0.8615702479338843, 'lose': 0.0371900826446281}
河牌
是梅花K(K♣)。 这使对方更容易胜利。 所以这对我来说是个坏消息。
river = ["Kc"]
board = flop + turn + river
verbose = True
villan_hand = None
odds = holdem_calc.calculate_odds_villan(board, exact_calculation,
num_sims, read_from_file ,
hero_hand, villan_hand,
verbose, print_elapsed_time = True)
hero_odds.append(odds[0]['win'])
print(odds[0])
{'tie': 0.11818181818181818, 'win': 0.8696969696969697, 'lose': 0.012121212121212121}
现在,我对随机牌的获胜几率从96%降至约87%。 但我仍然只以1.2%的极低概率输掉。 好吧,那条坏的河牌不是那么糟吧?
好吧,还有另外一个因素。 对方在翻牌圈和河牌圈都跟我有大赌注。 他可能比我想像的要好…对吗? 然后,我应该调整我的假定范围。
现在,我认为对方不再拥有77或88的一对,否则,鉴于我的高赌注,他不会跟下去。 我认为他可能有一对9或更好的一对,才能与99、10或QQ配对。 他可能还会有JJ从而导致平局。 或KK和AA,直到转牌时都是头对。 我决定保持10和K或更好的牌,因为有所谓的隐含赔率。 隐含赔率是对您打出的一笔钱可以从投注中赢取多少钱的估计。 因此,对方可能会等待中奖(他可能刚刚中了?)。 因此,我将对方的更新范围定义如下:
villan_range = Range('99+, AT+, KJ+')
display(HTML(villan_range.to_html()))
print("#combo combinations:" + str(len(villan_range.combos)))
现在,对方的连击数从144降低到了132。让我们计算更新后的赔率。
items = [holdem_calc.calculate_odds_villan(board, exact_calculation,
num_sims, read_from_file ,
hero_hand, villan_hand,
verbose, print_elapsed_time = False) for villan_hand in villan_range.combos]
odds = {}
[odds.update({odd_type: np.mean([res[0][odd_type] for res in items if res])}) for odd_type in ["tie", "win", "lose"]]
{'tie': 0.12, 'win': 0.72, 'lose': 0.16}
现在,我有72%的机会获胜(从86%下降的),而我在转牌时的失利几率从3.7%增加到16%。 我决定慎重一下,对方则全押,下注大约70%的彩池。
基本的河牌战略可以告诉您以下内容:
- 用你最小的牌作为河牌
- 利用您最强的资产押注
- 以中等强度的摊牌值检查手牌,以期达到摊牌
for hand_ranking in holdem_functions.hand_rankings:
print(hand_ranking +": " + str(np.mean([res[1][1][hand_ranking] for res in items if res])))
High Card: 0.0
Pair: 0.5066666666666667
Two Pair: 0.08
Three of a Kind: 0.13333333333333333
Straight: 0.28
Flush: 0.0
Full House: 0.0
Four of a Kind: 0.0
Straight Flush: 0.0
Royal Flush: 0.0
从赔率直方图中,我们可以将对方的可能手牌分为3种类型:
- 虚张声势:他拿着{好牌,成对}的几率为60.66%
- 中强度牌:他以{0.8}的几率拿着{Two Pair}
- 价值下注:他以41.33%的几率持有{三种牌}
对方的全押是有道理的,他持有好牌的概率太低而无法检查。 所以在这里我在想他要么因为虚弱而虚张声势,要么他发疯了,这是一个有价值的选择。 如果您的持牌量最差,那么会虚张声势;如果您的牌很强,则进行价值下注的基本策略有时被称为两极分化下注。 那就是对方在这里所做的。
回顾每种类型的概率(虚张声势,中等强度的手牌,价值下注),我基本上应该至少有60.66%的胜率,这是一个保守的衡量标准,因为对方可能会押注三分之一。 但是我应该跟进吗?
这是另一个称为底池赔率的概念。 底池赔率是指相对于底池大小进行下注的价格。 总而言之,如果我赢得底池的概率大于底池限注价格和底池大小之间的比率,我应该跟注。 让我们做一些数学运算:
- 赢取机会≥60.66%(保守估计)
- 底池价格= 0.7 *底池大小
- 预测底池大小=(1 + 0.7 + 0.7)*底池大小
- 底池赔率=底池价格/预测底池大小= 29%
我获胜的机会至少是底池赔率的两倍。 因此,我继续跟进。 结果呢? 对方转过牌。 桌子一度安静,却凝视着桌子上的Ace Jack。
讨论和结论
在本文中,我展示了如何表示基本的扑克元素(例如手牌和组合),以及如何在讲述威尼斯人夜晚的故事的同时,假设Python中的随机手牌和范围来计算扑克赔率。
我们展示了扑克有多么令人兴奋(概率上很有趣)。 在下面,我展示了我的获胜赔率是如何从翻牌到转牌,然后是河牌的改变过程,假设对方的随机牌以及推断范围。
我们观察到,即使最终结果不利于我,我还是赢得这一单挑局的主要人选。 这就是为什么扑克玩家说
您应该专注于做出决定,而不关注所取得的结果。
当然,本文中的所有分析都假设了一些范围和基本的扑克策略,这些策略和基本的扑克策略构成了我在玩游戏时的思维模型,并在本文中以Python实现。 我不是职业扑克玩家,还有很多方法。 我相信我犯了一些错误,例如,低估了对方在翻牌前加注时持有A和J的可能。
我很好奇,其他人将如何使用此处使用的Python框架来分析手牌。
作者:Thársis Souza, PhD
deephub翻译组:孟翔杰