Files
huajishe-tts/CODEBUDDY.md
e2hang ddcb28472c feat(double-up): 双升游戏 + 单端口 start.sh 部署 + 文档统一更新
- 新增双升(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
2026-08-30 02:11:04 +08:00

84 lines
8.1 KiB
Markdown

# CODEBUDDY.md
This file provides guidance to CodeBuddy Code when working with code in this repository.
## Overview
`tts-like` is a Tabletop Simulator-like board game platform built on [boardgame.io](https://boardgame.io) (rules engine + relay server) with a React/SVG web client. The desktop shell uses Tauri (not yet started). The product name shown in the UI is **滑稽社TTS (hxs TTS)**.
The architecture is a strict 4-layer separation: **game definitions** (pure boardgame.io `Game` objects) ← **rules engine** (`@tts-like/engine`, pure functions, no UI/network) → **network relay** (`@tts-like/server`, server-authoritative) → **UI** (`@tts-like/ui` primitives rendered by `apps/web`).
## Repository / Workspace layout
pnpm workspace (`pnpm-workspace.yaml` globs `packages/*` and `apps/*`). Each package/app has its own `package.json` with scripts; run them via `pnpm --filter @tts-like/<name> <script>` or `pnpm -r <script>` (recursive).
- `packages/engine` — Re-exports `boardgame.io`, the `TTSGame` type (boardgame.io `Game` + custom `ui` field), and the `createLobbyClient` lobby SDK. **Holds all game definitions** in `src/games/*.ts` (`drag-test`, `war`, `mill`/Nine Men's Morris, `holdem`/Texas Hold'em, `pig`/拱猪). Game logic has vitest unit tests (`*.test.ts`).
- `packages/protocol`**Types only** (no runtime). Defines `UISchema`, `ZoneDef`, `CardTemplate`, `ActionDef`, and the `resolveZoneEntities` helper used by the UI to map `game.ui.zones` to entities in `G`.
- `packages/ui` — React UI primitives (`Board`, `Zone`, `Card`, `Token`, `ActionButton`, `LobbyRoomList`). Thin wrappers driven declaratively by `game.ui`.
- `packages/server` — Node/Koa server wrapping boardgame.io's `Server` with: `/api`-prefixed lobby routes, custom `/api/rooms/*` room management (delete/heartbeat/kick), an auth system (SQLite + JWT-style tokens), and static file serving of `apps/web/dist` in production.
- `apps/web` — Vite + React SPA. `src/App.tsx` is the single entry; it registers games in the `GAMES` map, handles local vs online modes, login modal, and mounts per-game Board components (`src/*Board.tsx` for Mill/Holdem/Pig; generic `Board` from `@tts-like/ui` for DragTest/War). Has Playwright E2E in `e2e/`.
- `apps/desktop` — Tauri shell scaffold (**not yet active** — only `package.json` scripts exist).
- `docs/``impl.md` (high-level plan), `ui-primitives.md` (UI API spec), `dev-log/` (chronological build log: `README.md` index, `decisions.md`, `lessons-learned.md`).
## Common commands
Requires **Node ≥ 22.12** and **pnpm ≥ 10.16** (enable via `corepack enable`).
```sh
pnpm install # install workspace deps
# Dev: two processes (terminal 1 + terminal 2), or use run.sh for one-shot:
./run.sh # starts server (:8000) + vite (:5173) via nvm
pnpm --filter @tts-like/server dev # terminal 1: tsx watch relay server
pnpm dev # terminal 2: vite web (:5173); proxy → :8000
# Build
pnpm -r build # build all packages + web (vite build → apps/web/dist)
pnpm --filter @tts-like/server build # tsc → packages/server/dist
# Type check / tests / lint
pnpm -r ts # type-check every package
pnpm -r test # vitest run (engine + server unit tests)
pnpm --filter @tts-like/engine test # single package test (vitest run)
pnpm lint # eslint .
# E2E (Playwright, auto-starts server + web)
pnpm test:e2e # = pnpm --filter @tts-like/web test:e2e
npx playwright install # first-time Chromium download
pnpm verify # ts && test && test:e2e
# Production
docker compose up --build # server image (packages/server/Dockerfile)
```
To run a **single unit test file**: `pnpm --filter @tts-like/engine exec vitest run src/games/war.test.ts`. To run a **single E2E spec**: `pnpm --filter @tts-like/web exec playwright test e2e/war-multiplayer.spec.ts`.
## Key architectural concepts
### Game definitions = boardgame.io `Game` + `ui` field
Each game in `packages/engine/src/games/*.ts` exports a `TTSGame` (a boardgame.io `Game` plus a custom `ui` field that the UI consumes — boardgame.io ignores it). `ui` declares `zones`, `cards` template (SVG front/back), `actions` (buttons → moves), and optional `overrides`. The `DragTest` game is the minimal reference for this pattern (drag a card between zones via the `transferCards` move).
**Adding a game** requires three coordinated edits: (1) create `packages/engine/src/games/<name>.ts`, (2) register it in `packages/server/src/index.ts` (both the `Server({ games: [...] })` array and the `GAMES_ENTRIES` array — these are separate and both needed), and (3) add an entry to the `GAMES` map in `apps/web/src/App.tsx` (with `engineName` matching the boardgame.io `name`, `maxSeats`, optional `seatChoices`/`setupOptions`).
### UI is data-driven by `game.ui`
`packages/ui/src/Board.tsx` iterates `game.ui.zones`, resolves each zone's entities from `G` via `resolveZoneEntities` (protocol package; reads `zone.collection` for the ID array, `zone.entityField` for the ID→object map, defaulting to `'drawPile'`), and renders `<Zone>`/`<Card>`. Drag-drop calls `zone.dropMove` with `dropArgs`. Player-view filtering (hiding opponents' cards) is handled entirely by boardgame.io's `playerView` server-side — the UI must NOT re-implement it. Holdem/Mill/Pig use bespoke `Board` components in `apps/web/src/*Board.tsx` instead of the generic `Board`.
### Unified-port networking (dev-log #12)
The web (`:5173`) is the only port the browser/LAN sees. Vite proxies `/api` (→ `:8000/api`, rewritten to strip `/api` by `mountApiPrefix`) and `/socket.io` (ws) to the server (`:8000`). In production the server serves `apps/web/dist` itself, so a single port works. `createLobbyClient('')` uses same-origin `/api` automatically. `ALLOWED_ORIGINS` defaults to LAN ranges when unset (dev); set it explicitly in production.
### Server is server-authoritative relay, not just boardgame.io
`packages/server/src/index.ts` mounts, in order: `/healthz` → room management (`/api/rooms/*`, **must be registered before `mountApiPrefix`** so raw `/api/rooms` paths are handled) → auth (`/api/auth/*`) → a gateway forcing login for create/join → `mountApiPrefix` (rewrites `/api/*` → boardgame.io router) → static serve. Room state, presence, and the single-room-per-user constraint are all in-memory maps in `rooms.ts` (lost on restart). Auth uses Node 22 built-in `node:sqlite` (`auth-db.ts`).
### Player accounts & single-room constraint (dev-log #8/#12)
Create/join require a JWT-style token (HMAC-signed, 30-day TTL) from `/api/auth/login` or `/api/auth/register`. A logged-in user can only be in one room at a time (409 if already in another). `rooms.ts` enforces this and tracks `userRoom`/`roomPlayers`; `/api/auth/me` returns the current room so the client auto-reconnects. Admin users (preset `e2hang`/`evan1115`, overridable via env) can delete/kick in any room.
## Conventions & gotchas
- **TypeScript strict** with `noUncheckedIndexedAccess`; target ES2022, `moduleResolution: Bundler`. `packages/*` are consumed via source (`exports` point to `src/*.ts`), so no build step is needed to use them — just `pnpm install`.
- **boardgame.io version is pinned at `0.50.2`** everywhere — keep it in sync across packages.
- `packages/server` uses ESM (`"type": "module"`) with `.js` import specifiers (e.g. `import './rooms.js'`) even though source is `.ts` — required for `tsx`/`node --import tsx`.
- Do **not** copy `node_modules` between machines; native deps (esbuild, Playwright browsers) are OS/arch-specific.
- Game `moves` mutate `G` directly (no Immer) — `drag-test.ts` notes a past bug where local-variable aliasing caused duplicate card inserts; always create new arrays when moving entities.
- `GAME_ORDER` in `App.tsx` controls the game dropdown order and default selection — edit there, not hardcoded elsewhere.
- Detailed historical decisions and pitfalls live in `docs/dev-log/decisions.md` and `docs/dev-log/lessons-learned.md` — consult before non-trivial changes.