@@ -60,17 +60,17 @@ cover:
6060
6161我们主要来考虑一般型的向听数。根据向听数的定义,我们可以额外定义「0向听」表示听牌、「-1向听」为和牌。
6262
63- ### 常规的思考过程
63+ ### 启发式的思考过程
6464
65- 那么如何计算向听数呢?首先我们来思考一下人类玩家自己是怎么计算向听数的。
65+ 那么如何计算向听数呢?首先我们来思考一下人类玩家自己是怎么算的。不妨从和牌型倒着推算:
6666
67671 . 一手已经组成四个面子和一个雀头的牌,是-1向听。
68682 . 将其中一个面子换成一个搭子+一个孤张,则显而易见,这手牌的向听数+1。
69693 . 将其中一个面子换成三个孤张,则向听数+2(其中两个孤张需要替换为第三个孤张的靠张才能形成面子)。
70704 . 如果把雀头换为两张不同的牌,则在组合中没有其他对子的情况下,需要额外替换一张牌形成雀头,此时向听数需要再加1。
71715 . 一手牌最多只需要4组面子+搭子的组合,因此对于搭子数而言,应设置其上限为4-面子数。
7272
73- 根据上面naive的思考,似乎一个完美的算法就这样形成了 :
73+ 根据上面naive的思考,似乎一个完美的算法已经形成了 :
7474
7575对于一手牌的某种拆分方法,记其中的面子数为 $m$,搭子数为 $d$,雀头数为 $q, q\in\left\lbrace0、1\right\rbrace$,可以计算出向听数 $s$ 如下:
7676
@@ -126,20 +126,19 @@ $m = 0,\ d=0,\ q=0,\ s=2\times 4-0-0=8$
126126{% endnote %}
127127
128128
129- 好像基本没问题 。容易发现,对于一手牌而言,其拆分方法可能有很多种,我们需要对它进行不同的拆分,得到上述参数后分别计算向听数,并取其最小值,才能得到最终的向听数。
129+ 好像看起来没什么问题 。容易发现,对于一手牌而言,其拆分方法可能有很多种,我们需要对它进行不同的拆分,得到上述参数后分别计算向听数,并取其最小值,才能得到最终的向听数。
130130
131131### 特殊情形
132132
133133偶然间发现这样一手牌:
134134
135135{% mahjong 11112222333344z %}
136136
137-
138137我们按上面向听数的计算公式,得到:
139138
140139$$ m=3,\ d=0,\ q=1, s=2\times 1-0-1=1 $$
141140
142- 计算结果为1向听,但这一手牌移除3组搭子和一个雀头后 ,剩下的三个孤张「东南西」,没有一张能摸成搭子,故这手牌理应是2向听。
141+ 计算结果为1向听,但这一手牌移除3组面子和一个雀头后 ,剩下的三个孤张「东南西」,没有一张能摸成搭子,故这手牌理应是2向听。
143142
144143这种特殊情况的存在使得原先的算法需要进行一定程度的调整,经过一番搜索,我找到了这篇文章:
145144
@@ -156,7 +155,7 @@ $$m=3,\ d=0,\ q=1, s=2\times 1-0-1=1$$
156155
157156为应对这些复杂的情况,文章作者提出了一些新的参数。
158157
159- - G3:面子(顺子或刻子)的总数。-- group 3
158+ - G3:面子(顺子或刻子)的总数。
160159
161160- G2:搭子(差一张牌就能变成面子)的总数(不能是脏搭子)。
162161
@@ -233,7 +232,7 @@ if P == 0:
233232
234233当没有雀头时,我们至少需要额外一个进张来形成雀头,这会导致向听数+1。不过,根据剩余牌的类型,又能分为几类情况。
235234
236- 首先只有非脏牌的孤张牌才能形成雀头,这要求` R> 0 ` ,此时,我们可以直接用其中的某张孤牌来做雀头;否则,手上所有孤立牌都是爆满的,至少需要再进两张牌才行。这分别对应了上面代码中的 ` s += 1 ` 以及` s += 2 ` 。
235+ 首先只有非脏牌的孤张牌才能形成雀头,这要求` R > 0 ` ,此时,我们可以直接用其中的某张孤牌来做雀头;否则,手上所有孤立牌都处于爆满状态,已经摸不成雀头了,故至少需要再进两张牌才能凑一个雀头出来。这两种情况分别对应了上面代码中的 ` s += 1 ` 以及` s += 2 ` 。
237236
238237既然进来了新的牌凑成了雀头,我们当然得打掉对应数量的牌,按牌的利用价值,我们优先打走脏字牌,其次是脏数牌。最后才会打走孤张非脏牌。
239238
@@ -252,17 +251,17 @@ if DZ > 0:
252251
253252{% mahjong 11112222333344z %}
254253
255- 对于上面这手牌:G3= 3, G2= 0, DG2= 0, P= 1, DN= 0, DZ= 3。另外我们可以计算得到 K = 4, R = 0。
254+ 对于上面这手牌,最优的参数组合是 :G3 = 3, G2 = 0, DG2 = 0, P = 1, DN = 0, DZ = 3。另外我们可以计算得到 K = 4, R = 0。
256255
257- ` DZ -= G2 + 2 * R ` 这一步意味着我们想通过组面子的过程消耗掉一些脏字牌,在这个场景下 ,G2和R均为0,因此最后会剩余3张脏字牌。如果是三个普通的孤张,相比于一个面子而言,会“贡献”两向听,这一部分已经体现在 $s=2\times(4-m)-\max(d,4-m)-q$ 这个公式里了(每3张散牌的存在会减少一个面子),但如果三个孤张都是脏字牌——无法成搭,那么我们还需要额外的一次换牌,将其中一张脏字牌替换成普通的孤张,才能顺利组成面子,因此还需要额外+1。
256+ ` DZ -= G2 + 2 * R ` 这一步意味着我们希望在组面子的过程中消耗掉一些脏字牌。上面这手牌中 ,G2和R均为0,因此最后会剩余3张脏字牌。如果是三个普通的孤张,相比于一个面子而言,会“贡献”两向听,这一部分已经体现在 $s=2\times(4-m)-\max(d,4-m)-q$ 这个公式里了(每3张散牌的存在会减少一个面子),但如果三个孤张都是脏字牌——无法成搭,那么我们还需要额外的一次换牌,将其中一张脏字牌替换成普通的孤张,才能顺利组成面子,因此还需要额外+1。
258257
259258这也是最后这行代码` s += DZ // 3 ` 所做的事。
260259
261260---
262261
263262在进行了以上调整后,函数计算向听数:` return 2 * (K - G3) - G2 + s ` ,这个公式本质上和前面那个naive的公式是一样的。
264263
265- 综上,这个函数对数量达到4张的牌做了更完善的处理 。
264+ 综上,这个函数相当于对数量达到4张的牌做了更完善的处理 。
266265
267266### 3n+1型的向听数计算
268267
347346
348347---
349348
350- 最后嫖了一个huggingface space,把小程序后端部署起来了。欢迎各位立直麻将爱好者使用~~ 测试bug~~
349+ 最后嫖了一个huggingface space,把小程序后端部署起来了。本文相关代码可以在[ 这个space] ( https://huggingface.co/spaces/windshadow/riichi-toolkit/tree/main ) 中找到。
350+
351+ 欢迎各位立直麻将爱好者使用~~ 帮我测试bug~~
351352
352353![ ] ( https://blogfiles.oss.fyz666.xyz/png/0d565e1b-1e03-4a69-903d-76c2aed11ca0.PNG )
0 commit comments