麻将向听数计算优化:从枚举到毫秒级
向听数计算是麻将AI里调用最频繁的热点函数,朴素枚举在复杂牌型下要尝试数十万种分解,撑不起实时决策。工程路线是按花色分解加面子递推剪枝,再把34维计数数组编码成整数键做记忆化,单次计算可压进毫秒级。本文覆盖向听递推、有效牌权重、攻防切换阈值与四川血战三玩家并行推理的处理。
向听数计算是麻将AI里调用最频繁的热点函数,朴素枚举在复杂牌型下要尝试数十万种分解,撑不起实时决策。工程路线是按花色分解加面子递推剪枝,再把34维计数数组编码成整数键做记忆化,单次计算可压进毫秒级。本文覆盖向听递推、有效牌权重、攻防切换阈值与四川血战三玩家并行推理的处理。
麻将AI的每一次决策都要回答“这手牌离听牌还差几向、打哪张进步最大”,向听数计算因此成为调用频率最高、单次成本最不可控的热点:一次向听计算会在换牌穷举里被成百上千次调用,它慢十倍,整条决策管线就慢十倍。优化它比优化模型更划算。
麻将手牌的空间由34种牌、每种至多4张定义,13张手牌的合法组合在千万量级;向听计算还要在这之上枚举面子分解——同一手牌常有上百种搭子拆法。朴素递归不加剪枝时,单次计算能膨胀到数十万次分解尝试,这就是“从枚举到毫秒级”必须跨过的距离。
有效的优化分三步:先把手牌按万、筒、条三个花色独立分解(字牌单独处理),花色间状态用乘法合并;再在花色内部按“面子、搭子、对子”计数递推,剪掉不可能优于当前最优的分支;最后把34维计数数组编码成整数键做记忆化,重复牌型直接查表。三步之后,单次向听计算进入毫秒级。
// 向听数:按花色分解后合并(标准型,示意)
shanten(hand):
best = 8
for man in decompose(manzu):
for pin in decompose(pinzu):
for sou in decompose(souzu):
for hon in decompose(honors):
sets = man.sets + pin.sets + sou.sets + hon.sets
partial = man.partials + pin.partials + sou.partials + hon.has_pair
if sets > 4: continue // 剪枝:面子超4无效
s = 8 - 2*min(sets, 4) - min(partial, 4 - sets)
best = min(best, s)
return best // 七对/国士另走专用公式,不混入标准型向听数只回答“差几向”,决策还需要“进哪些牌、各剩几张”:对每个候选弃牌计算进张集合与剩余张数,加权得到有效牌期望收益。高效实现的关键是复用向听分解的中间结果——进张集合就是“加一张后向听数下降”的那些牌,缓存分解结果后,受牌计算从再次全量枚举降为一次增量查询。
攻防切换不能只看向听数:一向手的进攻价值取决于有效牌数量(好形打)与危险度(对手节奏、牌河信号)。工程上用双阈值——有效牌期望高于阈值且危险度低于阈值时进攻,两头都不占时防守,中间地带按人设参数走混合路线。阈值放在配置里而不是模型里,运营才能按段位调节AI风格。
血战到底的工程难点是三人缺一门、胡牌即退出、对局持续降元:同一局里AI要在一桌三人、两人、一人的状态间切换推理。实现上把每个存活对手独立建模,向听与危险度按存活人数分桶缓存;胡牌退出者的牌河转为公共信息,供剩余对手的危险度更新复用。并行推理按座位划分任务、无共享写状态,单桌三路决策在50ms预算内并行完成。
30 天全功能免费试用,5 分钟接入首个AI牌局。开通试用请添加商务微信 kxlin0101 或发送邮件至 skyin.lewis@gmail.com。