江西麻将胡牌算法图标

江西麻将胡牌算法

深入解析·实战代码·常见问题一站式搞定

江西麻将胡牌算法 全解

无论你是棋牌游戏开发者、算法学习者,还是对麻将感兴趣的玩家,这篇文章都能帮你彻底搞懂江西麻将的胡牌判断逻辑。

江西麻将胡牌算法概览示意图

一、江西麻将胡牌算法简介

江西麻将作为中国地方麻将的重要分支,以南昌麻将最具代表性。南昌麻将最大的特色在于「精牌」(也叫宝牌、癞子),大大提升了胡牌的灵活性和趣味性。

胡牌算法是棋牌游戏开发中最核心的模块之一,负责判断玩家当前的手牌是否满足胡牌条件。本文将从基础规则到进阶优化,为你呈现一套完整、可靠且高效的江西麻将胡牌算法方案。

关键词:胡牌判断 · 精牌处理 · 递归回溯 · 七对子 · 碰碰胡
南昌麻将精牌示意图

▲ 南昌麻将精牌(宝牌)玩法示意

二、江西麻将胡牌基本规则

2.1 牌型构成

以南昌麻将为例,使用万、条、筒三种花色,每种花色1~9各4张,共108张牌。部分地区玩法会加入风牌(东南西北中发白),扩展为136张。

2.2 胡牌公式

标准胡牌需满足:n × AAA + m × ABC + DD,其中:

2.3 特殊胡牌牌型

麻将胡牌牌型结构图

▲ 胡牌牌型结构示意图

三、算法核心逻辑

胡牌算法的本质是搜索与回溯。我们将手牌编码为计数数组,通过递归尝试所有可能的将牌、刻子、顺子组合,判断是否存在一种划分方式,使牌型满足胡牌条件。

3.1 数据结构

使用长度为27(或34)的整数数组表示手牌:

数组的每个位置存储该牌的张数(0~4)。

3.2 递归回溯思路

  1. 先检查七对子(快速判断)
  2. 遍历每一种牌型,尝试将其作为「将牌」
  3. 将牌去除后,递归检查剩余牌是否全部由刻子+顺子组成
  4. 递归时遇到不满足条件就剪枝回溯
胡牌算法递归流程图

▲ 递归回溯判断流程图

四、算法实现步骤

下面按步骤拆解完整的胡牌判断流程:

1

牌型编码

将手牌字符串转换为计数数组

2

统计数量

统计每种牌的张数

3

特殊牌型

检测七对子、碰碰胡

4

遍历将牌

尝试每个对子作为将牌

5

递归判断

检查剩余牌是否为面子

6

精牌处理

精牌可替代任意牌

4.1 牌型编码示例

例如:手牌「1万2万3万 7筒7筒7筒 3条4条5条 南南南 5万5万」

编码后数组为:[1,1,1,0,1,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, ...]

五、代码示例(JavaScript)

下面是一段完整的胡牌判断算法实现,支持普通胡牌、七对子、碰碰胡,并预留了精牌处理接口。

