1. 四川麻将胡牌规则基础解析
四川麻将作为国内流行的地方麻将玩法,其胡牌规则与国标麻将存在显著差异。在成都地区流行的"血战到底"玩法中,胡牌类型主要分为平胡、普通胡和七对三种基本形态。理解这些胡牌规则的核心逻辑,是开发自动检测算法的前提条件。
首先需要明确四川麻将的基本牌型构成:使用108张牌(万、条、筒各36张,无风牌和箭牌),玩家初始摸13张牌,胡牌时需要满足14张牌的特定组合。与国标麻将最大的区别在于四川麻将不允许吃牌,只能碰、杠,这使得牌型组合方式相对简化。
1.1 平胡的基本构成要件
平胡是四川麻将中最基础的胡牌形式,需要满足以下条件:
- 14张牌必须组成4个顺子或刻子(三张相同牌)加一对将牌
- 特殊情况下允许"四归一"(一个数字牌同时用于两个顺子)
- 不能有任何杠牌操作(暗杠、明杠都会改变胡牌类型)
- 必须满足"缺一门"规则(手牌中只能有两种花色)
典型平胡示例:
code复制23万 45万 67万 888万 55条
这个牌型包含三个顺子(23万、45万、67万)、一个刻子(888万)和一对将牌(55条),且只有万和条两种花色,符合平胡所有条件。
1.2 普通胡的特殊性判定
普通胡在四川麻将中专指带有特定牌型组合的胡法,主要包括:
- 包含至少一个刻子(三张相同牌)
- 允许有杠牌操作(需区分明杠和暗杠)
- 仍然需要满足"缺一门"的基本要求
- 可以包含特殊牌型如"将对"(由2、5、8组成的刻子或将对)
与平胡的关键区别在于普通胡不强制要求所有组合都是顺子,允许更灵活的牌型组合。例如:
code复制111万 555万 789万 22条 33条
这个牌型包含两个刻子(111万、555万)和一个顺子(789万),虽然有两对将牌看似不符合常规,但在特定规则下可以视为"七对"的变种。
1.3 七对牌的独特结构
七对是四川麻将中较特殊的胡牌形式,其特点是:
- 由7个对子组成,共14张牌
- 不允许有任何顺子或刻子
- 四个相同牌可以拆分为两个对子
- 同样需要遵守"缺一门"规则
典型七对示例:
code复制11万 22万 33万 44条 55条 66条 77条
值得注意的是,某些地区规则中"龙七对"(含四张相同牌)算作更高番型,但在基础七对检测中我们暂不考虑这种复杂情况。
关键记忆点:四川麻将胡牌检测必须首先验证"缺一门"规则,这是所有胡牌形式的前提条件。在实际算法实现中,应当优先进行花色校验,可以大幅减少无效计算。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 胡牌检测算法核心架构设计
实现一个健壮的胡牌检测系统需要建立合理的算法架构。基于麻将牌型的组合数学特性,我们可以采用递归回溯的算法框架,配合必要的预处理优化,来高效判定各种胡牌类型。
2.1 牌型数据结构的表示方法
首先需要确定麻将牌在程序中的表示方式。推荐采用紧凑的数值编码方案:
python复制class MahjongTile:
def __init__(self, suit: str, value: int):
self.suit = suit # 'w'(万), 't'(条), 'b'(筒)
self.value = value # 1-9
def __eq__(self, other):
return self.suit == other.suit and self.value == other.value
对于手牌的集合表示,使用字典结构可以高效统计各牌型的数量:
python复制hand_tiles = {
'w': [0]*10, # 万子,索引1-9使用
't': [0]*10, # 条子
'b': [0]*10 # 筒子
}
这种表示方法可以快速查询特定牌的数量,例如hand_tiles['w'][5]表示5万的数量。
2.2 基础胡牌检测算法流程
基于回溯的胡牌检测核心流程如下:
-
预处理阶段:
- 校验手牌数量是否为14张(未胡牌状态)
- 检查"缺一门"规则是否满足
- 统计各花色牌数,确定有效花色
-
七对快速检测:
- 统计所有牌的数量
- 检查是否恰好有7个对子(每种牌的数量为0或2)
-
常规胡牌检测:
- 尝试提取将牌(数量≥2的牌)
- 对剩余牌进行顺子/刻子组合检测
- 使用递归回溯验证所有可能的组合方式
-
特殊牌型处理:
- 检测杠牌情况(四张相同牌)
- 处理"四归一"等特殊组合
2.3 递归回溯算法的实现细节
核心递归函数伪代码:
python复制def is_regular_win(tiles: dict, has_pair: bool) -> bool:
if 所有牌数量为0:
return has_pair
for 每种牌 in tiles:
if tiles[牌] >= 3: # 尝试刻子
tiles[牌] -= 3
if is_regular_win(tiles, has_pair):
return True
tiles[牌] += 3
if 可以组成顺子: # 尝试顺子
减少顺子对应的牌
if is_regular_win(tiles, has_pair):
return True
恢复顺子对应的牌
if not has_pair and tiles[牌] >= 2: # 尝试将牌
tiles[牌] -= 2
if is_regular_win(tiles, True):
return True
tiles[牌] += 2
return False
这个算法的时间复杂度在最坏情况下为O(3^n),但由于麻将牌型的限制(每种牌最多4张,总牌数固定),实际运行效率可以接受。
3. 平胡与普通胡的具体实现差异
虽然平胡和普通胡在牌型组合上相似,但在具体实现时需要处理不同的规则细节,这些差异直接影响算法的判定逻辑。
3.1 平胡的严格条件验证
平胡检测需要额外检查以下约束条件:
- 无杠牌原则:手牌中不能有任何四张相同的牌
- 顺子优先:牌型组合中至少包含一个顺子
- 刻子限制:最多只能有一个刻子(三张相同牌)
实现时需要修改基础算法:
python复制def is_pinghu(tiles: dict) -> bool:
if count_kongs(tiles) > 0: # 检查杠牌
return False
pair_used = False
sequence_count = 0
triplet_count = 0
# 修改后的回溯逻辑
def backtrack(remaining, used_pair, seq_cnt, tri_cnt):
if all(v == 0 for v in remaining.values()):
return used_pair and seq_cnt >= 1
for tile in remaining:
if remaining[tile] >= 3 and tri_cnt < 1: # 刻子检测
remaining[tile] -= 3
if backtrack(remaining, used_pair, seq_cnt, tri_cnt + 1):
return True
remaining[tile] += 3
if can_form_sequence(tile): # 顺子检测
form_sequence(tile)
if backtrack(remaining, used_pair, seq_cnt + 1, tri_cnt):
return True
undo_sequence(tile)
if not used_pair and remaining[tile] >= 2: # 将牌检测
remaining[tile] -= 2
if backtrack(remaining, True, seq_cnt, tri_cnt):
return True
remaining[tile] += 2
return False
return backtrack(tiles.copy(), False, 0, 0)
3.2 普通胡的宽松处理规则
普通胡的检测相对宽松,主要特点是:
- 允许杠牌:四张相同牌可以视为杠牌
- 组合自由:不限制顺子和刻子的比例
- 特殊牌型:可以包含将对等特殊组合
算法实现上可以直接使用基础胡牌检测,但需要增加杠牌识别:
python复制def is_normal_win(tiles: dict) -> bool:
# 先检查七对
if is_seven_pairs(tiles):
return True
# 检查普通胡牌
return is_regular_win(tiles, False)
def contains_kong(tiles: dict) -> bool:
return any(count == 4 for count in tiles.values())
3.3 牌型检测的优先级策略
在实际游戏中,一手牌可能同时满足多种胡牌条件,此时需要明确检测优先级:
- 首先检查七对(因其条件最严格)
- 然后检查平胡(条件比普通胡更严格)
- 最后检查普通胡(最宽松的条件)
这种优先级设置可以避免误判,例如一手牌可能同时满足平胡和普通胡条件,但按照规则应该识别为平胡。
python复制def check_win_type(tiles: list) -> str:
tile_dict = convert_to_dict(tiles)
if not check_color_rule(tile_dict): # 缺一门检查
return "not_winning"
if is_seven_pairs(tile_dict):
return "seven_pairs"
elif is_pinghu(tile_dict):
return "pinghu"
elif is_normal_win(tile_dict):
return "normal_win"
else:
return "not_winning"
实战经验:在四川麻将中,七对和平胡的番数通常比普通胡高,因此在检测算法中优先识别这些特殊牌型对实现游戏逻辑非常重要。同时要注意地区规则差异,有些地方七对不算平胡,而有些地方则有"清一色"等额外规则。
4. 七对检测的特殊处理与优化
七对作为四川麻将中的特殊胡牌形式,其检测逻辑与常规胡牌完全不同。实现高效的七对检测需要专门的处理策略。
4.1 基础七对检测算法
七对的基本判定条件非常明确:
- 手牌正好14张
- 所有牌都成对出现(共7对)
- 满足"缺一门"规则
基础实现方法:
python复制def is_seven_pairs(tiles: dict) -> bool:
pair_count = 0
for suit in tiles:
for value in range(1, 10):
count = tiles[suit][value]
if count == 0:
continue
if count == 2:
pair_count += 1
elif count == 4: # 四张相同牌可拆为两对
pair_count += 2
else: # 1或3张都不符合七对
return False
return pair_count == 7
4.2 处理四张相同牌的情况
四川麻将规则中,四张相同的牌(如四个5万)在七对中可以有两种处理方式:
- 视为两对(主流规则)
- 视为一杠加一对(某些特殊规则)
我们的实现采用第一种更普遍的方式:
python复制def count_pairs(tiles: dict) -> int:
pairs = 0
for suit in tiles:
for value in range(1, 10):
count = tiles[suit][value]
if count >= 2:
pairs += count // 2
return pairs
4.3 七对检测的性能优化
由于七对检测不需要考虑牌的组合顺序,我们可以采用更高效的统计方法:
- 提前终止检查:在遍历过程中发现任何牌的数量为1或3时立即返回失败
- 并行统计:使用位运算或向量化操作加速计数
- 记忆化:对常见牌型缓存检测结果
优化后的实现:
python复制def is_seven_pairs_optimized(tiles: dict) -> bool:
total_pairs = 0
for suit in tiles:
suit_tiles = tiles[suit]
for value in range(1, 10):
cnt = suit_tiles[value]
if cnt == 1 or cnt == 3:
return False
if cnt == 2:
total_pairs += 1
elif cnt == 4:
total_pairs += 2
# 0的情况无需处理
return total_pairs == 7
4.4 七对与普通胡的边界情况
某些特殊牌型可能同时满足七对和普通胡的条件,例如:
code复制11万 22万 33万 44万 55万 66万 77万
这种情况下,按照四川麻将一般规则应该优先识别为七对,因为七对是更高番型的胡法。
实现时需要调整检测顺序:
python复制def check_win_type_advanced(tiles):
if is_seven_pairs(tiles):
return "seven_pairs"
elif is_pinghu(tiles):
return "pinghu"
elif is_normal_win(tiles):
return "normal_win"
else:
return "not_winning"
5. 完整实现与测试案例
将上述各个模块整合成完整的胡牌检测系统,并通过典型测试案例验证其正确性。
5.1 系统架构与模块整合
完整系统的类结构设计:
python复制class SichuanMahjongChecker:
def __init__(self):
self.tile_counts = {'w': [0]*10, 't': [0]*10, 'b': [0]*10}
def set_hand_tiles(self, tiles: list):
"""初始化手牌"""
self._reset_counts()
for tile in tiles:
self.tile_counts[tile.suit][tile.value] += 1
def check_win_type(self) -> str:
"""主检测方法"""
if not self._check_color_rule():
return "not_winning"
if self._is_seven_pairs():
return "seven_pairs"
elif self._is_pinghu():
return "pinghu"
elif self._is_normal_win():
return "normal_win"
else:
return "not_winning"
# 其他辅助方法...
5.2 典型测试案例设计
验证平胡的案例:
python复制def test_pinghu():
checker = SichuanMahjongChecker()
tiles = [
MahjongTile('w',2), MahjongTile('w',3),
MahjongTile('w',4), MahjongTile('w',5),
MahjongTile('w',6), MahjongTile('w',7),
MahjongTile('t',5), MahjongTile('t',5),
MahjongTile('t',5), MahjongTile('b',1),
MahjongTile('b',1), MahjongTile('b',1),
MahjongTile('b',2), MahjongTile('b',3)
]
checker.set_hand_tiles(tiles)
assert checker.check_win_type() == "pinghu"
验证七对的案例:
python复制def test_seven_pairs():
checker = SichuanMahjongChecker()
tiles = [
MahjongTile('w',1), MahjongTile('w',1),
MahjongTile('w',2), MahjongTile('w',2),
MahjongTile('w',3), MahjongTile('w',3),
MahjongTile('t',4), MahjongTile('t',4),
MahjongTile('t',5), MahjongTile('t',5),
MahjongTile('t',6), MahjongTile('t',6),
MahjongTile('t',7), MahjongTile('t',7)
]
checker.set_hand_tiles(tiles)
assert checker.check_win_type() == "seven_pairs"
5.3 边界条件与异常处理
需要考虑的特殊情况包括:
- 牌数不正确(不是13或14张)
- 无效的牌值(如0万或10条)
- 违反"缺一门"但牌型正确
- 同时满足多种胡牌条件的情况
异常处理实现示例:
python复制def set_hand_tiles(self, tiles: list):
if len(tiles) not in [13, 14]:
raise ValueError("手牌数量必须为13或14张")
self._reset_counts()
for tile in tiles:
if tile.value < 1 or tile.value > 9:
raise ValueError("牌值必须在1-9范围内")
self.tile_counts[tile.suit][tile.value] += 1
5.4 性能测试与优化建议
对于麻将游戏来说,胡牌检测的性能至关重要。可以通过以下方式优化:
- 预计算哈希:为常见牌型预先计算哈希值,建立缓存
- 并行检测:对不同的胡牌类型使用并行线程检测
- 早期终止:在统计过程中发现不可能胡牌时立即终止
- SIMD优化:使用现代CPU的向量指令加速统计计算
性能测试示例:
python复制import timeit
def benchmark():
checker = SichuanMahjongChecker()
tiles = [...] # 典型牌型
checker.set_hand_tiles(tiles)
def test():
checker.check_win_type()
time = timeit.timeit(test, number=1000)
print(f"平均检测时间: {time*1000:.3f}ms")
在实际项目中,经过优化的胡牌检测算法可以在0.1毫秒内完成一次检测,完全满足实时游戏的需求。
