很多人做麻将规则整理,习惯从「听牌」开始推,结果越推越乱。
换个角度会轻松得多:先把番型表拆开,让它变成一组可以独立判断的小条件,再把胡牌判定接上去。九江麻将算法列表这类整理工作,本质上做的就是这件事——番型是算法的出口,出口没定义清楚,里面的判定树怎么长都别扭。
为什么先拆番型,而不是先写判定
胡牌判定回答的是「这手牌成不成」,番型回答的是「成了值多少」。两者看着有先后,实际在代码里是耦合的。有些番型要求门清,有些要求杠上开花,这些条件并不等到算分才检查,在判定阶段就得留下痕迹。
所以比较省事的路径是:先列出所有番型,标注每个番型的触发条件属于哪一类,再回头设计判定流程需要采集哪些中间状态。顺序反了,后面改起来很痛苦。
把番型表分成三层来看
基础番:看一眼就能数清
平胡、碰碰胡、清一色这类,靠牌面结构就能定。它们的判定条件几乎不依赖过程信息,只要拿到最终手牌和副露就能算,实现上适合做成纯函数。
组合番:得看上下文
杠上开花、抢杠胡、海底捞月,这些番型的触发点发生在对局流程里,不在牌型里。如果你把它们和基础番混在一张表里、用同一套逻辑跑,很容易出现「牌型对了但时机不对」的误判。
特殊番与封顶
有些玩法设了封顶,到某个番数就不再累加;也有番型之间互斥,比如清一色和混一色不能同时算。这已经不是判定问题,而是结算规则,得单独放一层。
判定和计分怎么衔接
常规做法是先判胡、再算番。但这里有个例外:如果番型自带合法性约束(必须门清、必须自摸),那它其实参与了胡牌是否成立的前置校验。这类番型建议在数据结构里加个字段标记,区分「约束型」和「奖励型」,省得后面写结算时反复回头改。
- 约束型:不满足则该胡牌不成立
- 奖励型:只影响分数,不影响成立
- 互斥型:需要标注互斥组,按最大值或优先级取
落成一张能跑的表
每个番型至少要有这些字段:名称、触发条件、番数、类型、互斥组、是否受封顶影响。听着像废话,可真到写代码时,缺哪个字段都会在某一局里翻车。
- 先用几十个典型牌例做回归用例,保证每个番型至少被覆盖一次
- 再补边界用例:差一张、多一杠、刚好卡在封顶线上
- 拿真实对局回放跑一遍,看有没有出现预期外的组合
需要说明的是,九江本地不同桌的规则细节并不完全统一,口口翻怎么算、杠分怎么叠加,各家说法都有出入。
上面这套拆法更像是骨架,具体番数还得按你实际采用的口径填。别指望一份列表通吃所有房间。
把算法拆成番型,好处不在于让规则变简单,而在于让复杂度可见——你能清楚看到哪部分是判断、哪部分是计算、哪部分只是约定。剩下的就是耐心填空。