麻将游戏核心算法:牌型检测与胡牌判断

麻将胡牌算法是棋牌开发中最核心、最容易出bug的模块。一副牌136张,每张牌属于"万筒条字"四类,胡牌公式是 n * AAA + m * ABC + DD。怎么用代码高效检测?本文将深入解析。

一、麻将牌的编码方案

首先要把牌数字化。常见编码方案:

// 0-8 : 一万到九万
// 9-17: 一筒到九筒  
// 18-26: 一条到九条
// 27-33: 东南西北中发白

这样可以快速判断花色:suit = id / 9,点数为 rank = id % 9 + 1。判断顺子时,只需要检查连续的三张牌是否在同一花色内即可。

二、递归回溯胡牌算法

最经典的胡牌判断是回溯+剪枝。思路:尝试取出一对将牌,然后递归判断剩余牌是否能组成刻子或顺子。

function canHu(cards) {
    // 1. 无牌返回true(递归基)
    if (cards.length == 0) return true;
    // 2. 尝试取将牌(仅做一次)
    if (!hasPair) {
        for (每个牌值v,且数量>=2) {
            移除一对v,hasPair=true;
            if (canHu(剩余牌)) return true;
            加回一对v;
        }
        return false;
    }
    // 3. 取刻子或顺子
    let first = 最小牌值的牌;
    if (数量>=3) {
        移除三张first;
        if (canHu(剩余牌)) return true;
        加回三张first;
    }
    // 检查顺子:first,f+1,f+2都存在
    if (花色是万筒条 && 点数是≤7 && 存在连续三张) {
        移除这三张;
        if (canHu(剩余牌)) return true;
        加回三张;
    }
    return false;
}
💡 性能优化:直接递归传递数组效率极低。实际开发中通常把牌编码成数组 int[34] 记录每种牌的张数,每次递归深拷贝或回溯修改,速度可提升10倍以上。

三、通用算法优化

1. 提前剪枝

如果某花色牌总数不是3的倍数(将牌除外),直接返回false。比如万子有7张,除了将牌2张,剩5张无法组成完整的顺子/刻子。

2. 缓存计算结果

麻将局中相同手牌状态(如打出一张牌后)会被重复判断,可以用哈希表缓存。把手牌数组序列化后作为key存入map

3. 特殊牌型检测

七小对、十三幺、全不靠等特殊牌型需要单独优先判断。

// 七小对:每种牌数量只能是0或2,且恰好有7种
function isSevenPairs(cards) {
    let pairCount = 0;
    for (let i=0;i<34;i++) {
        if (cards[i]==2) pairCount++;
        else if (cards[i]!=0) return false;
    }
    return pairCount == 7;
}

四、听牌分析与AI出牌

知道了胡牌算法,就可以实现"听牌分析"——尝试打出一张牌后,剩下的手牌能否胡牌。遍历34种可能的舍牌,若剩余牌可胡,则这张牌是听牌候选。

更进一步的AI可以根据剩余牌概率向听数计算打哪张牌胡牌概率最高。这类算法通常配合蒙特卡洛模拟训练。

五、跨平台实现注意点

  • Lua:很多棋牌游戏用Lua做热更新,递归要注意尾递归优化
  • Go/C++:性能最好,适合服务端批量胡牌校验
  • TypeScript:前后端同构,一套代码可以跑在客户端和服务端(防止客户端作弊)
📞 需要完整的麻将胡牌算法源码(支持四川麻将、广东麻将、国标麻将)?贵阳艺青网络科技提供高精度经过验证的麻将算法库,支持九种麻将变体,欢迎咨询。