From 292a4181ce57a631b175dedba6bc4092b3794030 Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Tue, 24 Mar 2026 14:40:28 +0800 Subject: [PATCH] Create chengdu-mahjong-features.md --- chengdu-mahjong-features.md | 606 ++++++++++++++++++++++++++++++++++++ 1 file changed, 606 insertions(+) create mode 100644 chengdu-mahjong-features.md diff --git a/chengdu-mahjong-features.md b/chengdu-mahjong-features.md new file mode 100644 index 0000000..54a2e21 --- /dev/null +++ b/chengdu-mahjong-features.md @@ -0,0 +1,606 @@ +# 成都麻将功能整理 + +本文基于当前项目 `mahjong-server` 的实际代码实现整理,目的是给前端或测试同学做逐项对照测试。这里写的是“当前代码已经实现/暴露出来的功能”,不是传统成都麻将的完整规则说明。 + +## 1. 当前玩法范围 + +### 1.1 当前后端只支持成都麻将 + +- 创建房间时如果不传 `game_type`,默认就是 `chengdu` +- 如果传入其他玩法,服务端直接返回错误:`only chengdu mahjong is supported currently` +- 虽然引擎里预留了 `xueliu`、`hongzhong` 的规则工厂,但房间服务层目前只允许创建 `chengdu` + +### 1.2 当前牌集范围 + +- 只使用三门牌:万(`W`)、条(`T`)、筒(`B`) +- 每门 `1-9` 各 4 张,共 `108` 张 +- 当前不带字牌、风牌、箭牌、花牌 +- 成都玩法配置里明确 `HasHongZhong() == false` + +## 2. 房间功能 + +### 2.1 创建房间 + +HTTP 接口: + +- `POST /api/v1/game/mahjong/room/create` + +请求体: + +```json +{ + "name": "房间名", + "game_type": "chengdu", + "max_players": 4 +} +``` + +实现规则: + +- 必须登录鉴权 +- 创建者自动成为房主 +- 创建者自动进入房间,座位索引固定为 `0` +- `max_players` 为空时,使用配置默认值 +- 当前只允许 `4` 人房 +- 房间名为空时,自动生成类似 `成都麻将-xxxxxxxx` +- 初始房间状态为 `waiting` + +返回重点字段: + +- `room_id` +- `name` +- `game_type` +- `owner_id` +- `max_players` +- `player_count` +- `players` +- `status` + +### 2.2 房间列表 + +HTTP 接口: + +- `GET /api/v1/game/mahjong/room/list` + +查询参数: + +- `status` +- `game_type` +- `page` +- `size` + +实现规则: + +- 需要登录 +- 支持按房间状态筛选 +- 支持按玩法类型筛选 +- 支持分页 +- `size` 超过配置上限会被裁剪 + +### 2.3 加入房间 + +HTTP 接口: + +- `POST /api/v1/game/mahjong/room/join` + +请求体: + +```json +{ + "room_id": "xxx" +} +``` + +实现规则: + +- 需要登录 +- 只能加入 `waiting` 状态房间 +- 人数满 4 人后不可继续加入 +- 已在房间内再次加入,不会重复加人 +- 如果玩家名之前为空,再次进入时会补写玩家名 + +加入房间成功后,除了 HTTP 返回房间摘要,还会额外推送 WS 事件: + +- `room_joined` +- `room_member_joined`(仅首次加入触发) +- `room_player_update` + +### 2.4 查询房间详情 + +HTTP 接口: + +- `GET /api/v1/game/mahjong/room/:room_id` + +实现规则: + +- 需要登录 +- 必须是房间内玩家才能看该房间详情 +- 返回 `summary + public` + +其中 `public` 可用于前端牌桌展示,包含: + +- 当前阶段 `phase` +- 当前轮到谁 `current_turn_player` +- 是否必须先摸牌 `need_draw` +- 最近弃牌人 `last_discard_by` +- 最近弃牌 `last_discard_tile` +- 牌墙剩余数 `wall_count` +- 本局胡牌玩家 `winners` +- 本局分数 `scores` +- 当前响应窗口 `pending_claim` +- 每个玩家的公开状态 + +### 2.5 离开房间 + +离开房间是走 WS 动作,不是 HTTP 接口。 + +实现规则: + +- 只有 `waiting` 状态下允许离开 +- `playing` 状态下离开会失败 +- 普通玩家离开后,剩余玩家座位索引会重新整理 +- 房主离开后,房主身份自动转移给当前列表中的第一个玩家 +- 如果最后一个玩家离开,房间会直接删除 + +对应事件: + +- `room_left` +- `room_member_left` +- `room_owner_changed`(房主变更时) +- `room_player_update` +- `room_closed`(最后一人离开时) + +## 3. 开局和基础对局流程 + +### 3.1 开始游戏 + +开始游戏走 WS 动作: + +- `type = "start_game"` + +实现规则: + +- 只有房主能开始 +- 只有满 4 人时才能开始 +- 房间状态从 `waiting` 变成 `playing` +- 内部固定按当前房间玩家顺序创建 4 个玩家 +- 庄家固定为座位 `0` +- 发牌后庄家 14 张,其余玩家 13 张 + +开始后会推送: + +- 广播 `room_state` +- 给每个玩家单独推送 `my_hand` + +### 3.2 基础轮转规则 + +- 庄家开局先打牌,不需要先摸 +- 非当前操作者不能操作 +- 非庄家进入自己回合后,必须先 `draw` 才能 `discard` +- 每次打牌后,系统检查其他三家是否可 `hu / peng / gang` +- 如果没人可响应,则轮到下家摸牌 +- 如果所有可响应玩家都 `pass`,也轮到出牌者下家摸牌 + +### 3.3 牌墙耗尽 + +- 当需要摸牌但牌墙为空时,直接流局结束 +- 打完牌后如果后续无人响应,且牌墙为空,也直接结束 +- 摸到最后一张牌后,如果牌墙已空且无人可胡,也会结束 +- 结束后阶段 `phase = over` +- 房间状态同步变为 `finished` + +## 4. 对局动作功能 + +当前对局动作都通过 WS -> ws-gateway -> grpc -> mahjong-game 转发执行。 + +支持的动作类型: + +- `draw` +- `discard` +- `peng` +- `gang` +- `hu` +- `pass` + +### 4.1 摸牌 `draw` + +触发条件: + +- 必须轮到当前玩家 +- 当前状态必须 `need_draw = true` + +效果: + +- 从牌墙头部摸 1 张 +- 手牌加 1 +- `need_draw = false` +- 记录最近摸牌玩家 +- 如果是杠后补牌,会记录 `LastDrawFromGang = true` +- 如果摸到的是最后一张,会记录 `LastDrawIsLastTile = true` + +### 4.2 出牌 `discard` + +触发条件: + +- 必须轮到当前玩家 +- 当前不能处于“必须先摸牌”的状态 +- 必须指定要打出的牌,并且该牌确实在玩家手里 + +效果: + +- 从手牌移除该张牌 +- 追加到玩家弃牌区 `out_tiles` +- 记录 `last_discard_by` +- 记录 `last_discard_tile` +- 然后生成一个 `pending_claim` 响应窗口,给其他玩家判断能否 `hu / peng / gang` + +### 4.3 碰牌 `peng` + +触发条件: + +- 当前必须存在 `pending_claim` +- 该玩家必须在响应窗口中,并且 `CanPeng = true` +- 玩家手里要有与目标弃牌同牌面的 2 张牌 + +效果: + +- 手牌移除 2 张同牌 +- 明牌区新增一组 3 张碰牌 +- 当前回合转给碰牌玩家 +- 碰牌后不需要先摸,直接由碰牌玩家出牌 + +### 4.4 杠牌 `gang` + +当前只实现两种杠: + +- 明杠:别人打出的牌,你手里正好有 3 张相同牌 +- 暗杠:当前轮到自己时,手里本来就有 4 张相同牌 + +实现效果: + +- 组成 4 张杠牌放入明牌区 +- 杠后立即补 1 张牌 +- 杠后补牌来自牌墙头部 +- 补牌后当前回合仍归杠牌玩家 + +当前未见实现: + +- 碰后补杠 +- 抢杠流程本身 + +说明: + +- 代码里有 `QiangGangHu` 计分位,但当前动作流程没有真正把“抢杠胡窗口”建出来 + +### 4.5 胡牌 `hu` + +支持两类: + +- 自摸胡:当前轮到自己时胡自己的手牌 +- 点炮胡:在 `pending_claim` 窗口里胡别人刚打出的牌 + +共同前提: + +- 必须满足成都规则的胡牌校验 +- 必须满足“缺一门” + +胡牌后效果: + +- 玩家 `HasHu = true` +- `winners` 增加该玩家 +- 按规则计算本次番数并累计到 `scores` +- 成都玩法当前不是血流玩法,所以有人胡后本局直接结束 +- `phase = over` +- 房间状态会被服务层更新为 `finished` + +### 4.6 过牌 `pass` + +触发条件: + +- 当前必须有 `pending_claim` +- 该玩家必须在当前响应窗口里 + +效果: + +- 从当前 `pending_claim.options` 删除该玩家 +- 如果还有其他玩家待响应,则继续等待 +- 如果所有玩家都过,则轮到弃牌者下家摸牌 + +## 5. 成都麻将胡牌判定规则 + +### 5.1 缺一门 + +当前代码里,“缺一门”的实现方式是: + +- 手牌 + 副露中最多只能出现两种花色 +- 如果出现万、条、筒三门同时存在,则不能胡 + +注意: + +- 这里只做了“结果校验” +- 没有单独实现“开局定缺”流程 +- 也没有前端/协议层的“选缺门”动作 + +这意味着当前代码更接近“胡牌时校验是否缺一门”,而不是完整的成都定缺流程。 + +### 5.2 支持的胡型 + +当前规则代码支持以下胡型: + +- 平胡:`1` 番 +- 对对胡:`3` 番 +- 七对:`8` 番 +- 龙七对:`16` 番 +- 清一色:`8` 番 +- 清对:`16` 番 +- 清七对:`32` 番 +- 清龙七对:`64` 番 + +### 5.3 支持的附加番 + +- 杠上开花:`+2` 番 +- 海底捞月:`+2` 番 +- 抢杠胡:`+2` 番 + +### 5.4 胡牌组合方式 + +代码可识别: + +- 标准胡:`4` 组面子 + `1` 对将 +- 七对 +- 龙七对 + +说明: + +- 对对胡通过“全部由刻子/杠 + 1 对将”识别 +- 清一色通过“所有牌同一花色”识别 + +## 6. 分数处理方式 + +当前代码的分数逻辑比较简单,适合先做基础功能联调,不等同于完整成都麻将结算。 + +已实现: + +- 每次胡牌时,按番型计算一个整数分数 +- 这个整数直接累计到 `state.scores[winnerID]` +- 例如平胡就加 `1`,清龙七对就加 `64` + +当前未实现或未体现: + +- 输家扣分分摊 +- 自摸三家付、点炮单家付 +- 杠分结算 +- 查花猪 +- 查大叫 +- 退税 +- 荒庄查叫 +- 局数/圈风/连庄 +- 最终总结算 + +因此,当前 `scores` 更准确说是“本局胡牌番数累计”,不是正式货币化结算结果。 + +## 7. 前端可收到的核心事件 + +### 7.1 房间相关事件 + +- `room_joined` +- `room_member_joined` +- `room_player_update` +- `room_left` +- `room_member_left` +- `room_owner_changed` +- `room_closed` + +### 7.2 对局相关事件 + +- `room_state` +- `my_hand` + +### 7.3 `room_state` 关键字段 + +建议重点校验: + +- `room_id` +- `phase` +- `status` +- `current_turn_player` +- `need_draw` +- `last_discard_by` +- `last_discard_tile` +- `wall_count` +- `winners` +- `scores` +- `pending_claim` +- `players` + +其中每个公开玩家对象包含: + +- `player_id` +- `hand_count` +- `melds` +- `out_tiles` +- `has_hu` + +### 7.4 `my_hand` 关键字段 + +- `room_id` +- `hand` + +说明: + +- `my_hand` 是定向事件,只发给对应玩家 +- 前端应该只用它渲染自己的真实手牌 +- 其他玩家只看 `hand_count` + +## 8. 建议的 WebSocket 动作格式 + +从网关协议看,客户端 WS 消息结构大致如下: + +```json +{ + "type": "start_game", + "roomId": "room-xxx", + "requestId": "req-001", + "trace_id": "trace-001", + "payload": {} +} +``` + +通用字段: + +- `type` +- `roomId` +- `requestId` +- `trace_id` +- `payload` + +房间相关动作: + +- `join_room` +- `leave_room` +- `start_game` + +对局动作: + +- `draw` +- `discard` +- `peng` +- `gang` +- `hu` +- `pass` + +## 9. 当前代码中的已知限制/缺口 + +这一段非常重要,测试时请单独对照。 + +### 9.1 没有“定缺”流程 + +- 成都麻将通常有“定缺” +- 当前代码没有开局选缺门动作 +- 只有胡牌时做“最多两门花色”的校验 + +### 9.2 没有“换三张” + +- 项目内未看到换三张流程 +- 没有对应状态、动作、事件 + +### 9.3 不是血战到底,也不是血流成河 + +- 成都规则实现里 `IsBloodFlow() == false` +- 一旦有人胡牌,本局直接结束 +- 不支持“一炮多响后继续”或“胡后继续打” + +### 9.4 结算远未完整 + +- 当前只给赢家加一个番数值 +- 没有完整输赢结算模型 +- 没有杠分、查叫、花猪、退税等成都特色结算 + +### 9.5 `discard` 动作当前存在接线缺口 + +这一点从代码看是高风险问题: + +- 引擎层 `discard` 必须拿到具体 `tile` +- 但服务层 `toEngineAction(msg)` 当前只设置了 `Type` 和 `PlayerID` +- 没有把 `msg.Payload` 里的牌对象解析到 `types.Action.Tile` + +这意味着: + +- 如果前端通过现有 WS 通道发送 `discard`,很可能会因为缺少牌对象而失败 +- 报错大概率是 `discard tile is required` + +建议测试时优先验证这个点。 + +### 9.6 抢杠胡只有计分标志,没有完整流程 + +- 规则层支持 `QiangGangHu` 加番 +- 但当前核心流程没有看到“补杠被抢”的动作分支和响应窗口 +- 所以这个番目前更像预留能力,不一定能在真实流程里触发 + +### 9.7 优先级仲裁较简化 + +项目内 `engine/README.md` 还明确写了后续建议: + +- 需要补充更明确的优先级处理 `hu > gang > peng` + +当前代码行为是: + +- 打牌后把所有可操作选项放进 `pending_claim` +- 谁先发起动作、且在 options 中合法,谁就能执行 + +因此如果多个玩家同时都可响应,前端和测试要特别注意是否存在“先到先得”而不是严格优先级裁决。 + +## 10. 建议测试清单 + +### 10.1 房间层 + +- 创建房间成功,默认玩法为 `chengdu` +- 非 `chengdu` 玩法创建失败 +- 非 4 人房创建失败 +- 房主自动入房 +- 房间列表分页正常 +- 房间未满时可加入 +- 房间满员后加入失败 +- 对局开始后不能再加入 +- 房间内玩家可查看详情 +- 非房间内玩家查询详情失败 +- 普通玩家可离开等待中的房间 +- 房主离开后房主转移 +- 最后一人离开后房间关闭 +- 游戏进行中离开房间失败 + +### 10.2 开局与轮转 + +- 房主才能开始游戏 +- 必须满 4 人才能开始 +- 开局庄家 14 张,其余 13 张 +- 开始后收到 `room_state` +- 每位玩家都能收到自己的 `my_hand` +- 庄家首回合直接出牌 +- 非庄家必须先摸再打 +- 轮转顺序按座位顺延 + +### 10.3 响应动作 + +- 弃牌后正确生成 `pending_claim` +- 可碰玩家能执行 `peng` +- 碰后当前回合归碰牌玩家 +- 手牌与副露数量变化正确 +- 明杠成功后会补牌 +- 暗杠成功后会补牌 +- 所有可响应玩家都 `pass` 后轮到下家摸牌 + +### 10.4 胡牌与流局 + +- 平胡可胡且加 `1` 分 +- 对对胡可胡且加 `3` 分 +- 七对可胡且加 `8` 分 +- 龙七对可胡且加 `16` 分 +- 清一色可胡且加 `8` 分 +- 清对可胡且加 `16` 分 +- 清七对可胡且加 `32` 分 +- 清龙七对可胡且加 `64` 分 +- 杠上开花附加 `2` 分 +- 海底捞月附加 `2` 分 +- 三门齐全时不能胡 +- 点炮胡后本局直接结束 +- 自摸后本局直接结束 +- 牌墙为空时本局流局结束 + +### 10.5 高风险专项 + +- WS `discard` 是否因未传/未解析 `tile` 失败 +- 多家同时可响应时,是否符合预期优先级 +- `QiangGangHu` 是否实际上无法走通 + +## 11. 结论 + +当前项目里的“成都麻将”更准确地说,是一个: + +- 已具备房间管理 +- 已具备 4 人基础发牌和回合流转 +- 已具备碰/杠/胡/过等核心动作 +- 已具备部分成都胡型与番数计算 +- 但尚未实现完整成都特色流程与结算 + +如果你是拿它和“标准成都麻将产品需求”对照,当前更像“成都麻将基础可玩内核 + 房间联机骨架”,还不是完整商用品规。