深入解析麻将胡牌判断算法的原理与实现,涵盖标准胡牌、七对子、十三幺等特殊牌型,提供完整代码示例和优化方案。
麻将胡牌算法是判断一手麻将牌是否符合胡牌规则的计算机算法。在麻将游戏中,玩家需要将手中的牌组合成特定的模式才能胡牌。常见的胡牌牌型包括基本胡牌、七对子、十三幺等。
标准的麻将胡牌规则要求手牌必须满足以下条件:
这是一个标准胡牌示例:顺子+刻子+将牌
标准胡牌算法是最常见的胡牌判断方法,适用于大多数麻将规则。其基本思路是通过递归方式尝试所有可能的牌组组合。
标准胡牌算法的时间复杂度取决于递归的深度和分支数量。在最坏情况下,算法需要尝试所有可能的组合方式。
优化方法包括:
function canHu(hand):
if length(hand) != 14: return false
counts = countTiles(hand)
# 尝试每种可能的将牌
for tile in allTiles:
if counts[tile] >= 2:
counts[tile] -= 2
if canFormMeld(counts):
return true
counts[tile] += 2
return false
function canFormMeld(counts):
if all counts are 0: return true
# 优先尝试刻子
for tile where counts[tile] >= 3:
counts[tile] -= 3
if canFormMeld(counts): return true
counts[tile] += 3
# 尝试顺子(仅万条筒)
for tile in numberTiles where counts[tile] > 0:
if tile+1和tile+2存在且counts>0:
counts[tile]--; counts[tile+1]--; counts[tile+2]--
if canFormMeld(counts): return true
counts[tile]++; counts[tile+1]++; counts[tile+2]++
return false
除了标准胡牌外,麻将中还存在多种特殊牌型,这些牌型有各自的胡牌规则,不需要满足标准胡牌的条件。
七对子要求手牌由7个不同的对子组成,不能有重复的对子(某些规则允许重复)。
判断算法: 统计每种牌的数量,检查是否有7种牌的数量恰好为2。
十三幺由13种特定牌各一张,再加上其中任意一张作为将牌组成。
判断算法: 检查手牌是否包含所有13种幺九牌,且其中一种有两张,其余各一张。
全不靠是一种特殊的牌型,由147、258、369不同花色的牌加上字牌组成,每张牌都不靠张。
判断算法: 检查手牌是否符合147、258、369的分布规则,且没有顺子和刻子。
以下是一个简化的麻将胡牌算法JavaScript实现,包含标准胡牌和七对子的判断。
// 麻将胡牌算法实现
class MahjongHuChecker {
constructor() {
// 牌型定义:0-8: 万, 9-17: 条, 18-26: 筒, 27-33: 字牌
this.tileNames = [
'一万','二万','三万','四万','五万','六万','七万','八万','九万',
'一条','二条','三条','四条','五条','六条','七条','八条','九条',
'一筒','二筒','三筒','四筒','五筒','六筒','七筒','八筒','九筒',
'东','南','西','北','中','发','白'
];
}
// 标准胡牌判断
canHuStandard(hand) {
if (hand.length !== 14) return false;
// 统计每种牌的数量
const counts = new Array(34).fill(0);
for (const tile of hand) {
counts[tile]++;
}
// 尝试每种可能的将牌
for (let i = 0; i < 34; i++) {
if (counts[i] >= 2) {
counts[i] -= 2;
if (this.canFormMeld(counts, 4)) {
return true;
}
counts[i] += 2;
}
}
return false;
}
// 递归判断是否能组成所有顺子/刻子
canFormMeld(counts, remainingSets) {
if (remainingSets === 0) return true;
// 找到第一张有牌的牌
let firstTile = -1;
for (let i = 0; i < 34; i++) {
if (counts[i] > 0) {
firstTile = i;
break;
}
}
if (firstTile === -1) return false;
// 尝试刻子
if (counts[firstTile] >= 3) {
counts[firstTile] -= 3;
if (this.canFormMeld(counts, remainingSets - 1)) {
counts[firstTile] += 3;
return true;
}
counts[firstTile] += 3;
}
// 尝试顺子(仅万条筒)
if (firstTile < 27 && firstTile % 9 <= 6) {
if (counts[firstTile + 1] > 0 && counts[firstTile + 2] > 0) {
counts[firstTile]--;
counts[firstTile + 1]--;
counts[firstTile + 2]--;
if (this.canFormMeld(counts, remainingSets - 1)) {
counts[firstTile]++;
counts[firstTile + 1]++;
counts[firstTile + 2]++;
return true;
}
counts[firstTile]++;
counts[firstTile + 1]++;
counts[firstTile + 2]++;
}
}
return false;
}
// 七对子判断
canHuSevenPairs(hand) {
if (hand.length !== 14) return false;
const counts = new Array(34).fill(0);
for (const tile of hand) {
counts[tile]++;
}
let pairCount = 0;
for (let i = 0; i < 34; i++) {
if (counts[i] === 2) {
pairCount++;
} else if (counts[i] !== 0) {
return false; // 有不是对子的牌
}
}
return pairCount === 7;
}
// 综合胡牌判断
canHu(hand) {
return this.canHuStandard(hand) || this.canHuSevenPairs(hand);
}
}
// 使用示例
const checker = new MahjongHuChecker();
const hand = [0, 0, 1, 1, 2, 2, 9, 9, 10, 10, 11, 11, 27, 27]; // 示例手牌
console.log("是否可以胡牌:", checker.canHu(hand));
麻将胡牌算法是麻将游戏AI和麻将游戏开发中的核心技术之一。一个高效的胡牌判断算法可以显著提升游戏性能,特别是在需要实时判断听牌、胡牌可能性的场景中。
1. 位运算优化:使用位运算代替数组操作可以大幅提升算法性能。例如,可以使用34位整数表示每种牌的数量状态。
2. 记忆化搜索:将已计算过的牌型状态存储起来,避免重复计算,特别适用于需要频繁判断胡牌的场景。
3. 预计算表:对于常见牌型,可以预计算胡牌可能性,通过查表方式快速判断。
4. 并行计算:对于多种可能的将牌选择,可以使用并行计算同时尝试,提升多核CPU利用率。
麻将胡牌算法不仅用于判断是否胡牌,还可以应用于以下场景:
标准胡牌算法在最坏情况下的时间复杂度约为O(n!),其中n为手牌数量。但通过优化(如剪枝、记忆化搜索)可以将平均时间复杂度降低到可接受范围。实际应用中,由于麻将牌型有限,算法通常可以在毫秒级完成判断。
花牌通常不计入手牌总数,胡牌时额外计算番数。杠牌分为明杠和暗杠,算法处理时需要:
实现通用麻将胡牌算法需要:
例如,可以创建一个RuleEngine类,根据配置选择不同的HuChecker实现。
优化麻将AI胡牌判断性能的方法包括:
听牌算法的基本思路是:
优化方法: