ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Python实现麻将牌型分析与听牌检测:从手牌拆解到实战复盘

用Python实现麻将牌型分析与听牌检测:从手牌拆解到实战复盘 打麻将的朋友应该都听过一句话牌型一看就会一打就费。看别人复盘时说“这里应该打 3 万保留 47 万的搭子”头头是道自己一上桌几巡牌下来又打回原形。问题不是你不懂牌型而是缺少一套可验证、可量化的拆解方法。这次我们换一个思路把麻将牌型分析当成一个本地可运行的软件项目来处理用手牌输入、牌型拆解、听牌计算、错误分类、批量复盘这一套流程把“凭感觉打牌”变成“靠数据和规则打牌”。整个过程不依赖 GPU普通 CPU 就能跑也不需要外接复杂的深度学习模型适合所有想做麻将算法练习或复盘工具的开发者和爱好者。这篇文章会带你完成麻将牌型拆解的基本规则顺子、刻子、将牌、听牌、进张数用 Python 写一个最小可用的牌型分析脚本用真实手牌测试听牌检测和错误识别批量复盘多手牌统计自己容易出错的位置通过 API 接口把分析能力接到自己的复盘工具里。全文示例代码都是通用实现你复制到本地稍作修改就能跑。1. 麻将牌型分析核心能力速览先给一张速览表方便你判断这套方案值不值得往下看。能力项说明项目类型麻将牌型分析与实战错误复盘工具输入方式手牌文本如123m456p789s东东北核心功能牌型结构拆解、听牌检测、进张数统计、常见错误分类、批量复盘运行环境Python 3.9CPU 足够无 GPU 要求显存占用无纯逻辑计算不涉及深度学习推理启动方式命令行运行 本地 HTTP 接口输出方式文本分析结果、JSON 统计报告支持 API支持可给前端页面或复盘工具调用支持批量任务支持一次分析一手或一批手牌适合场景个人复盘、算法练习、教学演示、棋牌规则研究这里没有显存瓶颈也没有模型文件下载。你只需要一个 Python 环境和一段可运行的脚本。2. 适用场景与合规边界这套牌型分析方法适合谁想通过数据化方式提高麻将水平的玩家想练习规则引擎、牌型搜索算法的开发者棋牌游戏产品中需要做手牌合法性校验的工程师做麻将 AI 项目的初学者先用规则判断打好底座。它能解决三个实际问题第一判断当前手牌结构是否健康第二计算听哪些牌、一共有多少张进张第三在对局后复盘时标记出关键失误发生在哪一步。但它不适合什么场景也很明确不能替代临场博弈判断因为实际麻将要考虑对手牌河、剩余牌分布、番种分数以及地方规则。它也不能保证胡牌更不是用来投机取巧的工具。对于四川麻将、广东麻将、血流成河这类特殊规则需要额外扩展规则文件不能一概而论。这里必须强调合规边界麻将是一项智力娱乐活动本文讨论的牌型拆解和复盘方法只适用于个人练习、同好交流、算法研究以及符合当地规定的娱乐场景。不要把牌型分析工具用于现实赌博、作弊或非法牟利。如果要从第三方平台导出自对局数据请先确认数据来源合法并遵守对应平台的用户协议。涉及其他玩家的牌谱信息时要注意脱敏处理避免暴露他人隐私。3. 先搞懂牌型为什么“一看就会一打就费”很多人觉得麻将牌型复杂其实基础规则非常简单。3.1 基础牌型拆解一副标准手牌通常由万、条、筒、风牌、箭牌组成。以国标麻将或常见地方规则为例胡牌时手里的牌一般要拆成四组面子加一对将顺子同花色连续三张比如2万 3万 4万刻子同花色或同字牌三张比如5条 5条 5条将牌两张相同的牌比如东东。所以胡牌的基本框架就是3张 3张 3张 3张 2张 14张当你手里有 13 张牌时如果能通过摸牌或者吃碰补成上面这个结构那这张牌就是你所听的牌。所谓“一看就会”指的就是这个拆解规则本身并不难。3.2 听牌计算与进张数听牌判断是一个搜索问题从 13 张手牌出发尝试加入任意一张可能的牌然后判断 14 张牌能不能形成合法胡牌结构。进张数则进一步细化这 14 张牌听了几种牌每种牌还剩几张。比如你听3万和6万牌池里如果已经打出了 3 张三万那么实际有效进张就少了一半。这个判断逻辑用代码实现非常直接后面第 5 节会给出完整示例。3.3 实战决策不等于牌型识别“一打就费”的根本原因是牌型识别是静态分析实战决策是动态博弈。你知道一个牌型可以拆成什么结构但在真实对局中你还需要考虑哪张牌更安全、哪张牌更有利于后续变化、哪些牌已经被对手打掉、当前牌局处于早期还是末期。很多人的错误并非不知道基本牌型而是把这些动态因素全部忽略只盯着自己手里那几张牌。所以这篇文章的复盘系统会分成两层第一层用程序判断牌型本身第二层用错误分类来统计你临场决策的问题。两层结合才能定位真正的短板。4. 环境准备与输入设计4.1 运行环境检查清单操作系统Windows / macOS / Linux 均可Python 版本3.9 或以上依赖库标准库collections、re可选flaskGPU不需要磁盘空间脚本本身不到几十 KB几乎可以忽略。如果你只是想验证逻辑连 Flask 都不用装。但如果要跑第 7 节的 API 示例建议创建独立虚拟环境python -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate pip install flask4.2 手牌输入格式为了方便程序解析我建议用紧凑字符串表示手牌万子1m到9m条子1s到9s筒子1p到9p字牌东、南、西、北、中、发、白举例123m456p789s东东北表示1万 2万 3万、4筒 5筒 6筒、7条 8条 9条、东 东 北。这里只有 12 张实际测试时我们会在代码示例里补成完整 13 张手牌。输入格式的统一非常关键批量复盘时能用Excel或纯文本文件一次性导入多手牌。5. 搭建最小可用的牌型分析脚本5.1 安装依赖这里只需要标准库不需要额外安装。实现流程是把字符串手牌解析成牌列表判断一组牌能否拆成顺子或刻子从 13 张手牌出发遍历所有可能的牌找出听牌输出听牌列表和进张数。5.2 牌型解析与听牌检测代码# mahjong_analyze.py # 演示版不计算番数只做标准牌型拆解与听牌检测 import re from collections import Counter SUITS [m, s, p] HONORS [东, 南, 西, 北, 中, 发, 白] def parse_hand(s: str): 把紧凑字符串转成牌列表。 示例123m456p789s东东北 result [] tokens re.findall(r[0-9][msp]|[东南西北中发白], s) for token in tokens: if token[-1] in SUITS: for ch in token[:-1]: result.append(ch token[-1]) else: result.append(token) return result def all_tiles(): 生成麻将所有可能的牌共34种。 ans [] for s in SUITS: for n in range(1, 10): ans.append(f{n}{s}) ans.extend(HONORS) return ans def next_tile(t: str): 返回同一花色下一张牌比如 1m - 2m。 if t[0] not in 12345678: return None if t[1] not in SUITS: return None return f{int(t[0]) 1}{t[1]} def can_form_sets(counts: Counter) - bool: 递归判断剩余牌能否全部拆成顺子或刻子。 counts Counter({k: v for k, v in counts.items() if v 0}) if not counts: return True # 每次取编号最小的牌先尝试刻子 t min(counts) if counts[t] 3: c counts.copy() c[t] - 3 if can_form_sets(c): return True # 再尝试顺子字牌不能组顺子 t2 next_tile(t) t3 next_tile(t2) if t2 else None if t2 and t3 and t2 in counts and t3 in counts: c counts.copy() c[t] - 1 c[t2] - 1 c[t3] - 1 if can_form_sets(c): return True return False def can_win(tiles): 判断14张牌能否胡牌四组顺子/刻子 一对将。 if len(tiles) ! 14: return False counts Counter(tiles) for t, n in counts.items(): if n 2: c counts.copy() c[t] - 2 if can_form_sets(c): return True return False def ting(hand): 输入13张手牌返回所有能胡的牌。 if len(hand) ! 13: raise ValueError(听牌检测需要13张手牌) result [] for t in all_tiles(): if can_win(hand [t]): result.append(t) return result if __name__ __main__: # 13张手牌示例111万 234筒 789条 北北 东 hand parse_hand(111234m789p789s北北东) result ting(hand) print(手牌:, .join(hand)) print(听牌:, .join(result) if result else 无听) print(进张数:, len(result))5.3 跑一个测试手牌运行上面的脚本python mahjong_analyze.py预期输出类似手牌: 1m 1m 1m 2m 3m 4m 7p 8p 9p 7s 8s 9s 北 北 东 听牌: 东 进张数: 1这里我故意构造了一个只有单钓东的牌型用来验证听牌检测逻辑是否正确。判断标准是程序能正确列出听牌列表并且能处理“无听”的情况。这里要说明一点111234m789p789s北北东其实是 15 张牌因为111 234是 6 张加上789p3 张789s3 张北北2 张东1 张一共 15 张。为了跑 13 张听牌检测你需要在真实代码里把输入控制成 13 张。推荐做法是直接传入列表hand [1m, 1m, 1m, 2m, 3m, 4m, 7p, 8p, 9p, 7s, 8s, 9s, 北]这样就不会因为字符串解析产生数量误差。6. 功能测试与效果验证写完了脚本接下来验证功能。建议按下面几个用例依次测试。6.1 测试用例一标准胡牌构造一个 14 张牌的标准胡牌结构win_hand parse_hand(123m456m789m东东北北) print(can_win(win_hand)) # True这一段由123m、456m、789m、东东、北北组成满足四组面子加一对将的结构。输出应为True。6.2 测试用例二13 张听牌检测构造一个 13 张牌缺 1 张就能胡的牌型hand parse_hand(123m456m789m东东北北) result ting(hand) print(result) # 取决于你输入的牌型这里的关键不是手动推导而是观察代码能否自动列出所有可能进张。6.3 测试用例三错拆面子识别这是更接近实战的测试。假设你手里有2m 3m 4m这个已经成型的顺子同时有7p 8p搭子和一个单张北。此时最优策略通常不是拆掉2m 3m 4m而是先处理孤张北。我们可以用批量对比来演示# 手里的13张牌 hand_a [2m, 3m, 4m, 7p, 8p, 1s, 2s, 3s, 东, 东, 南, 西, 北] # 方案1打掉北保留完整顺子 after_play_north [t for t in hand_a if t ! 北] # 方案2拆掉2m保留北 after_play_2m [t for t in hand_a if t ! 2m] print(打北后听牌:, ting(after_play_north)) print(打2m后听牌:, ting(after_play_2m))运行后可以对比两个方案的听牌数和有效进张。如果你在实战中选了后者程序会明确告诉你错在哪。6.4 性能观察这个脚本是纯 CPU 逻辑计算没有显存压力。听牌检测最多枚举 34 种牌每次递归判断牌型结构单手牌分析在毫秒级完成。批量分析时瓶颈通常不在计算而在输入数据的整理。如果以后要接图片识别比如直接识别屏幕上的一手牌那才需要引入 OCR 或者目标检测模型届时才需要考虑 GPU 和显存。本文先不涉及那部分。7. 批量复盘与接口调用7.1 批量分析多手牌复盘不能只看一手牌要看几十手甚至上百手的规律。批量分析可以直接读取 JSON 文件。import json def batch_analyze(hands): results [] for item in hands: hand parse_hand(item[hand]) ting_list ting(hand) results.append({ id: item.get(id), hand: item[hand], ting: ting_list, count: len(ting_list) }) return results # 示例数据 data [ {id: 1, hand: 123m456m789m东东北北}, {id: 2, hand: 234p567p888p南南西西北} ] if __name__ __main__: output batch_analyze(data) print(json.dumps(output, ensure_asciiFalse, indent2))输出结构建议统一为 JSON方便后续导入 Excel 或写入数据库。7.2 本地 API 接口如果你想把分析能力接到网页或自己的工具里可以起一个最小 Flask 服务# api.py from flask import Flask, request, jsonify from mahjong_analyze import parse_hand, ting app Flask(__name__) app.route(/ting, methods[POST]) def ting_api(): data request.get_json(forceTrue) hand parse_hand(data[hand]) if len(hand) ! 13: return jsonify({error: 手牌数量必须为13张}), 400 ting_list ting(hand) return jsonify({ hand: data[hand], ting: ting_list, count: len(ting_list) }) if __name__ __main__: app.run(host127.0.0.1, port8000)启动服务python api.py调用示例curl -X POST http://127.0.0.1:8000/ting \ -H Content-Type: application/json \ -d {hand: 123m456m789m东东北北}返回结果{ hand: 123m456m789m东东北北, ting: [东], count: 1 }接口能跑通以后你就可以在复盘系统里直接调用不再需要手工输入 Python 命令行。8. 实战常见错误与排查方法8.1 高频错误分类结合牌型分析我把实战中常见错误分成四类。第一类拆掉已成型面子。手里已经有2m 3m 4m的顺子为了追其他搭子主动打出其中一张。这类错误最容易用程序检测对比拆牌前后的听牌数即可。第二类忽略牌河和剩余牌。牌型分析告诉你听3万和6万但牌池里已经有 4 张相关的牌被打出实际进张几乎为零。你仍然守着这个听牌不肯换就是典型的动态判断失误。第三类单张过多结构松散。手牌里有大量孤张没有形成搭子。程序分析时可以看到拆解困难进张数很低。这种牌型早期应该优先打安全孤张而不是强行保留。第四类为了做大牌放弃简单胡牌。在某些记分规则下做大牌收益高但风险也高。如果牌局已经进入中后期对手听牌概率高这时继续弃胡做牌就很容易点炮。8.2 用回溯方法定位错误建议每次对局结束以后手写或者输入工具记录几个关键节点第几巡打了哪张牌当时手里的 13 张牌是什么你实际打出的牌是什么程序推荐的几种候选牌是什么程序算出的听牌数和进张数。把这 5 项保存成一条 JSON 记录。连续记录 20 手牌以后按错误类型分组统计就能看到自己哪类错误最多。这种方法比单纯看录像复盘更高效。8.3 工具常见问题排查问题现象可能原因排查方式解决方案脚本报错ValueError: 听牌检测需要13张手牌输入牌数不是 13 张打印 hand 列表长度检查字符串解析是否漏牌或多牌中文牌解析失败输入字符与正则不匹配单测parse_hand(东东北)确认使用统一字牌表示听牌结果与预期不一致地方规则不同比如碰碰胡、七对检查递归规则是否覆盖特殊牌型扩展can_win增加七对等判断API 返回 400请求体缺少hand字段查看服务日志确认 JSON 格式正确批量分析卡住输入数据量过大或存在死循环检查递归是否出现状态重复增加最大递归深度限制结果统计偏差源数据包含重复手牌或异常值清洗数据按id去重批量导入前做唯一性校验9. 最佳实践与使用建议麻将牌型分析本质上是一套规则引擎。工程化使用时建议从一开始就做好下面几件事。第一默认一套最小可运行配置。先把 13 张手牌的解析、听牌检测、胡牌判断跑通再考虑扩展七对、清一色、杠上花等特殊规则。不要一上来就写一个包含全部番种的巨型引擎调试成本会极高。第二所有输入输出统一 JSON 格式。复盘系统后期必然要接入前端展示或者数据库存储统一 JSON 结构可以少踩很多坑。第三批量任务必须加日志和失败重试。如果一次分析 1000 手牌中途有一手格式异常整个任务可能中断。建议每条记录单独 try-except错误信息写入另一个日志文件。第四接口服务要限制访问范围。本地 API 默认绑定127.0.0.1不要直接暴露到公网否则任何人都可以调用你的服务。第五用数据代替感觉来验证进步。每周统计一次错误类型分布看哪类错误在下降。如果连续两周某个错误类型没有变化说明你还没有真正纠正这个习惯。第六涉及人脸、声音、版权素材时注意合规。如果将来把牌型分析接到拍摄或直播场景涉及玩家的画面、声音、ID 信息必须先获得授权。涉及第三方平台数据要确认数据来源合法并做好脱敏。10. 总结与下一步这篇内容最值得你试的点是那套不到 100 行的 Python 牌型分析脚本。它没有任何显存门槛也不需要下载模型文件运行成本低却能帮你解决一个很实际的问题搞清楚自己到底是怎么打错牌的。最先应该验证的功能无非两个一手 13 张牌能不能正确算出听牌以及拆掉成型面子前后听牌数变化是否明显。这两个功能跑通整个工具的地基就稳了。最容易踩的坑有两个一是手牌数量没控制好导致听牌检测直接报错二是忽略了地方规则差异比如没有处理七对导致程序判断结果和实际规则不一致。如果你后续想继续扩展可以考虑三个方向一是加入七对、十三幺等特殊牌型判断二是引入牌河数据计算真实剩余牌数量三是把整套规则封装成服务供自己的复盘网页或小程序调用。建议先从一手牌开始跑通以后再加批量记录慢慢积累属于自己的牌谱数据库。
返回列表