news 2026/9/8 9:58:53

麻将实战牌型决策:用Python听牌判断器量化牌效,告别“一看就会一打就费”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
麻将实战牌型决策:用Python听牌判断器量化牌效,告别“一看就会一打就费”

麻将实战中,最常见的牌型问题不是不认识牌,而是“看一眼就懂,一上场就错”。很多玩家在复盘时看到高手拆掉一个看似不错的搭子,或者听一张冷门牌,会觉得自己也会这么打;真正轮到自己出牌,却常常在几个选项之间犹豫,最后凭感觉打出错误的牌。“麻将实战牌型一看就会,一打就费”说的就是这种落差。

问题不在于记忆力,而在于判断标准。看牌型只需要回答“现在听起来是哪几张”,打牌却要回答“下一步怎么做才更接近和牌”。这两个问题看着是一回事,实际上需要完全不同的工具:一个是牌型识别,一个是决策计算。全文会用牌效率的概念说明为什么看会了还会打错,并用一个只依赖 Python 标准库的听牌判断器,把听牌范围、剩余枚数这些关键指标算出来,再用结果复盘常见错误,最后给出一张出牌前可以逐项对照的检查清单。

1. 为什么“一看就会,一打就费”?

1.1 看懂牌型不等于会做取舍

牌型识别只回答“现在是什么状态”,决策要回答“现在该做什么”。比如看到 123456789m 3455p,你可以一眼看出这手牌听 2p 和 5p;但出牌前真正要回答的问题是:上一手应该打 3p 还是 6p?这个选择需要比较进张、剩余枚数、安全度和对手行为。

“一看就会”通常停留在第一层:能认出牌长什么样,能说出听哪些牌。“一打就费”则发生在第二层:面对两个以上选项时,没有稳定的标准来决定选哪个。而第二层能力并不是靠多看牌型就能练出来的,它需要一个可重复的计算框架。

用一句通俗的话说:知道这手牌听什么,是“会读”;知道为什么这么打,是“会打”。大多数玩家缺的是后者。

1.2 牌型判断的四个可计算指标

要减少“凭感觉”,先把牌型判断拆成可计算的指标。下面四个指标在实战中经常被混为一谈,但它们解决的是不同问题。

指标含义计算方式新手误区
向听数距离和牌还差几次有效进张当前手牌距离“4 组面子加 1 对将牌”的差距只看有没有听,不看差几手
有效牌摸到后能让向听数减少的牌枚举下一巡可能摸到的牌,计算对向听的影响把任何可能的牌都算成进张
听牌种类当前听哪几张牌枚举 34 种牌,加入后能和牌的就是听牌只会数种类,不数剩余张数
剩余枚数某张牌在牌山中还可能存在的数量从 4 张总数中减去手牌、河牌、副露和可见牌只看自己手里有没有,不看场上

这四个指标的意义在于,把“感觉这手牌不错”换成“这手牌有 6 个进张,其中 2 个已经出现在河牌”。用数字代替直觉,才能避免每次都用同一个错误理由说服自己。

1.3 为什么知道指标还会打错

知道指标只完成了第一步,第二步是在真实牌局里快速获取可见信息,并给候选项排序。信息不全、时间压力、情绪波动都会让判断退化。新手最常犯的错,是在信息不足时过早做决定,比如序盘就固定单钓一张,或者明明还能拆搭子,却先打了安全牌。

“一看就会,一打就费”的另一个原因,是复盘时只核对结果,不核对过程。胡了一手牌就认为打法对,没胡就认为运气差,这样的复盘不会积累经验。正确做法是把每一手牌都看作一个决策样本:当时的可见牌有哪些,可选方案有哪些,最终选了什么,为什么这么选。

2. 先准备一个可以运行的牌型判断环境

2.1 教学规则和简化约定

