楔子
几年前一个机缘巧合,让我有机会尝试编写了一个JavaScript版本的德州扑克模拟器,这项工作并没有十分顺利的结束,因为在项目的调试阶段,我发现程序在模拟结果上和市面上现有的扑克模拟工具,以及根据概率论计算的得到牌型概率表,有部分出入,这说明在我代码中的牌型判断逻辑,存在一些错误。
碍于整个牌型判断逻辑比较复杂,我并没有继续深入研究其中原委,而是搁置了下来,但是这个项目让我对概率论产生了浓厚的兴趣。在此之前我已经通过计算机模拟,验证了很多常见的概率问题,比如掷骰子、扔硬币等,直到开发PorbCraft,当完善了骰子模拟器之后,我又萌生了制作一个德州扑克模拟器的念头。
TIP
如果你完全没了解过德州扑克,可以参考维基百科的介绍。我们只是作为概率游戏的一个例子,来讨论其中的概率问题,并不建议你在现实中参与赌博。
这次我从头开始,重新设计牌型判断逻辑,实际的开发时间很短,但思考的时间很长,真正帮助我最终完成整个牌型判断逻辑的关键,是我想明白了一件事:作为一个7张牌选5张组成最大牌型的游戏,判断牌型应该从大往小判断,在牌型大小接近的几种牌型中,四条
与葫芦
不会和同花
同时成立,因为四条
和葫芦
都需要有3张相同的牌,而同花
则需要5张同花色的牌,所以同花
成立时,组成四条
或葫芦
的牌数已经不够了,这个结论可以大大简化牌型判断逻辑和运行效率。
实际测试中,当我用千万次的随机测试来验证我的牌型判断逻辑时,我发现它的判断顺子
逻辑有问题。我是怎么确定这个问题存在的呢?答案是通过对照模拟结果和数学概率论的计算结果确定的。顺子
的出现概率远低于计算结果,说明我的代码漏掉了一些情况,我很快定位了问题所在,顺子判断逻辑果然存在错误。
下面是在修正了顺子判断逻辑后,模拟100万次的结果:
牌型 | 独立事件数量 | 模拟概率 | 理论概率 |
---|---|---|---|
高牌 | 23,294,460 | 17.434% | 17.412% |
一对 | 58,627,800 | 43.765% | 43.823% |
两对 | 31,433,400 | 23.525% | 23.496% |
三条 | 6,461,620 | 4.862% | 4.830% |
顺子 | 6,180,020 | 4.627% | 4.619% |
同花 | 4,047,644 | 3.024% | 3.025% |
葫芦 | 3,473,184 | 2.567% | 2.596% |
四条 | 224,848 | 0.166% | 0.168% |
同花顺 | 41,584 | 0.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的概率是,约等于0.366,也就是说,抽100次都没有抽到1的概率约等于36.6%,而至少抽到1次1的概率就是,约等于63.4%,我们可以模拟一下这个过程:
实验次数 | 100次中抽到至少一次1的次数 | 概率 |
0 | 0 | - |
请多次点击按钮来模拟抽取的过程,每点击一次,程序会进行100次抽数模拟,然后统计抽到1的次数,最终会得到一个概率,这个概率会随着采样次数的增加逐渐趋近于63.4%。
什么,你说这个概率在跳动?那是你的采样次数太少,增加采样次数,概率就会稳定下来。这是一个看起来简单但却反直觉的事,明明有1%的命中率,但抽100次,确有36.6%的概率抽不到。
很多抽卡游戏之所以要设置保底机制,就是为了弥补实际概率和玩家直觉之间的差距,让玩家有更好的体验,保底机制本质上是一种伪随机概率,这里说的“伪随机”和前文说的“伪随机数”有一些区别。
伪随机和真随机
我们说的真随机数,是指完全不可预测
的随机数,比如在现实中掷骰子、扔硬币。计算机生成的随机数,是通过一个确定的算法生成的,这个算法的输入是一个种子,种子不同,生成的随机数序列也不同,但是如果种子相同,生成的随机数序列也是相同的,这种随机数我们称之为伪随机数
。伪随机数,理论上是可以预测的,但在我们做计算机仿真的时候,使用伪随机数当作真随机数使用是没有什么问题的,而在计算机加密领域就要慎重了。
所以虽然计算机生成的是伪随机数,但我在文中不会强调“伪”字,这里其实用“赝”随机数更为贴切,后文将省略“伪”字称其为随机数
。而前文后提到的“伪随机概率”,是指通过一些机制,让玩家的体验更好,而不是真正的随机概率,就例如抽卡游戏的保底机制,或者是Dota2的暴击率算法,以及你的听歌软件的随机播放算法等。
接下来我们就从听歌软件的随机播放算法开始聊聊这些伪随机概率。
PS:这个网站是干什么的
这个网站是我的应用ProbCraft
的补充学习网站,ProbCraft
是我开发的一个小品级的iOS随机工具应用,主要功能是模拟掷骰子、扔硬币、抽卡、掷骰子等随机事件,以及一些概率游戏的模拟,比如德州扑克、掷骰子等。应用原本的定位是工具类应用,但随着开发的深入,我更倾向于将其定位为一个概率游戏的学习工具,但应用中如果包含太多的说明内容,会显得臃肿,所以我决定将一些内容放到这个网站中,通过超链接嵌入的方式,从应用中跳转到这个网站,来获取更多的学习内容。