0


简单的统计学:如何用Python计算扑克概率

介绍

在本文中,我们展示了如何在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%的彩池。

基本的河牌战略可以告诉您以下内容:

  1. 用你最小的牌作为河牌
  2. 利用您最强的资产押注
  3. 以中等强度的摊牌值检查手牌,以期达到摊牌
 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种类型:

  1. 虚张声势:他拿着{好牌,成对}的几率为60.66%
  2. 中强度牌:他以{0.8}的几率拿着{Two Pair}
  3. 价值下注:他以41.33%的几率持有{三种牌}

对方的全押是有道理的,他持有好牌的概率太低而无法检查。所以在这里我在想他要么因为虚弱而虚张声势,要么他发疯了,这是一个有价值的选择。如果您的持牌量最差,那么会虚张声势;如果您的牌很强,则进行价值下注的基本策略有时被称为两极分化下注。那就是对方在这里所做的。

回顾每种类型的概率(虚张声势,中等强度的手牌,价值下注),我基本上应该至少有60.66%的胜率,这是一个保守的衡量标准,因为对方可能会押注三分之一。但是我应该跟进吗?

这是另一个称为底池赔率的概念。底池赔率是指相对于底池大小进行下注的价格。总而言之,如果我赢得底池的概率大于底池限注价格和底池大小之间的比率,我应该跟注。让我们做一些数学运算:

  1. 赢取机会≥60.66%(保守估计)
  2. 底池价格= 0.7 *底池大小
  3. 预测底池大小=(1 + 0.7 + 0.7)*底池大小
  4. 底池赔率=底池价格/预测底池大小= 29%

我获胜的机会至少是底池赔率的两倍。因此,我继续跟进。结果呢?对方转过牌。桌子一度安静,却凝视着桌子上的Ace Jack。

讨论和结论

在本文中,我展示了如何表示基本的扑克元素(例如手牌和组合),以及如何在讲述威尼斯人夜晚的故事的同时,假设Python中的随机手牌和范围来计算扑克赔率。

我们展示了扑克有多么令人兴奋(概率上很有趣)。在下面,我展示了我的获胜赔率是如何从翻牌到转牌,然后是河牌的改变过程,假设对方的随机牌以及推断范围。

我们观察到,即使最终结果不利于我,我还是赢得这一单挑局的主要人选。这就是为什么扑克玩家说

您应该专注于做出决定,而不关注所取得的结果。

当然,本文中的所有分析都假设了一些范围和基本的扑克策略,这些策略和基本的扑克策略构成了我在玩游戏时的思维模型,并在本文中以Python实现。我不是职业扑克玩家,还有很多方法。我相信我犯了一些错误,例如,低估了对方在翻牌前加注时持有A和J的可能。

我很好奇,其他人将如何使用此处使用的Python框架来分析手牌。

作者:Thársis Souza, PhD

本文代码:https://github.com/souzatharsis/holdem_calc

deephub翻译组:孟翔杰

DeepHub

微信号 : deephub-imba

每日大数据和人工智能的重磅干货

大厂职位内推信息

长按识别二维码关注 ->

好看就点在看!********** **********

标签:

“简单的统计学:如何用Python计算扑克概率”的评论:

还没有评论