Skip to content

楔子

几年前一个机缘巧合,让我有机会尝试编写了一个JavaScript版本的德州扑克模拟器,这项工作并没有十分顺利的结束,因为在项目的调试阶段,我发现程序在模拟结果上和市面上现有的扑克模拟工具,以及根据概率论计算的得到牌型概率表,有部分出入,这说明在我代码中的牌型判断逻辑,存在一些错误。

碍于整个牌型判断逻辑比较复杂,我并没有继续深入研究其中原委,而是搁置了下来,但是这个项目让我对概率论产生了浓厚的兴趣。在此之前我已经通过计算机模拟,验证了很多常见的概率问题,比如掷骰子、扔硬币等,直到开发PorbCraft,当完善了骰子模拟器之后,我又萌生了制作一个德州扑克模拟器的念头。

TIP

如果你完全没了解过德州扑克,可以参考维基百科的介绍。我们只是作为概率游戏的一个例子,来讨论其中的概率问题,并不建议你在现实中参与赌博。

这次我从头开始,重新设计牌型判断逻辑,实际的开发时间很短,但思考的时间很长,真正帮助我最终完成整个牌型判断逻辑的关键,是我想明白了一件事:作为一个7张牌选5张组成最大牌型的游戏,判断牌型应该从大往小判断,在牌型大小接近的几种牌型中,四条葫芦不会和同花同时成立,因为四条葫芦都需要有3张相同的牌,而同花则需要5张同花色的牌,所以同花成立时,组成四条葫芦的牌数已经不够了,这个结论可以大大简化牌型判断逻辑和运行效率。

实际测试中,当我用千万次的随机测试来验证我的牌型判断逻辑时,我发现它的判断顺子逻辑有问题。我是怎么确定这个问题存在的呢?答案是通过对照模拟结果和数学概率论的计算结果确定的。顺子的出现概率远低于计算结果,说明我的代码漏掉了一些情况,我很快定位了问题所在,顺子判断逻辑果然存在错误。

下面是在修正了顺子判断逻辑后,模拟100万次的结果:

牌型独立事件数量模拟概率理论概率
高牌23,294,46017.434%17.412%
一对58,627,80043.765%43.823%
两对31,433,40023.525%23.496%
三条6,461,6204.862%4.830%
顺子6,180,0204.627%4.619%
同花4,047,6443.024%3.025%
葫芦3,473,1842.567%2.596%
四条224,8480.166%0.168%
同花顺41,5840.030%0.0311%

通过概率表,不难发现德州扑克的牌型大小,正是根据牌型的出现概率大小来决定的。但同时也不难发现一个例外,一对的成牌概率反直觉般比高牌还要大,并且牌型也大,实际游戏中也的确如此。这是因为上面的模拟概率是在52张牌中抽7张的成牌概率,而规则沿用传统的搜哈规则,概率是52张牌抽5张的成牌概率,这两者是不同的。

当然有时候也会认为修改规则,让游戏更刺激,比如短牌德州扑克中,由于去掉了2-5点的牌,导致同花出现概率低于葫芦,顺子的概率高与三条。所以一般短牌中同花牌型比葫芦大,但顺子和三条谁更大要看玩的人如何商量了。

TIP

电影赌侠中的一句台词:「同花打得過full house?除非你爸變成兔子!」 然而在短牌德州扑克中,同花的确打得过full house(葫芦牌型),因为同花的成牌概率远低于葫芦。

概率游戏

作为一个曾经接触过游戏开发的游戏玩家,我也曾经对游戏的随机性进行过一些思考,随机性和游戏性息息相关,一般棋类游戏由于缺少随机性,一般是硬实力的对抗,不免缺乏乐趣。而随机性过高的游戏,又是纯粹的运气,缺乏策略性,也容易让玩家疲劳。麻将作为一种传统的随机性和策略性兼备的游戏,可谓是经久不衰。

现代电子游戏中,无论是暴击闪避率、开箱子抽卡、还是随机地图生成,都是基于概率的设计,我们可以通过计算机来生成随机数,就像下面这样:

最小值
最大值

当我们设置了最小值和最大值,点击“生成按钮”就可以得到一个介于这两个值之间的随机数。这个随机数是通过计算机的伪随机数生成器生成的,虽然不是真正的随机数,但对于大多数应用已经足够了。

这里我们思考一下,当我们从1-100中随机抽取一个数,抽到1的概率应该是1/100 = 1%,按照直觉,我们会觉得1%的概率,意味着我们抽100次就至少能抽到1次,很不幸这种直接是不符合实际的。

根据概率论计算,抽100次都没有抽到1的概率是(11/100)100(1-1/100)^{100},约等于0.366,也就是说,抽100次都没有抽到1的概率约等于36.6%,而至少抽到1次1的概率就是10.366=0.6341 - 0.366 = 0.634,约等于63.4%,我们可以模拟一下这个过程:

实验次数100次中抽到至少一次1的次数概率
00-

请多次点击按钮来模拟抽取的过程,每点击一次,程序会进行100次抽数模拟,然后统计抽到1的次数,最终会得到一个概率,这个概率会随着采样次数的增加逐渐趋近于63.4%。

什么,你说这个概率在跳动?那是你的采样次数太少,增加采样次数,概率就会稳定下来。这是一个看起来简单但却反直觉的事,明明有1%的命中率,但抽100次,确有36.6%的概率抽不到。

很多抽卡游戏之所以要设置保底机制,就是为了弥补实际概率和玩家直觉之间的差距,让玩家有更好的体验,保底机制本质上是一种伪随机概率,这里说的“伪随机”和前文说的“伪随机数”有一些区别。

伪随机和真随机

我们说的真随机数,是指完全不可预测的随机数,比如在现实中掷骰子、扔硬币。计算机生成的随机数,是通过一个确定的算法生成的,这个算法的输入是一个种子,种子不同,生成的随机数序列也不同,但是如果种子相同,生成的随机数序列也是相同的,这种随机数我们称之为伪随机数。伪随机数,理论上是可以预测的,但在我们做计算机仿真的时候,使用伪随机数当作真随机数使用是没有什么问题的,而在计算机加密领域就要慎重了。

所以虽然计算机生成的是伪随机数,但我在文中不会强调“伪”字,这里其实用“赝”随机数更为贴切,后文将省略“伪”字称其为随机数。而前文后提到的“伪随机概率”,是指通过一些机制,让玩家的体验更好,而不是真正的随机概率,就例如抽卡游戏的保底机制,或者是Dota2的暴击率算法,以及你的听歌软件的随机播放算法等。

接下来我们就从听歌软件的随机播放算法开始聊聊这些伪随机概率。

PS:这个网站是干什么的

这个网站是我的应用ProbCraft的补充学习网站,ProbCraft是我开发的一个小品级的iOS随机工具应用,主要功能是模拟掷骰子、扔硬币、抽卡、掷骰子等随机事件,以及一些概率游戏的模拟,比如德州扑克、掷骰子等。应用原本的定位是工具类应用,但随着开发的深入,我更倾向于将其定位为一个概率游戏的学习工具,但应用中如果包含太多的说明内容,会显得臃肿,所以我决定将一些内容放到这个网站中,通过超链接嵌入的方式,从应用中跳转到这个网站,来获取更多的学习内容。