人人麻将:从规则状态机到 Android 包的四川血战麻将

1074 字
5 分钟
人人麻将:从规则状态机到 Android 包的四川血战麻将

这是我做得最完整的一个项目:四川血战到底,四人实时对战,有真人房也有 AI 补位,H5 和 Android 包都出了,现在跑在自己的 VPS 上。

为什么是麻将#

麻将看着简单,实际是个很好的服务端练手题:它同时具备强规则多人实时状态必须绝对一致三个特征。任何一处判断写歪,四个客户端立刻就会吵起来。写完一遍麻将,基本把状态同步该踩的坑踩了一轮。

而且川麻的「血战到底」比标准麻将更麻烦——有人胡牌后不是结束,而是离桌,剩下的人继续打。这意味着对局状态机不能简单地按「一局 = 四个人从头打到尾」建模。

架构#

整体拆成三个仓库:

仓库职责技术栈
majiang-server规则引擎 + 房间服务Node.js、Socket.IO
majiang-app客户端Cocos Creator 3.8.8、TypeScript
majiang-godot-commercial-fork另一条客户端技术路线的试验Godot

关键的一条设计决定:规则引擎和网络层彻底分开

majiang-server/
src/
core/ # 纯函数式的规则与对局状态机,不认识 socket
server/ # HTTP + Socket.IO,只负责收发和广播
test/ # 直接对 core 做单元测试,不起服务

core 里没有任何网络代码,它就是一个 (状态, 动作) => 新状态 的状态机。好处非常直接:

  • 测规则不用起服务器,npm run test:core 几秒跑完
  • AI 就是一个「读状态、返回动作」的函数,和真人玩家走完全同一条代码路径
  • 以后要换传输层(比如换成 WebSocket 原生或 KCP),core 一行不用动

规则实现的几个点#

牌墙:108 张,只有万、筒、条,没有风牌和箭牌。这是川麻和国标最直观的区别。

定缺:开局每人必须选一门花色作为「缺门」,之后不能碰杠胡这门牌,手里的缺门牌必须优先打出去。这一步是川麻的灵魂,也是 AI 最容易写蠢的地方。

没有吃:川麻只有碰、杠、胡,没有吃。少了一个动作,但优先级判定并没有变简单——同一张牌可能同时有人要碰、有人要胡,得按「胡 > 杠 > 碰」的优先级仲裁,还要处理一炮多响。

血战到底:某人胡牌后从牌桌移除,但牌局继续,直到只剩一人或牌墙摸完。所以对局状态里必须显式维护「还在场的玩家」这个集合,而不是假定永远是四个人。

一次缺陷审计#

项目跑了一段时间后我做过一轮系统性的缺陷排查,一共整理出 20 条问题,修掉了 17 条。剩下 3 条是需要改数据结构的,放到了后面的迭代里。

这轮审计最大的收获不是修了多少 bug,而是确认了一件事:凡是能在 core 层用单元测试复现的问题,修起来都很快;凡是只能靠「开四个浏览器手动打一局」复现的,都很慢。 后来我把能下沉的判断全部往 core 挪了。

客户端与打包#

客户端用 Cocos Creator 3.8.8 + TypeScript。选它的主要原因是一套工程同时出 H5 和 Android,而对局逻辑完全复用服务端已有的协议,客户端不重写任何规则——客户端只负责渲染和把玩家操作发上去,所有判定以服务端为准。

Android 出包这一步坑最多,主要是 Gradle 和 NDK 的版本匹配问题。这部分我单独记了笔记,之后补一篇。

部署#

服务端直接 tar over ssh 推到 VPS,不走 CI。原因很实际:这个项目的部署频率不高,而且有几个目录(玩家数据、房间快照)绝对不能被覆盖,走脚本反而容易手滑。改数据相关的东西之前必须先停服。

后面要做的#

  • 血流成河玩法
  • 账号和金币持久化(现在是内存,重启清零)
  • 换三张
  • 完整番型表

这篇是项目概览。具体到某一块(比如状态机怎么设计、Android 出包踩了什么坑)我会单独开文章写。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

人人麻将:从规则状态机到 Android 包的四川血战麻将
https://dr.free--china.com/posts/majiang-blood-battle/
作者
董锐
发布于
2026-08-20
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
董锐
网络 / 系统工程师。身份底座、跨境网络、信息化交付。
公告
这是项目履历站,配合简历看。中英文 PDF 在「关于我」页可下载。
分类
标签
站点统计
文章
8
分类
3
标签
35
总字数
7,284
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.16.6
文章许可
CC BY-NC-SA 4.0