- 新增双升(DoubleUp)完整实现:引擎 double-up-core.ts / double-up.ts、牌桌 UI DoubleUpBoard.tsx、单测与 E2E - 甩牌跟牌修复:validateFollow 新增 mixed 分支,甩牌后其他家可正常跟单张同花色牌 - start.sh:PORT=5173 单端口一键启动(Koa 同时 serve 页面+/api+/socket.io),浏览器直连无需 Vite 代理 - 玩家名 server 权威化(join 时写入 G.playerNames);LobbyRoomList 支持 fixedSeats(双升固定 4 人) - 文档:新增 dev-log/14、更新 dev-log README 索引与进度、更新根 README 启动/部署说明 - .gitignore 忽略根目录 data/*.db 运行时 SQLite
98 lines
6.9 KiB
Markdown
98 lines
6.9 KiB
Markdown
# 14 · 双升(DoubleUp)游戏 + 单端口一键启动 / 部署
|
||
|
||
## 做什么
|
||
|
||
1. **新增双升(两副牌升级 / 拖拉机 / 80 分)游戏**:完整规则引擎 + 牌桌 UI + 联机。
|
||
- 纯逻辑在 `packages/engine/src/games/double-up-core.ts`(与 boardgame.io 无关,便于单测)。
|
||
- 游戏定义在 `packages/engine/src/games/double-up.ts`(boardgame.io `Game` + `ui` 字段)。
|
||
- 牌桌 UI 在 `apps/web/src/DoubleUpBoard.tsx`(自定义 Board,非通用 `<Board>`)。
|
||
- 规则选型见 `docs/double-up/00-rules.md`:V1 含大小王(108) / V2 2 为常主 / V3 翻第四张兜底 / V4 甩牌罚分 / V6 抠底封顶 64 倍。
|
||
- E2E 实测脚本在 `apps/web/e2e/double-up-full-game.spec.ts`。
|
||
2. **新增 `start.sh`**:单端口(`PORT=5173`)一键启动——Koa 同时 serve 前端页面 + `/api` REST + `/socket.io` WS,浏览器直连 `:5173`,无需 Vite 代理。
|
||
3. **玩家名字 server 权威化**:join 时由 server 把登录用户名写入 `G.playerNames[playerID]`,不再依赖客户端 `setName` 是否触发。
|
||
4. **LobbyRoomList 支持固定人数**:双升固定 4 人,创建房间不再显示人数下拉(`fixedSeats` prop)。
|
||
|
||
## 怎么解决(具体做法)
|
||
|
||
### 1. 双升引擎结构
|
||
|
||
- `double-up-core.ts`:所有确定性逻辑(主牌判定 `isTrump`、牌序 `compareCards`/`trumpWeight`、牌型解析 `parsePlay`、跟牌校验 `validateFollow`、甩牌全大 `isThrowAllBig`、赢墩 `trickWinnerIndex`、抠底倍数 `kouchudiMultiplier`、计分升级 `levelDelta`)。无随机、无 `Date`、无副作用,便于单测与 playerView 多端一致。
|
||
- `double-up.ts`:`Game` 定义 + 阶段机 `waiting → deal → bottom → play → score`。关键 move:
|
||
- `startGame`:房主触发,原子 `dealAll` 一次发满 100 张(每家 25),剩 8 张作底;**这是修掉联机同步卡死的关键**——早期一张张 `dealOne` 发会产生大量连续 move,触发 boardgame.io 客户端 stateID 竞态,其他客户端收不到更新。
|
||
- `declareTrump` / `confirmTrump`:发牌阶段亮主/反主(优先级 单级牌<对级牌<对小王<对大王),四家全确认后 `resolveTrump` 定主花色 + 庄家 + 庄家收 8 底牌。
|
||
- `setBottom`:庄家扣 8 张隐藏底牌。
|
||
- `playCards`:单/对/拖拉机/甩牌;首出若 `mixed` 且同花色副牌则判为甩牌,调用 `isThrowAllBig` 校验,失败罚分并强制出最小牌。
|
||
- 一墩结算在 `applyPlay`:四家出齐(按人数计,不按牌数)判赢家,赢家收走全部桌面牌;`currentTrick` 为扁平每牌列表,`trickPlays` 为每玩家一手。
|
||
|
||
### 2. 跟牌校验(甩牌修复点)
|
||
|
||
`validateFollow` 对 `mixed`(甩牌领出,如 AAK 三张同花)单独分支:手里该类别有牌就只需出 ≥1 张同花色牌,**不强制对子/拖拉机,也不要求张数一致**;断门则任意出。该修复让甩牌后其他家可正常跟单张同花色牌(此前误落到 pair/tractor 逻辑被拒)。
|
||
|
||
### 3. 单端口 `start.sh`
|
||
|
||
根目录 `start.sh`(已 `chmod +x`):
|
||
|
||
```sh
|
||
./start.sh
|
||
```
|
||
|
||
行为:
|
||
- 加载 nvm + pnpm,缺依赖自动 `pnpm install`。
|
||
- 若 `apps/web/dist` 不存在,自动先 `pnpm --filter @tts-like/web build`(单端口 serve 需要该产物)。
|
||
- 清掉残留的 5173/8000 占用。
|
||
- `PORT=5173 pnpm --filter @tts-like/server dev` 后台启动(tsx watch,开发热重载)。
|
||
- 等 `:5173/healthz` 就绪后打印访问地址;Ctrl-C 连同派生进程一起清理。
|
||
|
||
**为什么映射 5173 即可**:`packages/server/src/index.ts` 的 `PORT` 默认 8000,但显式支持 `PORT=5173`;且 `serveStatic` 在 `apps/web/dist` 存在时自动 serve 整个前端。所以 Koa 单一进程在 5173 上同时提供页面 + `/api` + `/socket.io`,浏览器同域直连,**无需 Vite 代理**。这契合 `CODEBUDDY.md`「统一端口」一节的设计。
|
||
|
||
### 4. 玩家名字 server 权威
|
||
|
||
`packages/server/src/rooms.ts` 的 join 成功后,`dispatchMove(server, games, matchID, playerID, credentials, 'setName', [playerID, username])` 把登录用户名写进 `G.playerNames`。这样无论客户端是否触发 `setName` 都能看到真实名字,而非 `玩家0/1/2/3`。best-effort:失败仅影响显示。
|
||
|
||
### 5. 固定人数大厅
|
||
|
||
`LobbyRoomList` 新增 `fixedSeats` prop:`App.tsx` 给 `double-up` 传 `fixedSeats: 4`,创建房间时不显示人数下拉,直接用 4。`onSelect` 回传的 `numPlayers` 也用该固定值。
|
||
|
||
## 部署(公网暴露,不含 frp 配置)
|
||
|
||
> frp 的具体配置由用户另行管理;这里只记录服务器侧与客户端侧的非 frp 部分。
|
||
|
||
**服务端**(生产单端口):
|
||
|
||
```sh
|
||
pnpm --filter @tts-like/web build # 生成 apps/web/dist
|
||
PORT=5173 ALLOWED_ORIGINS=https://你的域名 pnpm --filter @tts-like/server start
|
||
# 或开发/调试:直接 ./start.sh(占 5173)
|
||
```
|
||
|
||
- 必须设 `ALLOWED_ORIGINS` 为浏览器将使用的公网 origin(留空走默认 LAN 段,公网会被 CORS 拒绝,Socket.IO 握手也失败)。
|
||
- TLS 终止在反代(Nginx/Caddy)或隧道层;整链 http 或整链 https 保持一致,避免 `wss://` 与 `ws://` 混用被浏览器拦。
|
||
- 数据库 `data/*.db`(根目录 SQLite)是 server 运行时数据,已在 `.gitignore` 的 `packages/server/data/*.db` 覆盖思路下管理——注意根目录 `data/` 当前未忽略,提交时需排除(见 commit 说明)。
|
||
|
||
**客户端(浏览器)**:零配置。打开 `http://<公网地址>:5173`(或隧道给的 https 域名)即可。`createLobbyClient('')` 走同域相对路径,`/socket.io` 同源。
|
||
|
||
**局域网联机**:同 dev 模式,`http://<本机内网IP>:5173/` 直接可用(origins 默认含 LAN 段)。
|
||
|
||
## 成果
|
||
|
||
- 新增文件:`double-up-core.ts` / `double-up.ts` / `DoubleUpBoard.tsx` / `double-up.test.ts` / `double-up-sim.test.ts` / `e2e/double-up-full-game.spec.ts` / `start.sh` / `docs/double-up/*`。
|
||
- 修改:`App.tsx`(注册双升 + 待机 UI 简化)、`vite.config.ts`(allowedHosts 更新)、`server/index.ts`(注册 DoubleUp)、`rooms.ts`(setName 权威化)、`LobbyRoomList.tsx`(fixedSeats)。
|
||
- 测试:双升引擎单测 58 例通过;全引擎套件 174 例通过;`tsc --noEmit`(web + engine)干净。
|
||
|
||
## 用户验证路径
|
||
|
||
```bash
|
||
./start.sh # 单端口 :5173
|
||
# 浏览器开 http://localhost:5173
|
||
# 1. 选「双升」→ 创建房间(固定 4 人)→ 4 个客户端加入
|
||
# 2. 房主开始 → 发牌 → 亮主/确认 → 庄家扣底 → 出牌(单/对/拖拉机/甩)
|
||
# 3. 整局打完自动结算 + 升级,进入下一局
|
||
```
|
||
|
||
## 关联
|
||
|
||
- [12-unified-port-and-player-names.md](./12-unified-port-and-player-names.md) — 统一端口基础(vite proxy + 静态 serve)
|
||
- [13-holdem-game.md](./13-holdem-game.md) — 同类「引擎 + Board + 可变人数大厅」范式
|
||
- `docs/double-up/00-rules.md` — 双升规则拍板
|
||
- `CODEBUDDY.md`「统一端口联网」一节 — 单端口同源设计说明
|