1. 项目概述:当AI坐上麻将桌
“AI打麻将”这个事儿,听起来像是科幻电影里的桥段,但今天,它已经是一个可以动手实现的、充满挑战和趣味的实战项目。这不仅仅是让电脑学会“碰杠吃胡”的规则那么简单,它背后融合了计算机视觉、强化学习、博弈论等多个前沿领域的技术。想象一下,你坐在家里,打开电脑,就能和三个不知疲倦、算力超群的AI牌友来上几圈,或者开发一个能帮你复盘、分析牌局的智能助手,是不是挺带劲的?
这个项目的核心目标,是构建一个能够“看懂”牌局并“做出决策”的AI智能体。它需要完成两个关键任务:一是“感知”,即通过摄像头或图片,实时识别出麻将牌桌上的所有牌面(包括手牌、河牌、碰杠牌);二是“决策”,即基于识别出的牌面信息,结合麻将的复杂规则(如番种、听牌概率、对手行为推测),计算出当前最优的出牌、吃、碰、杠或胡牌策略。
对于开发者、麻将爱好者,或者对AI应用感兴趣的朋友来说,这个项目都是一个绝佳的练手机会。它不像围棋、象棋那样有完全信息,麻将是不完全信息博弈,充满了概率、欺骗和心理战,这让AI的决策逻辑更加复杂和有趣。通过这个项目,你不仅能深入理解目标检测、图像分类等CV技术如何落地,还能一窥强化学习在非完美信息博弈中的魅力。接下来,我就结合自己折腾这个项目的经验,从头到尾拆解一遍,把关键的技术选型、实操步骤和踩过的坑都摊开来聊聊。
2. 核心思路与技术选型:让AI先“看见”再“思考”
要让AI打麻将,我们得把它拆解成两个相对独立但又紧密协作的模块:视觉感知模块和决策推理模块。这个架构思路很清晰,先解决“是什么”的问题,再解决“怎么办”的问题。
2.1 视觉感知模块:如何让AI“看清”每一张牌?
这是整个项目的地基。麻将牌种类固定(通常为34种花色×4张+特殊牌),但实际场景复杂:牌可能以不同角度摆放、部分被遮挡、光照条件多变、背景杂乱。因此,我们需要一个鲁棒性强的目标检测模型。
为什么选择YOLO系列?在相关热搜词里,SSD和YOLO都被频繁提及。两者都是单阶段目标检测算法的佼佼者。经过实测和社区反馈,我最终选择了YOLOv8,原因如下:
- 精度与速度的平衡:YOLOv8在保持YOLO系列一贯高速的同时,精度又有显著提升。对于需要实时或准实时响应的麻将场景(比如从视频流中识别),速度至关重要。
- 生态完善,易于部署:Ultralytics公司维护的YOLOv8开源库,文档清晰,API友好。从训练到导出为ONNX、TensorRT等格式进行部署,一条龙服务非常顺畅。相比之下,虽然SSD也很经典,但其生态和社区活跃度在当前阶段略逊于YOLO。
- 对小目标友好:麻将牌在整张图片中占比可能较小(尤其是远处的牌),YOLOv8在模型结构上针对小目标检测进行了一些优化,表现更稳定。
关于“三维目标检测”和“不规则目标”:热搜词里提到了“三维目标检测”和“注意力机制检测不规则目标”。在标准麻将AI场景中,我们通常假设牌是平铺在二维平面上的,因此2D目标检测已经足够。除非你的场景是机器人手臂抓取立体堆叠的牌,否则不需要引入复杂的3D检测。至于牌的形状是规则的矩形,所以“不规则目标检测”的复杂机制在这里不是必需品,标准的边界框回归就能很好工作。
注意:模型选择不是绝对的。如果你对轻量化有极致要求,可以考虑YOLOv5s或Nano版本;如果追求极致精度且算力充足,可以试试YOLOv9或DETR系列模型。但对于大多数入门和中级项目,YOLOv8是一个“开箱即用”且效果不错的起点。
2.2 决策推理模块:如何让AI“思考”出下一张牌?
当AI“看到”了所有牌,难题才真正开始。麻将的决策属于不完全信息、多人、零和、随机(摸牌)博弈。主流思路有以下几种:
- 基于规则的专家系统:这是最直观的方法。编写大量“IF-THEN”规则,例如:“如果手牌差一张听牌,则优先打出生张或字牌”。这种方法实现简单,但麻将局面浩如烟海,规则难以穷尽,且无法处理概率和长期收益问题,AI会显得很“笨”。
- 基于搜索的博弈树:类似于象棋AI,模拟未来几步所有可能的出牌、摸牌序列,选择胜率最高的路径。但麻将分支因子巨大(每轮可能打出的牌有几十种),且信息不完全,搜索深度非常有限,计算量爆炸。
- 强化学习(Reinforcement Learning, RL):这是目前最主流且效果最好的方法。让AI作为智能体(Agent)与环境(麻将桌)互动,通过胡牌、放铳等结果获得的奖励(Reward)来学习策略。特别是深度强化学习(DRL),如Deep Q-Network (DQN)、Proximal Policy Optimization (PPO),结合神经网络来近似复杂的价值函数或策略函数,能够处理高维状态空间。
- 监督学习:收集人类高手的对局数据,让AI学习在给定局面下,人类高手会做出什么决策。这可以作为一个很好的预训练模型,为强化学习提供初始策略。
我们的混合策略:在实战中,纯强化学习从零开始训练效率极低。一个有效的策略是**“模仿学习+强化学习微调”**。先用大量人类牌谱数据做监督学习,让AI学会“像人一样打牌”,得到一个基础策略网络。然后让这个网络在自我对局(Self-play)的环境中,通过强化学习(比如PPO)进行微调和提升,探索出超越人类经验的策略。AlphaGo的成功也部分得益于这种思路。
关于“AI Agent”:决策模块本质上就是一个麻将AI Agent。它接收视觉模块传来的结构化牌局信息(状态State),输出一个动作(Action),如“打出三万”,并随着对局进行不断更新其内部策略。
3. 实战构建:从零搭建一个麻将AI视觉系统
理论聊完,我们进入实战环节。首先攻克视觉部分,这是所有后续工作的基础。我会以YOLOv8为例,详细走一遍流程。
3.1 数据准备与标注:万事开头难
没有高质量的数据,再好的模型也是空中楼阁。你需要准备一个包含各种麻将牌、在各种场景下的图片数据集。
数据收集:
- 来源1:自行拍摄。这是最可靠的方式。使用手机或摄像头,从不同角度、不同光照(白天、夜晚、台灯下)、不同背景(麻将桌布、木质桌面、杂乱背景)拍摄麻将牌。重点要覆盖:单张牌特写、手牌(13张叠放)、河牌(散落)、碰杠牌(两组并排放置)。
- 来源2:网络爬取与游戏截图。可以从麻将教学网站、游戏视频中截图。但需注意版权,且这类图片风格可能比较单一。
- 数据量建议:至少准备2000-3000张图片。每张图片中可能包含多张牌。确保34种花色牌(如一万到九万、条、筒、东南西北中发白)每种都有充足的样本,尤其是“白板”这类特征较少的牌。
数据标注:
- 工具推荐:使用
LabelImg或更高效的CVAT、Roboflow进行标注。 - 标注格式:YOLO格式要求每个标注对象保存为一个txt文件,内容为:
<class_id> <x_center> <y_center> <width> <height>。坐标和宽高都是相对于图片尺寸的归一化值(0-1之间)。 - 类别定义:你需要建立一个
classes.txt文件,按顺序列出所有牌的种类。例如:
关键技巧:对于“一萬”和“一筒”,尽管文字不同,但图案差异巨大,必须分为不同类别。对于“东风”和“西风”等字牌,模型是可以学会区分的。0 yi_wan 1 er_wan ... 8 jiu_wan 9 yi_tiao ... 33 bai_ban - 标注注意事项:
- 边界框(Bounding Box)要紧贴牌的四边。
- 即使牌有轻微旋转或遮挡,框也要沿着牌的边缘。
- 对于紧密叠放的手牌,确保框与框之间不要有太多重叠,尽量分开。
3.2 模型训练与调优:教会AI认牌
数据准备好后,我们就可以开始训练模型了。
环境搭建:
# 使用Ultralytics官方推荐方式安装 pip install ultralytics训练脚本: 创建一个train.py脚本或直接使用命令行。核心是准备好一个数据集配置文件mahjong.yaml。
# mahjong.yaml path: /path/to/your/mahjong_dataset # 数据集根目录 train: images/train # 训练集图片路径 val: images/val # 验证集图片路径 # 类别数量和名称 nc: 34 # 你的麻将牌总类别数 names: ['yi_wan', 'er_wan', ..., 'bai_ban'] # 与classes.txt对应然后开始训练:
yolo task=detect mode=train model=yolov8n.pt data=mahjong.yaml epochs=100 imgsz=640 batch=16model=yolov8n.pt: 这里我用了纳米(nano)模型,它体积小、速度快,适合快速迭代和部署。如果你的数据量足够大且追求更高精度,可以换成yolov8s.pt或yolov8m.pt。epochs=100: 迭代轮数,根据损失曲线(loss curve)提前停止或增加。imgsz=640: 输入图片缩放尺寸。麻将牌细节需要较高分辨率,640是一个不错的起点,可以尝试768。batch=16: 批大小,取决于你的GPU显存。
训练过程中的监控与调优:
- 损失曲线:关注
train/box_loss,train/cls_loss和val/box_loss,val/cls_loss。理想情况是训练损失和验证损失都平稳下降,且两者差距不大。如果验证损失很早就开始上升,说明模型过拟合了。 - 评估指标:主要看
mAP50-95(mean Average Precision)。这是目标检测的核心指标。mAP50(IoU阈值0.5)达到0.95以上,mAP50-95达到0.7以上,对于麻将牌检测就算很不错了。 - 常见问题与调优:
- 过拟合:现象是训练集精度很高,验证集精度很低。解决方案:增加数据增强(在
mahjong.yaml中配置augment=True或使用更激进的数据增强参数),加入随机裁剪、色彩抖动、模糊、马赛克等;使用更小的模型(如yolov8n);添加权重衰减(weight_decay)。 - 欠拟合:现象是训练集和验证集精度都很低。解决方案:增加模型复杂度(换用yolov8m或l);增加训练轮数;检查数据标注质量。
- 某些类别识别差:例如“白板”和“发财”容易混淆。解决方案:针对性补充这些类别的训练数据;检查标注是否准确;可以尝试在损失函数中为这些类别增加权重。
- 过拟合:现象是训练集精度很高,验证集精度很低。解决方案:增加数据增强(在
实操心得:在训练初期,不要一上来就训100轮。先用小数据集(比如200张图)训10轮,快速验证你的数据管道和基础配置是否正确。看到损失在下降,再上全量数据。这能节省大量时间。
3.3 模型部署与实时推理:让AI“在线”看牌
训练好的模型(如runs/detect/train/weights/best.pt)需要部署到实际应用环境中。
部署方式选择:
- 本地Python脚本:最简单的方式。使用Ultralytics的API进行推理。
from ultralytics import YOLO import cv2 model = YOLO('path/to/best.pt') # 单张图片推理 results = model('your_image.jpg') # 可视化结果 plotted = results[0].plot() cv2.imshow('Detection', plotted) cv2.waitKey(0) # 访问检测结果 for result in results: boxes = result.boxes.xyxy # 边界框坐标 confs = result.boxes.conf # 置信度 cls_ids = result.boxes.cls # 类别ID # 可以进一步处理,如转换为牌面文字 - Web服务(API):使用FastAPI或Flask将模型封装成HTTP API,方便其他程序(如决策模块)调用。
from fastapi import FastAPI, File, UploadFile app = FastAPI() model = YOLO('best.pt') @app.post("/detect/") async def detect_mahjong(file: UploadFile = File(...)): image_bytes = await file.read() # 将bytes转换为OpenCV格式... results = model(image) # 将results解析为JSON返回... return {"tiles": detected_tiles_list} - 边缘设备部署:如果想做成嵌入式产品,需要将模型转换为更高效的格式。
- 导出为ONNX:
yolo export model=best.pt format=onnx。ONNX格式通用性强,可以被多种推理引擎(如ONNX Runtime)调用。 - 导出为TensorRT:
yolo export model=best.pt format=engine。这是NVIDIA GPU上的终极加速方案,能获得极低的延迟,但对环境配置要求较高。
- 导出为ONNX:
实时视频流处理: 对于实时打牌场景,需要处理摄像头视频流。
cap = cv2.VideoCapture(0) # 0代表默认摄像头 while True: ret, frame = cap.read() if not ret: break # 为了速度,可以降低推理频率,比如每5帧处理一次 results = model(frame, verbose=False) # verbose=False关闭控制台日志 plotted_frame = results[0].plot() cv2.imshow('Mahjong Detection', plotted_frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()性能优化技巧:
- 调整推理尺寸:
model.predict(source=frame, imgsz=480)。减小imgsz能大幅提升速度,但可能会降低小目标的检测精度,需要权衡。 - 使用半精度:
model.predict(source=frame, half=True)。在支持FP16的GPU上,可以提速且几乎不损失精度。 - 批处理:如果同时处理多个摄像头画面,使用批处理(
batch参数)效率更高。
4. 决策系统设计初探:从规则到智能
视觉部分让AI获得了“眼睛”,决策部分则是“大脑”。这里我分享一个从简单到复杂的实现路径,你可以循序渐进。
4.1 第一步:实现一个基于规则的“入门级”AI
在尝试复杂的强化学习之前,先实现一个规则引擎,有助于你彻底理解麻将的状态表示和逻辑流程。
状态表示: 首先,需要定义如何用数据结构表示一个牌局。
class MahjongGameState: def __init__(self): self.hand_tiles = [] # 手牌列表,如 ['1m', '1m', '2m', '3m', '5z', '5z'...] self.discard_pile = [] # 河牌(弃牌堆) self.melds = [] # 副露(吃、碰、杠的牌组) self.current_draw = None # 刚摸到的牌 self.wind = 'EAST' # 当前场风 self.player_wind = 'EAST' # 自家门风 self.remaining_tiles = 70 # 剩余牌墙数(简化) self.is_riichi = False # 是否立直你需要将视觉模块识别出的文字(如“yi_wan”)映射到麻将通用记法(如“1m”代表一万,“5z”代表白板)。
核心规则引擎: 编写一系列函数来判断牌型、计算听牌、评估打牌风险。
def can_chi(self, tile): """判断是否能吃上家打出的牌""" # 逻辑:上家打出的牌,是否能与手牌中的两张牌组成顺子 pass def can_pon(self, tile): """判断是否能碰任何一家打出的牌""" # 逻辑:手牌中是否有至少两张与打出牌相同的牌 pass def find_tenpai_tiles(self): """计算当前手牌听哪些牌""" # 这是麻将AI的核心算法之一。常用方法是“向听数”搜索。 # 暴力法:从所有牌中依次尝试添加一张到手牌,判断是否和牌(胡牌)。 # 更高效的方法是使用“表向听”算法或基于“牌效率”的启发式搜索。 pass def evaluate_tile_to_discard(self): """评估打哪张牌最好(基于规则)""" candidates = self.hand_tiles.copy() if self.current_draw: candidates.append(self.current_draw) best_tile = None best_score = -float('inf') for tile in candidates: # 模拟打出这张牌 new_hand = [t for t in candidates if t != tile] # 计算打这张牌后的“牌效率”分数 # 分数可以基于:向听数减少量、安全度(是否生张)、番种潜力等 score = calculate_tile_efficiency(new_hand) if score > best_score: best_score = score best_tile = tile return best_tile牌效率计算: 这是规则AI的灵魂。一个简单的牌效率模型可以只考虑向听数(Shanten)。向听数是指当前牌型距离和牌(听牌)还差几张有效进牌。向听数越少越好。网上有开源的和牌计算库(如mahjong这个Python包),可以直接调用其向听数计算函数。
踩坑记录:自己实现一个完整的、高效的向听数计算函数非常复杂,涉及到大量排列组合。强烈建议在初期使用成熟的轮子,把精力放在决策逻辑的构建上。等你对麻将AI的核心有更深理解后,再考虑优化底层算法。
4.2 第二步:引入基于统计与概率的“进阶”AI
纯规则AI不懂概率和风险。进阶一步,我们可以让AI具备一些“概率思维”。
危险度评估: 记录所有玩家打出的牌(河牌),根据“现物”(绝对安全的牌)和“筋牌理论”(例如,如果4万和7万都已被弃,则1万相对安全)来评估打某张牌是否会放铳(点炮)。
class SafetyPredictor: def __init__(self): self.discard_history = [] # 所有玩家的弃牌序列 def get_danger_score(self, tile): """评估打出某张牌的危险系数(0-1,越高越危险)""" score = 0.0 # 规则1:如果是“现物”(对手刚打过的牌),危险度为0 if tile in self.get_recent_safe_tiles(): return 0.0 # 规则2:根据“筋牌”理论调整危险度 score += self.calculate_suji_danger(tile) # 规则3:如果是中张牌(3-7),且其相邻牌很少被弃,危险度增加 score += self.calculate_middle_tile_danger(tile) return min(score, 1.0)期望点数计算: 在决定是否立直、是否追立、是否攻防转换时,AI需要估算不同决策的期望收益。
- 自摸期望:根据当前听牌的种类和剩余牌数,估算自摸的概率,乘以胡牌后的得分。
- 放铳损失:估算打某张危险牌导致放铳的概率,乘以放铳后损失的分数。
- 决策:选择“期望收益 = 自摸期望 - 放铳损失”最大的行动。
这个阶段的AI已经具备了一定的竞技水平,能够根据场况在进攻和防守之间做出权衡。
4.3 第三步:探索强化学习与深度学习的“终极”形态
要让AI达到甚至超越人类高手水平,必须引入学习能力。这里简要描述一下深度强化学习(DRL)的框架。
状态表示(State Representation): 将麻将游戏状态编码成神经网络可以理解的输入。这是一个关键设计点。通常是一个多维向量或图像。
- 向量化表示:将34种牌,分别用4个通道表示:手牌中数量、自家副露中数量、河牌中数量、全局可见数量(用于估算剩余牌)。再加上场风、门风、剩余牌数、是否立直等标量信息。最终形成一个
(34*4 + N)维的向量。 - 图像化表示:类似AlphaGo,将牌面信息、玩家信息等渲染成一个多通道的“图像”,供卷积神经网络处理。
动作空间(Action Space): AI可以执行的动作包括:打出34种牌中的一张、吃、碰、杠(明杠、暗杠)、立直、和牌(自摸、荣和)、放弃(过)。这是一个离散动作空间。
奖励函数(Reward Function): 这是强化学习的指挥棒。设计的好坏直接决定AI学成什么样。
- 稀疏奖励:只在游戏结束时给予奖励(胡牌+分,放铳-分)。这种奖励非常稀疏,AI很难学习。
- 稠密奖励:设计中间奖励引导AI。例如:
- 每减少一向听数,给予小正奖励。
- 成功立直,给予正奖励。
- 打出高危险牌,给予小负奖励。
- 摸到有效进张,给予小正奖励。
- 最终得分作为终局奖励。
网络结构与训练: 使用一个深度神经网络(如MLP或CNN)作为策略网络(Policy Network),输入状态,输出各个动作的概率分布。使用PPO或A3C等策略梯度算法进行训练。训练需要在模拟环境中进行大量的自我对局(Self-play)。
开源项目参考: 完全从零实现一个DRL麻将AI工程量巨大。幸运的是,有一些优秀的开源项目可以借鉴和学习,例如mjai或Suphx的部分公开思路。研究这些项目的架构和代码,是快速入门的最佳途径。
重要提示:深度强化学习训练需要巨大的计算资源和时间,并且对算法调参要求极高。对于个人开发者,我建议先专注于完成前两步(规则+概率),构建一个可运行的、具有一定强度的AI。第三步可以作为长期研究和学习的目标。
5. 系统集成与实战调试
当视觉模块和决策模块都开发得差不多了,就需要将它们集成起来,形成一个完整的、可以运行的AI麻将玩家。
5.1 模块联调:打通“感知-决策”闭环
集成系统的核心是一个主循环,它不断从摄像头获取图像,调用视觉模块识别,更新游戏状态,然后调用决策模块获取动作,最后执行动作(在模拟器中)或输出建议。
# 伪代码示例 vision_model = load_yolo_model() decision_ai = RuleBasedAI() # 或 ProbabilisticAI() game_state = MahjongGameState() cap = cv2.VideoCapture(0) while game_is_on: # 1. 感知 frame = get_frame_from_camera(cap) detection_results = vision_model.predict(frame) parsed_tiles = parse_detections_to_tile_names(detection_results) # 2. 状态更新 update_game_state(game_state, parsed_tiles) # 此函数需要根据牌的位置(手牌区、河牌区)来区分 # 3. 决策 if is_my_turn(game_state): action = decision_ai.decide(game_state) # 4. 执行/输出 if action.type == 'DISCARD': print(f("[AI] 打牌: {action.tile}") # 在模拟环境中执行打牌操作 simulate_discard(action.tile) elif action.type == 'PON': print(f("[AI] 碰!") # ... 处理其他动作 # 短暂延时,模拟人类思考时间,也避免CPU跑满 time.sleep(0.5)关键挑战与调试:
- 状态同步:视觉识别可能会有误差或延迟,导致AI内部维护的游戏状态与真实牌局不同步。必须设计状态校验和纠错机制。例如,每隔几轮,强制用视觉识别结果完全重置AI的内部状态;或者当AI的动作在现实中无法执行时(比如试图打出一张不存在的牌),触发状态重新同步。
- 动作映射:决策AI输出的是“打出一万”这样的逻辑指令。你需要一个执行器,在真实的在线麻将游戏中,这可能意味着模拟鼠标点击“一万”这张牌。这涉及到UI自动化(如使用
pyautogui),但请注意游戏反作弊机制。在模拟环境中,则直接修改游戏状态数据结构即可。 - 实时性要求:从识别到决策必须在几秒内完成,否则超时会被判负。优化视觉模型的推理速度(如前文所述)和决策算法的计算效率至关重要。对于复杂的DRL模型,可能需要进行模型剪枝、量化才能在CPU上实时运行。
5.2 性能评估与提升:你的AI到底有多强?
如何衡量你的麻将AI水平?不能只靠感觉。
- 构建测试集:
- 标准牌谱测试:收集大量人类对局的终盘局面(例如,来自天凤、雀魂等平台的牌谱)。将这些局面输入给你的AI,记录它的选择,并与人类高手(或牌谱中的实际选择)进行对比。计算“决策一致率”。
- 残局测试:专门设计一些经典的攻防残局、立直判断残局,看AI能否做出最优解。
- 进行模拟对局:
- 自我对局:让多个相同版本的AI相互对战成千上万局,统计它们的平均顺位和得分。虽然不能完全代表对阵人类的水平,但可以观察其稳定性。
- 与规则AI对战:让你的AI与一个纯随机出牌的AI、一个基于简单规则的AI对战,看胜率提升。
- 关键指标:
- 平均顺位:越低越好(1为最高)。
- 和牌率:成功胡牌的局数比例。
- 放铳率:点炮的局数比例。
- 立直率与立直和了率:衡量进攻性。
- 打点:平均每次胡牌获得的分数。
通过分析这些指标,你可以定位AI的弱点。例如,如果放铳率很高,说明危险评估模块有问题;如果和牌率很低,说明牌效率计算或听牌判断有问题。
5.3 常见问题与故障排查
在开发和集成过程中,你肯定会遇到各种各样的问题。这里列一些我踩过的坑和解决办法。
视觉模块问题:
- 问题:识别准确率在真实场景下骤降。
- 排查:检查训练数据与真实环境差异是否过大。真实环境的光照、牌具颜色、摄像头角度是否在训练集中有充分体现?
- 解决:进行领域自适应。在真实环境下拍几百张新图,只对模型进行少量轮次的微调(Fine-tuning),而不是从头训练。
- 问题:某些牌(如“白板”和“發财”)总是混淆。
- 排查:查看混淆矩阵(Confusion Matrix)。如果这两个类别混淆严重,说明它们在特征空间里太接近。
- 解决:针对性补充这两个类别的、带有挑战性的训练数据(如侧放、反光)。也可以在数据增强时,专门增加针对颜色和纹理的扰动。
决策模块问题:
- 问题:AI总是很快打出生张,疯狂放铳。
- 排查:检查危险度评估函数。是否没有正确利用“现物”信息?筋牌逻辑是否正确实现?
- 解决:引入“场况”评估。在比赛早期,可以进攻性强一些;在比赛后期或自己点数领先时,应大幅提高安全牌的权重。实现一个简单的“攻防开关”。
- 问题:AI在复杂听牌(如多面听、染手)时选择很差。
- 排查:牌效率计算函数可能只考虑了向听数减少,没有考虑听牌后的和了形好坏(比如是听边张还是双碰,是听1种牌4张还是听3种牌8张)。
- 解决:在评估打牌选择时,不仅要看打完后向听数,还要模拟摸进一张有效牌后的有效进张数和听牌种类数,选择“未来期望”最高的打法。
集成系统问题:
- 问题:系统运行一段时间后,AI状态与真实牌局完全对不上。
- 排查:这是典型的状态漂移。可能是某次识别错误(如漏检一张牌)导致后续所有状态都错了。
- 解决:实现定期硬同步。例如,在每一局开始、每次自家摸牌后,都强制用视觉识别结果完全覆盖AI内部的手牌状态。对于河牌和副露,可以每轮都进行全量更新。
- 问题:决策速度太慢,导致超时。
- 排查:使用性能分析工具(如Python的
cProfile)找到瓶颈。是视觉推理慢,还是决策搜索慢? - 解决:视觉部分采用更小的模型或降低推理分辨率。决策部分,对于规则AI,检查是否有低效的循环;对于搜索算法,限制搜索深度;对于神经网络,进行模型优化。
- 排查:使用性能分析工具(如Python的
开发一个能打的麻将AI是一个系统工程,涉及计算机视觉、传统博弈算法和现代强化学习。从简单的规则引擎做起,逐步增加概率模型,最后挑战深度学习,这个路径既能不断获得正反馈,又能层层深入地理解其中的技术精髓。最重要的是,这个过程本身就像打麻将一样,充满了未知、挑战和乐趣。当你看到自己编写的AI第一次做出一个精妙的弃和选择,或者勇敢地立直并自摸时,那种成就感是无与伦比的。希望这篇长文能为你提供一张清晰的“地图”,祝你开发顺利,早日和你的AI牌友战个痛快!