为了把牌型判断变成可运行代码,先把规则做简化:只讨论普通和牌型,也就是 4 组面子加 1 对将牌。牌种只保留万、筒、条三类,去掉字牌;不处理七对、国士、役种判定。这种简化不影响本篇讨论的核心:进张和听口判断。

如果你是打日麻、川麻或国标麻,规则细节会有差异,但听牌判断和牌效率的基础逻辑一致。在这个环境下跑通的代码,后续可以根据你的规则扩展。

2.2 Python 环境要求

只需要 Python 3.6 以上版本,不用安装任何第三方库。新建一个mahjong_helper.py文件,把下面的代码放进去,在命令行运行即可。

python --version

如果能正常输出 Python 版本号,环境就准备好了。使用 Jupyter Notebook、VS Code 或 PyCharm 都可以,代码不依赖特定编辑器。

2.3 手牌怎么表示

用类似123m456p789s的字符串表示手牌。字母mps分别表示万、筒、条,数字表示点数。例如123m表示一万、二万、三万,45p表示四筒、五筒。

parse_tiles函数会把这样的字符串展开成m1m2m3这样的列表,方便后续统计和排序。

hand = parse_tiles("1112345678999m") print(hand)

输出会是一串m1m9的列表。用字符串表示的好处是直观,方便复盘时把牌谱上的手牌直接复制进来。

3. 最小听牌判断器:用代码验证牌型

3.1 判断思路

听牌判断的本质是枚举:当前手牌有 13 张,把牌山中可能存在的 34 种牌依次补进来,如果补进来后的 14 张牌能拆成 4 组面子加 1 对将牌,那么这张补进来的牌就是听牌。

实现分三层:

  1. is_win判断 14 张牌能不能拆成标准型。
  2. can_form_all_sets判断去掉雀头后,剩余牌能不能全部组成刻子或顺子。
  3. listen枚举 34 种补牌,得到听牌列表。

注意这里说的“可能存在的 34 种牌”不包括字牌。代码只处理万、筒、条。

3.2 完整代码

# 麻将牌型判断工具(教学版) # 只处理万、筒、条,不考虑字牌、七对、国士等特殊牌型 TILE_STRINGS = [s + str(n) for s in "mps" for n in range(1, 10)] def tile_key(tile): # tile 格式:"m1"、"p3"、"s9" return (tile[0], int(tile[1:])) def parse_tiles(s): tiles = [] i = 0 while i < len(s): suit = s[i] i += 1 nums = "" while i < len(s) and s[i].isdigit(): nums += s[i] i += 1 for ch in nums: tiles.append(suit + ch) return tiles def count_tiles(tiles): d = {} for t in tiles: d[t] = d.get(t, 0) + 1 return d def can_form_all_sets(counts): counts = {k: v for k, v in counts.items() if v > 0} if not counts: return True tile = min(counts.keys(), key=tile_key) cnt = counts[tile] # 尝试刻子 if cnt >= 3: c = counts.copy() c[tile] -= 3 if can_form_all_sets(c): return True # 尝试顺子 if tile[0] in "mps" and int(tile[1:]) <= 7: num = int(tile[1:]) t1 = tile[0] + str(num + 1) t2 = tile[0] + str(num + 2) if counts.get(t1, 0) > 0 and counts.get(t2, 0) > 0: c = counts.copy() c[tile] -= 1 c[t1] -= 1 c[t2] -= 1 if can_form_all_sets(c): return True return False def is_win(tiles): if len(tiles) % 3 != 2: return False counts = count_tiles(tiles) for head in list(counts.keys()): if counts[head] < 2: continue c = counts.copy() c[head] -= 2 if can_form_all_sets(c): return True return False def listen(hand): waits = [] for tile in TILE_STRINGS: if hand.count(tile) >= 4: continue if is_win(hand + [tile]): waits.append(tile) return waits if __name__ == "__main__": hand1 = parse_tiles("1112345678999m") waits1 = listen(hand1) print("手牌1:", sorted(hand1, key=tile_key)) print("听牌:", waits1) print("各听牌剩余枚数:", {t: 4 - hand1.count(t) for t in waits1}) print("听口总数:", len(waits1)) hand2 = parse_tiles("123456789m3455p") waits2 = listen(hand2) print("手牌2:", sorted(hand2, key=tile_key)) print("听牌:", waits2) print("各听牌剩余枚数:", {t: 4 - hand2.count(t) for t in waits2}) print("听口总数:", len(waits2))

