# 立直麻将(Mahjong)实现计划 > 规则依据:[`00-rules.md`](./00-rules.md)(雀魂标准,四麻 + 三麻,半庄战)。 > 目标文件与集成点见 00-rules §14。本文只描述**工程拆分与实施顺序**。 --- ## 一、模块拆分(均放 `packages/engine/src/games/`) | 文件 | 职责 | 是否纯函数 | | --- | --- | --- | | `mahjong-tiles.ts` | 牌定义、牌组生成(四麻/三麻)、赤宝牌、排序、宝牌指示映射、索引/牌种互转 | ✅ | | `mahjong-yaku.ts` | 和牌型分解(标准型/七对/国士)、役种判定、番数、符计算、点数计算、振听/听牌判定辅助 | ✅ | | `mahjong.ts` | boardgame.io `Game` 定义:阶段机、moves、playerView、本局/终局结算、连庄/进庄 | ❌(含状态机) | 对应测试:`mahjong-tiles.test.ts`、`mahjong-yaku.test.ts`、`mahjong.test.ts`。 ## 二、牌与状态数据模型 - **牌种索引**(34 种):`0-8 = 1m..9m`,`9-17 = 1p..9p`,`18-26 = 1s..9s`,`27-33 = 東南西北白發中`。 - **物理牌**:`{ id: string; kind: string; red?: boolean }`,其中 `kind` 用 `'1m'..'9m'/'1p'../'1s'../'1z'..'7z'`;赤 5 用 `'0m'/'0p'/'0s'` 表示,`normalizeKind` 归一到 `'5m'/'5p'/'5s'` 参与牌型判定。 - **G 关键字段**:`players/hands/deck/wall/deadWall`、`melds[pid]`、`discards[pid]`、`scores[pid]`、`riichi[pid]`、`doraIndicators`、`honba`、`kyoutaku`(供托)、`bakaze`(场风)、`dealer`、`round`、`phase` 相关标记、`pendingClaims`(鸣牌/荣和响应)、`turn`、`lastDiscard`、`furiten` 标记等。 ## 三、阶段机与主循环 ``` waiting ──startGame──▶ playing ──(和了/流局)──▶ roundEnd ──nextRound──▶ playing ... └──(半庄终局)──▶ gameEnd ``` - `playing` 内部用 `turnStep` 区分:`draw`(摸牌)→ `discard`(打牌)→ `claim`(鸣牌响应窗口)。 - 鸣牌响应:逆时针依次询问有资格者(吃/碰/杠/荣/拔北),`pass` 或超时继续;优先级 荣和 > 碰/杠 > 吃。 - **发牌必须一次性原子完成**(踩坑记忆:单客户端 flood move 会破坏其他客户端同步)。 ## 四、实施顺序(每步附验证) 1. **P1 牌与宝牌**:`mahjong-tiles.ts` + 测试(牌组张数、赤 5、宝牌指示循环、三麻去牌)。 2. **P2 和牌与役**:`mahjong-yaku.ts` + 测试(和牌分解、全部役种表、双倍役满、宝牌加算)。 3. **P3 符与点数**:符计算 + 点数表 + 测试(对拍 §9.3 表、满贯封顶、切上满贯不采用、亲/子/自摸/荣和、本场)。 4. **P4 听牌/振听/食替**:听牌判定、舍张/同巡/立直后振听、现物+筋食替校验 + 测试。 5. **P5 Game 骨架**:阶段机 + 配牌/摸打/立直/鸣牌/和了/流局 moves + playerView + 本局/终局结算 + 测试(用 boardgame.io 的 `Client`/`Game.T` 驱动)。 6. **P6 UI**:`apps/web/src/MahjongBoard.tsx`(参照 `BridgeBoard.tsx` 重设计为立式麻将桌)。 7. **P7 接入**:注册 server 两数组 + `App.tsx`(GAMES/GAME_ORDER/GameKey/Local/Online View)。 8. **P8 验证**:`pnpm -r ts && pnpm --filter @tts-like/engine test`,必要时补 E2E。 ## 五、风险与注意 - 役种与符计算是最大复杂度来源:**先写穷尽单测对拍标准表**,再写 Game 逻辑。 - 三麻自摸损、拔北、北客风等需在点数与役判定中分支处理。 - 鸣牌并发在 boardgame.io 下单线程,需用状态窗口 + 超时模拟;避免多客户端抢发同一 move。 - 隐藏信息严格走 `playerView`,UI 不得二次实现。