麻将胡牌算法是棋牌开发中最核心、最容易出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:前后端同构,一套代码可以跑在客户端和服务端(防止客户端作弊)
📞 需要完整的麻将胡牌算法源码(支持四川麻将、广东麻将、国标麻将)?贵阳艺青网络科技提供高精度经过验证的麻将算法库,支持九种麻将变体,欢迎咨询。