这段代码的关键点在can_form_all_sets:它每次选择当前最小的一张牌,先试刻子,再试顺子。如果两条路都走不通,就返回假。is_win则先尝试每一张可能有雀头的牌,把雀头拆掉后,再检查剩余牌能否全部组成面子。

3.3 运行结果

mahjong_helper.py所在目录运行:

python mahjong_helper.py

预期输出如下:

手牌1: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9'] 听牌: ['m1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9'] 各听牌剩余枚数: {'m1': 1, 'm2': 3, 'm3': 3, 'm4': 3, 'm5': 3, 'm6': 3
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 14:37:52

GEO优化指南:跨境品牌如何提升AI搜索可见度

这篇文章不是概念科普&#xff0c;而是一份可以直接拿去用的采购决策参考。核心问题是跨境企业最关心的一件事&#xff1a;当海外用户开始用 ChatGPT、Perplexity、Google AI Overviews、Bing Copilot&#xff0c;以及国内的百度 AI 搜索、豆包等工具搜索产品时&#xff0c;你的…

作者头像 李华
网站建设 2026/9/4 17:00:50

AI辅助VMP脱壳实测:加速分析而非自动破解

开头先给结论&#xff1a;AI 确实能在 VMP 类样本的逆向和脱壳分析里帮上忙&#xff0c;但它做的是“加速分析”和“辅助整理”&#xff0c;不是直接甩一个命令就自动脱壳成功。我拿 CTF 靶场里常见的 VMP 虚拟化壳样本、自己编译的带壳测试程序&#xff0c;以及几道典型的二进…

作者头像 李华
网站建设 2026/9/5 13:36:44

LocalAI:免费在本地跑大模型、图像和语音的完整指南

LocalAI&#xff1a;免费在本地跑大模型、图像和语音的完整指南 【免费下载链接】LocalAI LocalAI is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required. 项目地址: https://gitcode.com/GitHub_Trending/…

作者头像 李华
网站建设 2026/9/6 17:44:58

Codex 命令行工具安装配置与模型报错排查实战

这次我们来看 Codex 命令行工具的安装与配置。最近开发圈里讨论最多的问题不是“提示词怎么写更好”&#xff0c;而是怎么把 Codex 跑通&#xff1a;怎么安装、怎么配置 API Key、为什么一运行就报 “model is not supported”、网上流传的 “gpt-5.6-sol” 到底能不能用。这篇…

作者头像 李华
网站建设 2026/9/2 11:01:17

基于OpenCV的中国象棋识别与棋谱定位完整项目实战

简介&#xff1a;本资源是一套基于OpenCV的象棋图像识别与棋谱定位完整实现方案&#xff0c;面向人工智能课程设计、本科毕设及CV方向初学者&#xff0c;解决传统棋类图像中棋子类别识别与坐标精确定位两大核心问题。压缩包共394个文件&#xff0c;含385张标注清晰的棋子PNG样本…

作者头像 李华
网站建设 2026/9/6 6:00:42

AI异构计算工程师笔试复盘:从CUDA到分布式训练核心考点解析

“AI异构计算工程师”这个岗位&#xff0c;放在今天已经是各大厂的标配编制了&#xff0c;但在2019年百度校招里单独拿一批题来笔试&#xff0c;确实有很强的风向标意味。当年很多同学都是带着“调参侠”的心态去投递&#xff0c;真坐到考场里才发现&#xff0c;题目跟你平时用…

作者头像 李华