# 15 · 立直麻将(Mahjong)游戏 > **做什么**(用户要求)→ **遇到什么问题** → **怎么解决** ## 做什么 在 tts-like 中加入**日本立直麻将**,规则采用**雀魂标准**,覆盖**四麻 + 三麻**、**半庄战**、**纯真人**(AI 后续)。 规则文档先行:`docs/mahjong/00-rules.md`(玩法依据 + 选型)、`docs/mahjong/01-implementation-plan.md`(工程拆分)。 UI 参照桥牌 `BridgeBoard.tsx` 的思路二次设计为立式麻将桌。 ## 实现结构 - `packages/engine/src/games/mahjong-tiles.ts` — 牌具:34 种牌、四麻 136/三麻 108 去 **2m~8m**、赤 5(四麻 3 / 三麻 2)、宝牌指示映射、排序与计数。测试 `mahjong-tiles.test.ts`。 - `packages/engine/src/games/mahjong-yaku.ts` — 和牌分解(标准型/七对/国士)、**全役种**(含双倍役满、累计役满)、符计算、点数(含满贯封顶、**不采用切上满贯**、亲/子/自摸/荣和、本场、三麻自摸损)、听牌/振听/食替辅助。测试 `mahjong-yaku.test.ts`。 - `packages/engine/src/games/mahjong.ts` — boardgame.io `Game`:阶段机 `waiting→playing→roundEnd→gameEnd`,moves(`startGame/declareRiichi/discard/tsumo/respond/ankan/kakan/nuki/kyushu9/nextRound/setName`),鸣牌窗口(荣>碰/杠>吃、**头跳**),`playerView` 剥离手牌/牌山/里宝。测试 `mahjong.test.ts`。 - `apps/web/src/MahjongBoard.tsx` — 麻将桌 UI(手牌/牌河/副露/宝牌指示/剩余牌数/点数/操作按钮 + 鸣牌响应倒计时)。 - 注册:`packages/server/src/index.ts` 两数组 + `apps/web/src/App.tsx`(`GAMES`/`GAME_ORDER`/`GameKey`/本地+联机 View,`maxSeats=4`、`seatChoices=[3,4]`)。 ## 遇到的问题 1. **赤宝牌表达**:牌型判定用索引(0..33),索引会丢赤牌信息。→ 赤 5 用 `'0m'/'0p'/'0s'`,判定时 `normalizeKind` 归一到 `5m/5p/5s`;赤牌数单独按 `Tile.red` + `Meld.redCount` 统计。 2. **隐藏信息与剩余牌数**:`playerView` 把 `wall` 置空后 UI 无法显示“剩余张数”。→ 额外注入公开字段 `wallCount`、`doraIndicators`、`handCounts`。 3. **boardgame.io 单线程下的鸣牌并发**:真实麻将是并发申请+优先级比较。→ 用“响应窗口 + 全部响应后按 荣>碰/杠>吃 结算”建模,UI 端 8 秒倒计时自动跳过来避免卡死。 4. **立直支付时机**:先宣言后打牌,若在宣言与打牌之间自摸会漏付供托。→ 把 1000 点支付与 `riichi=true` 提前到 `declareRiichi`,`discard` 只负责进入一发。 ## 结果 - `pnpm --filter @tts-like/engine test`:**310 通过**(新增麻将 59 条)。 - `apps/web` 的 `tsc --noEmit && vite build` 通过;server 类型检查通过。 - 已知**待补**(`mahjong.ts` 内有 TODO):四风连打/四家立直/三家和了/四杠散了等途中流局、流局满贯、立直后暗杠的听牌不变校验、和了终局/听牌终局的精确 All Last 判定、精确一发计时。九种九牌、荒牌流局听牌罚符、连庄/进庄、击飞、西入已实现。 ## 与 §14 对接要点的落地 规则文档 §14 列的三处注册与 `playerView` 约束均已按约定实现;发牌为 `freshRoundState` 内**原子完成**(避免多客户端抢发同一 move 造成状态错乱)。