/** * 江西麻将胡牌算法 * 支持普通胡牌、七对子、碰碰胡 * 牌型编码:0-8万,9-17条,18-26筒 */ const WAN = 0, TIAO = 9, TONG = 18; /** * 将手牌数组转换为计数数组 * @param {number[]} tiles 手牌数组(已排序) * @returns {number[]} 长度34的计数数组 */ function countTiles(tiles) { const cnt = new Array(34).fill(0); for (const t of tiles) { cnt[t]++; } return cnt; } /** * 主判断函数 * @param {number[]} tiles 手牌(14张) * @returns {boolean} 能否胡牌 */ function isHu(tiles) { const cnt = countTiles(tiles); // 1. 七对子检测 if (isQiduizi(cnt)) return true; // 2. 碰碰胡检测 if (isPengPengHu(cnt)) return true; // 3. 标准胡牌检测 return isStandardHu(cnt); } // 七对子:7个对子 function isQiduizi(cnt) { let pairs = 0; for (let i = 0; i < 34; i++) { if (cnt[i] % 2 !== 0) return false; pairs += cnt[i] / 2; } return pairs === 7; } // 碰碰胡:4个刻子 + 1对将 function isPengPengHu(cnt) { // 尝试每种将牌 for (let i = 0; i < 34; i++) { if (cnt[i] < 2) continue; let temp = [...cnt]; temp[i] -= 2; let ok = true; for (let j = 0; j < 34; j++) { if (temp[j] % 3 !== 0) { ok = false; break; } } if (ok) return true; } return false; } // 标准胡牌:n副刻子 + m副顺子 + 1对将 function isStandardHu(cnt) { // 尝试每种将牌 for (let i = 0; i < 34; i++) { if (cnt[i] < 2) continue; const temp = [...cnt]; temp[i] -= 2; if (canFormMianzi(temp)) { return true; } } return false; } // 递归判断剩余牌是否全部由面子组成 function canFormMianzi(cnt) { // 找到第一张非0牌 let first = -1; for (let i = 0; i < 34; i++) { if (cnt[i] > 0) { first = i; break; } } // 所有牌都组完 if (first === -1) return true; // 尝试刻子 if (cnt[first] >= 3) { cnt[first] -= 3; if (canFormMianzi(cnt)) { cnt[first] += 3; return true; } cnt[first] += 3; } // 尝试顺子(需同花色且连续) if (first < 27 && first % 9 <= 6 && cnt[first + 1] > 0 && cnt[first + 2] > 0) { cnt[first]--; cnt[first + 1]--; cnt[first + 2]--; if (canFormMianzi(cnt)) { cnt[first]++; cnt[first + 1]++; cnt[first + 2]++; return true; } cnt[first]++; cnt[first + 1]++; cnt[first + 2]++; } return false; } // 示例测试 const tiles = [ WAN + 0, WAN + 0, WAN + 0, WAN + 1, WAN + 2, WAN + 3, TONG + 1, TONG + 1, TONG + 1, TIAO + 5, TIAO + 6, TIAO + 7, WAN + 8, WAN + 8 ]; console.log(isHu(tiles)); // true 或 false

💡 精牌(宝牌)处理:在精牌场景中,可将精牌数量单独标识,在递归时优先尝试精牌替代。具体实现可将 canFormMianzi 增加一个参数 wildcard,当实际牌不足时使用精牌补齐。

六、常见问题解答(FAQ)

江西麻将的精牌(宝牌)如何处理?
精牌在胡牌算法中可以视为「赖子」或「万能牌」。处理方式:将精牌数量单独记录,在递归判断时,如果需要的牌实际数量不足,可用精牌补充。例如需要3张5万,但只有2张,有1张精牌,则精牌当作5万使用。
七对子胡牌如何快速判断?
非常简单:遍历计数数组,检查是否每种牌的数量都是偶数,且总对子数等于7。如果满足即可以胡,无需再判断顺子和刻子。
碰碰胡与普通胡牌的关系是什么?
碰碰胡是普通胡牌的一种特殊情况——4副面子全部是刻子,没有顺子。因此碰碰胡的判断可以在标准胡牌判断之前单独检测,也可以被标准胡牌逻辑涵盖(当顺子尝试全部失败但刻子成功时)。
胡牌算法如何优化性能?
常用优化技巧:1)先把数量大于4的牌剪枝掉(不合法);2)优先处理花色多、难以配对的牌;3)使用记忆化搜索,缓存中间状态;4)将牌型编码为位图,用查表法加速;5)提前终止——一旦找到可行方案立即返回。
江西麻将与四川麻将的胡牌算法有何区别?
四川麻将要求缺一门(缺少一种花色),且不能吃牌;江西麻将没有这些限制,且有精牌(宝牌)机制,增加了算法的灵活性和复杂度。核心胡牌判断逻辑相似,但江西麻将需要额外处理精牌替代。
精牌可以替代两张不同的牌吗?
可以。精牌作为万能牌,在递归回溯过程中可以被尝试替代任何一张缺失的牌。算法会对精牌的每种可能用途进行遍历,只要存在一种用法使牌型胡牌,就判定为可胡。

七、结语

江西麻将胡牌算法是棋牌开发中的重要基础,涵盖搜索、回溯、剪枝、状态表达等经典计算机算法思想。通过本文的详细讲解和代码示例,相信你已经对胡牌判断有了全面而深入的理解。

在实际项目中,还需要结合精牌、算番、听牌提示等进阶功能进一步完善。如果你在学习和开发中遇到问题,欢迎在评论区留言交流!