ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

麻将实战牌型决策:用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的字符串表示手牌。字母m、p、s分别表示万、筒、条数字表示点数。例如123m表示一万、二万、三万45p表示四筒、五筒。parse_tiles函数会把这样的字符串展开成m1、m2、m3这样的列表方便后续统计和排序。hand parse_tiles(1112345678999m) print(hand)输出会是一串m1到m9的列表。用字符串表示的好处是直观方便复盘时把牌谱上的手牌直接复制进来。3. 最小听牌判断器用代码验证牌型3.1 判断思路听牌判断的本质是枚举当前手牌有 13 张把牌山中可能存在的 34 种牌依次补进来如果补进来后的 14 张牌能拆成 4 组面子加 1 对将牌那么这张补进来的牌就是听牌。实现分三层is_win判断 14 张牌能不能拆成标准型。can_form_all_sets判断去掉雀头后剩余牌能不能全部组成刻子或顺子。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(), keytile_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, keytile_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, keytile_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
返回列表