麻将和牌算法
麻将牌有1-9万,1-9条,1-9筒,东南西北,中发白各4张,共34种136张牌。有些地方的麻将还有梅兰花竹、春夏秋冬各一张。一般将梅兰花竹、春夏秋冬用作万能牌(赖子牌、混牌)。
麻将和牌的算法一般分为直接计算法与查表法两种:直接计算法就是按照和牌的规则进行计算;查表算法就是预先把所有和牌的牌型穷举出来组成一张表,在使用的时候直接查表即可。这两种算法各有优劣:直接计算法占用内存比较小,占用CPU会比较高,性能低下,带赖子的时候算法稍显复杂;查表法占用内存比较多,但CPU的开销比较小,性能很高。下面就简单介绍一下这种两算法:
一、直接计算法
麻将的通用和牌规则(不包括7对,13幺等等特殊的和牌规则)是一系列的顺子(三张连牌,如123等等)加一系列的刻子(三张一样的牌,如111等等)再加一对将牌,即可和牌,用公式来表示即为:X * ABC + Y * DDD + EE,也就是说有X个顺子,Y个刻子加一对将牌,X与Y都是大于等于0的数,且整手牌的张数不能超过14张。
关于直接计算法,在网上找到几篇参考文章,这里就不再赘述了。 https://www.cnblogs.com/laddc/p/6646365.html http://xingbinice.iteye.com/blog/2380673
二、查表法
查表法最重要是把所有可和牌型全部穷举出来保存到表中方便直接查询。穷举出来还是比较容易的,但是要保存下来,如果没有一个比较好的方法,则会出来占用内存非常大的情况。
麻将有34种牌,我们可以把每种牌定义成一个字节,我们知道一手牌最多可以有14张牌,每张牌占一个字节,就需要14个字节,穷举出全部可和牌型占用内存是相当惊人的。
网上有一文章介绍了如何表示和保存可和牌型: https://blog.csdn.net/qq_38880234/article/details/76922107?utm_source=blogxgwz0
作者把源码也共享出来了: https://github.com/pinorr/HuPaiMJ
作者写了共四版,前两版作为基础,第三版作了优化,根据测试正确性没发现什么问题,其使用了unordered_map来存储,但是我发现在VS2010的Debug模式下会很慢,可以改为map;第四版则直接使用了数组来存储,性能得到了提升,但是有一个BUG(224420000 牌型胡不了),作者说不好解决,笔者进行了一些研究发现还是比较好解决,只是内存的使用有所增加。
源码在对一种和牌牌型计算数组索引的时候把4张相同的牌做了如下处理:
if ((byIndex[i] & BIT_VAL_FLAG) > 3) byIndex[i] -= 3; nKey |= (int)(byIndex[i] & 0x03) « (2 * i);
即发现是大于3的则减去3,然后再压缩成2位来存储。 在查表时,则进行如下处理:
if (byTemp > 3) { byDelIndex = i; nVal |= (int)(byTemp - 3) « (2 * i); } …… if (byDelIndex != 0xFF) { byCards[9 * cor + byDelIndex] -= 2; –cor; ++byJiangNum; continue; }
即查表时同样对大于3的数作减3处理,并记录该数所在索引,作特殊处理。 作者使用了262144个BYTE来存储,即18位,共0.25M内存,使用了3个这样的数组,共0.75M的内存,不得不说做得很精妙,但是遗憾的是有已知BUG。该方法出现BUG的的关键在对大于3的处理上.
笔者在此基础上做出改进,由于一手牌中同时大于3的数量不会超过3个,这样可以增加两位来记录大于3的个数。此方法共占用20位,1048576个字节,即1M,比原方式占用内存大了4倍,但总体还不算大,经过测试之前的BUG不复存在,目前未发现任何BUG。
祝开心!
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2018/2018-11-07-麻将和牌算法/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。