# tetris **Repository Path**: leander530/tetris ## Basic Information - **Project Name**: tetris - **Description**: 俄罗斯方块 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-30 - **Last Updated**: 2026-10-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 俄罗斯方块 · 单机 + 双人在线对战 经典单机俄罗斯方块的服务化改造版本:保留原有单人玩法,新增**双人实时在线对战**——创建/加入房间、同步开局、垃圾行攻击、顶死判负、断线重连、再战一局。一个 Node 进程即可部署。 > 设计方案见 [docs/multiplayer-design.md](docs/multiplayer-design.md)。原单文件版 `tetris.html` 已完成迁移并移除,单人模式由 `client/src/pages/single.ts` 承载。前端生产构建的高强度混淆方案见 [docs/frontend-obfuscation.md](docs/frontend-obfuscation.md)。 ## 特性 - **单人模式**:本地引擎无网络依赖,手感与原版一致(7-bag 随机、DAS/ARR 横移、ghost 落点、消行闪白) - **双人在线对战**:房间码创建/加入 → 倒计时 → 同 seed 同步开局;己方棋盘本地模拟零延迟,对手板快照同步 - **竞技规则**:消行发动垃圾行攻击(连击加成)、先顶死者负、服务器时钟统一升级、断线 10 秒宽限重连、rematch - **战绩持久化**:SQLite 落库对战战绩与单人成绩;大厅双榜单(单人榜 / 对战胜负榜),对战结算页显示双方历史交手记录 ## 技术栈 | 层 | 选型 | | ---- | ------------------------------------------ | | 前端构建 | Vite 5 + TypeScript 5 | | 渲染 | Canvas 2D(dpr 适配,多棋盘实例) | | 通信 | Socket.IO 4(websocket 优先,自动重连/降级) | | 服务端 | Node.js 20 LTS+ + Express(托管静态)+ Socket.IO | | 房间状态 | 进程内存 | | 战绩持久化 | Upstash 托管 Redis(ioredis,TLS,纯 JS 无原生依赖) | ## 快速开始 ```bash # 开发:并行启动 vite(5173) + 游戏服务器(3000),vite 已代理 /socket.io 与 /api npm run dev # 打开 http://localhost:5173 ,开两个浏览器标签页(或两台设备)即可对战 # 类型检查 npm run typecheck # 生产:构建前端(dist/) + 编译服务端(dist-server/) # 前端产物自动高强度混淆(javascript-obfuscator),加大逆向门槛;开发模式不受影响 npm run build # 生产运行:单端口 3000 同时服务静态资源与 WebSocket npm start # PORT=8080 npm start 可改端口 ``` 战绩存储在 Upstash 托管 Redis:本地开发与容器运行都通过环境变量 `REDIS_URL` 注入连接串(形如 `rediss://default:<密码>@<主机>:6379`)。**未设置时游戏照常运行**,仅战绩/排行榜功能不可用(接口返回空)。 本地 `npm run dev` 与 `npm start` 会自动读取项目根目录的 `.env`(`--env-file`),把 `REDIS_URL=rediss://...` 写进去即可;Docker 部署仍用 `-e REDIS_URL=...` 注入。注意 `.env` 不进仓库(已 gitignore),换机器需重建;`.env` 缺失时 `npm run dev`/`npm start` 会直接报错退出。 部署到云主机时放行对应端口即可;域名场景建议 Nginx 反代 + TLS(wss)。 ## Docker 部署 前后端打包在同一镜像内,服务端单端口同时提供页面与 WebSocket。项目无任何原生模块,镜像构建简单可靠。 **战绩数据存放在 Upstash 云端**,容器无需挂载数据卷,只需通过 `-e REDIS_URL` 注入连接串(在 [upstash.com](https://upstash.com) 免费创建数据库后,控制台复制 `rediss://...` 连接串)。 ```bash # 构建镜像 docker build -t tetris-battle . # 运行:注入 Upstash 连接串(替换为你的真实连接串) docker run -d -p 3001:3000 \ -e REDIS_URL='rediss://default:<密码>@<主机>.upstash.io:6379' \ --name tetris-battle tetris-battle # 自定义端口 docker run -d -p 8080:8080 -e PORT=8080 \ -e REDIS_URL='rediss://default:<密码>@<主机>.upstash.io:6379' \ --name tetris-battle-8080 tetris-battle # 删除容器(战绩在 Upstash 云端,不受影响) docker rm -f tetris-battle ``` 启动成功日志:`[tetris] redis connected` + `[tetris] server listening on ...`。对战战绩与单人排行榜自动写入 Upstash。 **容器起不来且 `docker logs` 为空的排查**:说明 node 进程从未运行,真实报错在 `docker run` 命令的输出里。先去掉 `-d` 前台运行看完整报错;或 `docker ps -a` 查看容器状态。常见原因:宿主机端口已被占用(换 `-p` 左侧端口)。 部署到云主机时放行对应端口即可;域名场景建议 Nginx 反代 + TLS(wss)。 ## 操作方式 | 按键 | 作用 | | ------------------ | -------------------- | | `←` `→`(或 `A` `D`) | 左右移动(长按 DAS/ARR 连发) | | `↑` / `X`(或 `W`) | 顺时针旋转 | | `Z` / `Ctrl` | 逆时针旋转 | | `↓`(或 `S`) | 软降 | | `空格` | 硬降 | | `P` / `Esc` | 暂停(**仅单人模式**,对战禁用暂停) | | `R` | 重新开始(仅单人模式) | | `Enter` / `空格` | 单人模式开始 | ## 对战规则 同步模型为「客户端权威模拟 + 服务器调度转发」,服务器对攻击数值/消息频率做轻量校验(单次攻击 ≤4 行、总数 ≤7、快照 ≥150ms、移动 ≥40ms),详细论证见设计文档 §3.2 / §8.3。 ## 工程结构 ``` tetris/ ├─ shared/ # 前后端共享(同构) │ ├─ constants.ts # 棋盘规格/计分/重力曲线/攻击表/对战参数 │ ├─ shapes.ts # 7 种方块预旋转查表、踢墙表、颜色 │ ├─ bag.ts # mulberry32 + 种子化 7-bag │ ├─ board.ts # 棋盘编解码(200 字符快照) │ └─ protocol.ts # Socket.IO 消息类型(前后端共用) ├─ client/ # 前端(Vite root) │ ├─ index.html │ └─ src/ │ ├─ core/ # 引擎(纯逻辑,随机源注入)+ 键盘输入(DAS/ARR) │ ├─ render/ # Canvas 渲染器(可多实例)+ 预览 │ ├─ net/ # Socket.IO 封装 + 断线自动重回房间 │ ├─ pages/ # menu / room / battle / single 四页 │ └─ main.ts # hash 路由 ├─ server/ # 服务端 │ ├─ index.ts # Express 静态 + API 路由 + Socket.IO 启动 │ ├─ roomManager.ts # 房间 CRUD / 房间码 / 空房回收 │ ├─ match.ts # 对局状态机 / 校验限流 / 判负 / 计时升级 / 宽限 │ ├─ db.ts # Upstash Redis(ioredis):战绩/排行榜读写 │ └─ api.ts # HTTP API:榜单查询与成绩提交 └─ vite.config.ts # root=client、@shared 别名、/socket.io 与 /api 代理 ``` 引擎(`core/engine.ts`)为纯逻辑、无 DOM 依赖,随机源通过 seed 注入——这使「同一页面渲染两块棋盘」「服务端复现对局」成为可能;后续可基于此做服务端输入重放校验。 ## 迭代状态 - ✅ M1 服务化重构(拆模块 + Vite/TS + 单人迁移,手感与原版一致) - ✅ M2 MVP 对战(房间/倒计时/同步 seed/快照同步/垃圾攻击/判负/rematch) - ✅ M3 体验增强(连击加成、时间升级、攻击动效与预警条、断线宽限重连) - ✅ 战绩持久化(Upstash Redis:对战战绩/胜负榜、单人排行榜、历史交手记录) - ⬜ M4(快速匹配池、B2B/攻击抵消、移动端适配、音效)与服务端输入重放校验