Files
erqiwang_youle/CLAUDE.md
T

123 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
本文件为 Claude Code(claude.ai/code)在本仓库中工作时提供指导。
## 这是一个什么仓库
一个友乐/gameabc 房卡类小游戏平台:浏览器端由私有的 `gameabc.min.js` 2D Canvas 引擎驱动,服务端是一个 Node.js 游戏服务器平台(`youle` 应用),双方通过 WebSocket/HTTP 收发 JSON 包通信。项目根目录**没有 `package.json`、没有 npm 构建/lint/测试流水线**——这是纯静态 JS:客户端靠 `<script>` 标签加载,服务端靠平台自带的 `min_loadJsFile`/`require` 加载。
`client/js/01_SubGame/codes/` 和 `server/games` 目前都是**空的**——尚未接入任何具体子游戏。本仓库是平台层 + 可复用框架的脚手架,未来第一个子游戏会在此基础上搭建。
## 常用命令
没有配置任何构建/lint/测试工具。仓库里唯一的脚本:
```bash
# 根据 client/assets/spine/*.json 重新生成 client/generated/spine_assets.js 与 spine_data.js
client/scripts/build_spine_data.cmd # 内部调用 build_spine_data.ps1
```
在 `client/assets/spine/` 增删 Spine 导出文件后运行;不要手改这两个生成文件。
查看客户端效果需用 HTTP 方式(而非 `file://`)打开 `client/index.html`,例如 `npx http-server client -p 8080`。
`shared/`(共享算法,待子游戏接入后才有)的改动应按两份文档中「§10 测试纪律」的要求跑 Node 单测验证——目前尚未接入测试框架,意味着需要直接用 `node` 运行相应脚本。
## 架构
### 两套独立代码库,一份线上协议
- `client/` —— 浏览器端,严格 ES5,由 gameabc canvas 引擎驱动。
- `server/` —— Node.js,严格 ES5,即 `youle` 游戏平台。
- 两端交换的 JSON 包结构固定为 `{ app: "youle", route, rpc, data }`:`route` 决定投递到服务端哪个模块,`rpc` 决定调用该模块上的哪个方法(`mod[pack.rpc](pack)`——没有二次 `switch(action)` 分发,一个操作对应一个 RPC 方法)。**服务端把结果告知客户端的唯一可靠方式是主动推送**(`o_room.method.sendpack_toseat/toother`);`DoPack` 的返回值并不是真正的下发通道。成败判定**只**看推送包里的 `data.success`(布尔值)——绝不用 `status`/`code`。
### 客户端分层(`client/js/`,依赖关系即 `index.html` 里的加载顺序)
```
js/vendor/ 第三方与引擎(gameabc.min.js、jquery、spine-canvas)—— 禁止修改
js/00_Surface/ 平台代码 —— 禁止修改
js/gameabc-framework/ 可复用、游戏中立的框架(见下)—— 禁止渗入任何具体玩法的逻辑
core/ GameABCUtils(唯一允许直接调用 gameabc/引擎原生 API 的模块)、SpriteManager(做 ID 校验的业务级精灵 API)
system/ EventBus(空的 Events{} 容器——具体玩法事件由子游戏自行追加,框架不预置)、SpriteEventController、SpriteGestureRecognizer、AnimationManager、AudioManager
ui/ BaseComponent、UIManager、SpriteCopyUtils、DynamicSpriteList、RecordView(及其默认配置)、AlignmentUtils
spine/ SpineMgr —— 必须在 gameabc.min.js 之后、gamemain.js/gameenddraw 被(重新)定义之前加载
network/RpcHelper —— 自动给外发请求注入平台字段(agentid/gameid/playerid/roomcode/seat)
templates/ *.template.js —— 供新子游戏「复制填充」的起点模板;不会被 index.html 直接引用
js/01_SubGame/
00_SubGame_Config.js / 01_SubGame_modify.js / 02_SubGame_Input.js 固定的平台契约文件(见下)
codes/ 子游戏自己的代码(尚未创建);codes/shared/ 将是 server/<游戏>/shared/ 的只读同步副本
```
依赖方向严格单向:`01_SubGame/codes` → `gameabc-framework` → `gameabc.min.js`。框架不反向依赖任何子游戏。**`index.html` 里的加载顺序就是依赖关系图**——新增文件必须插在其依赖之后、使用者之前,否则运行时会拿到 `undefined`(这里没有模块系统)。
可编辑范围:
| 路径 | 规则 |
|---|---|
| `js/vendor/`、`js/00_Surface/` | 禁止修改 |
| `01_SubGame/00_SubGame_Config.js` | 仅可改**已有** `Game_Config.*` 配置项的值——禁止新增/删除/改名/改结构 |
| `01_SubGame/01_SubGame_modify.js` | 仅可填顶部「配置区」(`Type_1/Type_2/CreateRoomData/combat/game_config/roomDes`);转发壳部分不碰 |
| `01_SubGame/02_SubGame_Input.js` | 完全不碰 |
| `js/gameabc-framework/` | 可改,但须保持游戏中立(不含具体玩法常量/逻辑) |
| `01_SubGame/codes/`(除 `shared/`) | 子游戏自由开发区 |
| `01_SubGame/codes/shared/` | 服务端 `shared/` 的只读同步副本;改服务端一侧后再同步 |
子游戏有两种接入方式(见 `docs/client/development-guide/06`):**内联模式**(旧——业务逻辑直接写进三个契约文件)或 **Hooks 外置模式**(新——三个契约文件退化为纯转发壳,查 `SubGameHooks.X` 并委托;子游戏逻辑全部放在 `codes/SubGameHooks.js` + `codes/`)。每个子游戏二选一,不能混用;Hooks 外置模式的模板位于 `gameabc-framework/templates/subgame-entry/`。
### 服务端分层(`server/`)
```
server/packet.js、applist.js 顶层收包入口 / 应用注册表 —— 禁止修改
server/class/class.app.js|class.mod.js 三层路由:app(按 pack.app)→ mod(按 pack.route)→ method(按 pack.rpc)
server/youle/ "youle" 应用
server_room/class.room.js o_room(seatlist[]、sendpack_toseat/toother)
server_room/class.player.js o_player(每座位一个,持有用于定向发包的 conmode/fromid)
server_room/class.export.js|import.js 平台自身的 export/import 服务(check_player、deduct_roomcard、save_grade……)
server/<游戏容器目录>/<游戏>/ ← 子游戏接入后**唯一**可编辑的区域(目录名并非框架强制固定;"games2" 只是早期的一种约定)
mod.js 创建模块(cls_mod.new(modname, routename, youle_app)),按依赖顺序加载文件,把 RPC 方法定义为「提参 → 委托给 handler」的薄入口
export.js 平台按需回调的可选接口(get_needroomcard、get_asetcount、makewar、get_deskinfo、get_disbandRoom、player_enter/leave……)
import.js 对平台 4 个服务的薄封装:check_player、deduct_roomcard、save_grade、finish_gametask
```
每个房间的状态挂在 `o_room.o_desk.data.*` 上(在 `export.makewar` 内创建,且必须建立 `o_room.o_desk ⇄ o_desk.o_room` 双向引用)。**对局状态禁止用模块级单例/全局变量存放**——服务端在同一进程内并发跑多个房间;缓存/定时器/决策表必须以 `房间 + seat` 为 key(而非仅 `seat`),并在小局结束/解散/开新局时清理干净。
服务端代替玩家执行的「自动」操作(AI 托管、超时等)必须走与真人操作完全相同的 handler → 广播链路,这样客户端永远不需要为它们单开一套解析分支。
### 文档(改动前先读)
`docs/client/development-guide/`与`docs/server/development-guide/`(按 01→05/04 编号)是本项目遵循的权威、项目专属开发指南——改哪块就读对应编号的文档,而不是从代码反推约定。`docs/games/engineering/` 是位于两者之上的、与平台无关的工程方法论层(单一权威数据源 SSOT、单向依赖、职责单一、对扩展开放对修改封闭 OCP、配置优先于硬编码、显式失败优于隐式兜底)。
注意:`server/docs/development-guide/` 是 `docs/server/development-guide/` 的旧版、部分过期的副本(例如它仍写死可编辑目录为 `games2/`,而新版已改为「容器目录名不再框架强制」)。两者冲突时以 `docs/server/development-guide/` 为准。
具体子游戏「二七王」(`server/games/erqiwang/`)另有两份权威文档,改动该子游戏前必读:
- `server/games/erqiwang/docs/design/design.md` —— **二七王玩法规则的唯一权威说明,必须严格遵守**。牌局构成、主牌顺序、叫分坐庄、出牌/跟牌/甩牌、捡分扣底、算子升级、算奖、房间选项等一切玩法规则以此为准;代码实现与该文档冲突时,属于代码缺陷,应改代码去符合规则(除非该规则项标注为「待确认」),**不得反过来改规则去迁就代码**。
- `server/games/erqiwang/docs/protocol/packet_protocol.md` —— 二七王前后端收发包协议与包数据定义,**必须完全符合代码实现(`server/games/erqiwang/*.js`)**。方向与 design.md 相反:协议文档是对既有代码行为的如实记录,两者不一致时以**代码为准**——应修改本协议文档去适配代码;代码新增/删除包或字段时,也要同步补全/删除本文档对应条目。
两套文档中反复强调的硬性规则(文档记载均对应过真实事故):
- 全面严格 ES5(`var`/`function`,继承用 `Object.create`;禁止 `let/const`/箭头函数/模板字符串/`class`/解构/`Promise`)。
- 服务端 `require` 只能写在文件开头的守卫块内(`if (typeof require !== 'undefined') { var X = require(...); }`)——禁止在函数体内中途 `require`;两个运行时之后统一按同一个全局名引用。
- 成败判定只看推送包上的 `data.success`——绝不用 `status`/`code`,也不能写两者都判的兼容兜底。
- 对影响发牌/庄家/手牌/计分/重连的权威数据,禁止用默认值掩盖缺失(`|| 0`、`|| []`、`|| ''`)——应直接显式报错;默认值仅允许用于纯展示/日志字段。
- 一个职能只在一个模块实现——需要该能力时调用其所属模块,而不是在别处重新实现一份。
## 测试纪律
与 `docs/client/development-guide/05` §10、`docs/server/development-guide/04` §10 一致,是本项目对测试代码的硬性要求:
- **测试代码可以不守严格 ES5**:严格 ES5 是为了兼容线上浏览器/友乐运行时;测试只跑在 Node(本地/CI),不上线,不受此限制,可用现代语法写测试。
- **正式代码(待测代码)禁止出现专为测试服务的逻辑**:不允许在核心业务代码里写"仅单测传 X 时……"之类的分支、专供测试用的钩子/后门、为了可测性而放宽的校验。
- **严禁为了让测试通过而修改正式代码**:除非确认是正式代码本身有业务缺陷(这种情况按缺陷修复处理,而不是"为了兼容测试");否则正式代码的行为不因测试而改变。
- **严禁为了测试通过率而降低测试标准**:不允许 skip/条件 return、软化断言、放宽阈值、try-catch 吞异常、把硬断言改成"存在才校验"等任何掩盖手段。
- **方向唯一**:正式代码服务生产,不服务测试——测试要适配正式代码的生产契约(真实的输入形状、真实的调用方式),而不是让正式代码迁就测试的简化输入/不规范 stub。
- **失败先裁根因**:测试失败时先用证据(打印中间态、最小复现)裁定是【业务代码缺陷】还是【测试脚本缺陷】,禁止凭猜测下结论;业务缺陷修业务、脚本缺陷修脚本,两者都保持/恢复硬断言。
- **用例要全面**:新增或修改测试时,正面用例(正常路径/合法输入)、反面用例(非法输入/错误路径/异常分支)、边界用例(空值、极值、临界条件、越界)三类都要覆盖,不能只测 happy path。
## Git 提交
本仓库已初始化 git(见根目录 `.gitignore`)。提交纪律与 `docs/server/development-guide/04-开发规范与红线.md` §11 一致:
- **及时提交,不堆积**:每完成一个可独立成立的逻辑改动(一个修复/一个功能点/一次重构/一批相关文档改动)就提交,不要把多个不相关改动攒成一次大提交。
- **可以自动提交,无需每次都问**:完成一个阶段性改动后可直接创建 commit,不必逐次向用户确认;`push` 仍按需(涉及推送远程、force-push 等仍按常规安全规范处理)。
- **提交信息用中文**,简明说明「做了什么/为什么」,一次提交聚焦一件事,结尾保留 `Co-Authored-By` 署名行。
- 仍遵守通用安全底线:不用 `git add -A`/`git add .` 囫囵提交(防止误收敏感文件/大文件),不跳过 hooks,不做 `reset --hard`/`push --force` 等破坏性操作。