# Game **Repository Path**: cc0419/game ## Basic Information - **Project Name**: Game - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-15 - **Last Updated**: 2026-09-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 局域网联机小游戏合集 纯网页的多人小游戏集合,零客户端安装。一台电脑运行 Node.js 服务端,同一 WiFi 下的手机、平板、电脑用浏览器打开链接即可一起玩。 | 游戏 | 人数 | 玩法 | 默认端口 | | --- | --- | --- | --- | | [游戏菜单页](#游戏菜单页) | — | 星空 + 温馨弹幕,一键进入各游戏 | 3000 | | [狼人杀](#狼人杀) | 4 - 8(房主可加 AI 补位) | 经典身份推理,昼夜循环,投票放逐 | 3001 | | [你画我猜](#你画我猜) | 2 - 8 | 一人画词、众人猜词,自动轮换画手 | 3002 | | [贪吃蛇大作战](#贪吃蛇大作战) | 1 - 200 | 大蛇吃光点、撞死别人吃它的身体,含机器人陪玩 | 3003 | 游戏各自独立,可同时运行(不同端口),互不干扰;**推荐从菜单页(3000)进入各游戏**,无需记端口。 > 端口规划:菜单 3000、狼人杀 3001、你画我猜 3002、贪吃蛇 3003,后续新增游戏依次顺延(3004、3005…)。 --- ## 环境要求 | 项目 | 要求 | | --- | --- | | Node.js | 18 及以上(推荐 20+) | | 浏览器 | Chrome / Edge / Firefox / Safari 等现代浏览器 | | 网络 | 所有玩家在同一局域网(同一 WiFi) | ## 快速开始 ```bash # 1. 安装依赖(仅首次需要,只有一个 ws 依赖) npm install # 2. 启动游戏(按需选择,可各开一个终端同时跑) npm run wolf # 狼人杀 → http://localhost:3001 npm run draw # 你画我猜 → http://localhost:3002 npm run snake # 贪吃蛇大作战 → http://localhost:3003 npm run menu # 游戏菜单页 → http://localhost:3000(从这里进各游戏,免记端口) ``` 启动成功后终端会打印访问地址,例如: ``` 狼人杀 · 服务已启动 本机访问: http://localhost:3001 局域网访问: http://局域网IP地址:3001 ``` 按 `Ctrl + C` 停止服务。 ## 如何联机 1. 主机启动服务,记下终端里的**局域网访问**地址。 2. 其他玩家在同一 WiFi 下,用浏览器打开该地址。 3. 每人填写**昵称**和**相同的房间号**,点击「进入房间」。 4. 人数达标后,房主(最先加入者)点击「开始游戏」即可开局。 - 狼人杀人不够时,房主可在大厅点 `+` **添加 AI 玩家**补位(真人 + AI 满 4 人即可开局)。 > 想分多组同时玩,只要填写不同的房间号即可,各房间互不干扰。 ### 连不上怎么办 - **确认在同一 WiFi**:手机不要开流量,不要连访客网络(部分访客网络会隔离设备)。 - **放行防火墙**:首次启动时 Windows 会弹窗询问,请勾选「专用网络」并允许;若当时点了取消,可到 `控制面板 → Windows Defender 防火墙 → 允许应用通过防火墙` 中放行 Node.js。 - **手动指定端口**:若端口被占用,可用 `set PORT=8080 && npm run wolf`(Windows)或 `PORT=8080 npm run wolf`(macOS/Linux)换端口,访问地址端口同步改变。 ## 项目结构 ``` test/ ├── package.json # 依赖与启动脚本(draw / wolf) ├── Dockerfile # Docker 镜像构建(node:20-alpine) ├── docker-compose.yml # 容器编排(单容器跑四游戏,映射 3000/3001/3002/3003) ├── run-all.js # 容器入口:同时启动四个服务(菜单 + 三游戏) ├── .dockerignore # 构建时排除 node_modules/.git 等 ├── .gitignore # Git 忽略 node_modules 等 ├── menu/ # 游戏菜单页(零依赖) │ ├── server.js # HTTP 静态服务 │ └── public/ │ ├── index.html # 页面结构(星空/极光/弹幕/卡片/页脚) │ ├── style.css # 深色星空主题、玻璃拟态卡片 │ └── game.js # 弹幕引擎、星空生成、卡片链接、暖心话 ├── werewolf/ # 狼人杀 │ ├── server.js # HTTP 静态服务 + WebSocket 房间/回合状态机/胜负判定 + AI 调度 │ ├── ai-bot.js # AI 玩家决策(简单/标准难度、怀疑分、聊天模板) │ └── public/ │ ├── index.html # 页面结构(登录、游戏主界面、身份卡、公告、结算) │ ├── style.css # 样式(深色主题、昼夜氛围、响应式布局) │ └── app.js # WebSocket 通信、界面渲染、情境化操作、AI 大厅控制 ├── draw-and-guess/ # 你画我猜 │ ├── server.js # HTTP 静态服务 + WebSocket 房间/回合/计分逻辑 │ └── public/ │ ├── index.html │ ├── style.css │ └── app.js # 画板绘制、WebSocket 通信、界面渲染 └── snake-arena/ # 贪吃蛇大作战(零依赖,含 Windows bat 快捷脚本) ├── server.js # HTTP 静态服务 + 手写 WebSocket + 30Hz 游戏主循环 ├── public/ │ ├── index.html # 页面结构(菜单、HUD、排行榜、小地图) │ ├── style.css │ └── game.js # 客户端渲染、插值、输入、摄像机 ├── tools/test-client.js # 自动化协议测试 ├── 启动游戏.bat # Windows 双击开服 └── 关闭服务器.bat # Windows 双击关服 ``` --- # 游戏菜单页 一个星空 + 极光 + 温馨弹幕的入口页,三张游戏卡片一键直达,**无需再记 IP 加端口**。 - 卡片上的链接自动按当前访问的主机名拼接,IP 访问和 localhost 访问都通用 - 各游戏的进入页面(及狼人杀/你画我猜的对局顶栏)都有「← 返回菜单」按钮,随时可回到本页 - 温馨语句弹幕随机飘过(速度/位置/颜色随机),页脚点一下换一句暖心话 - 零 npm 依赖,纯静态页面,`npm run menu` 即可在 3000 端口运行 --- # 狼人杀 ## 局型配置(按房间人数自动分配) | 人数 | 配置 | | --- | --- | | 4 | 1 狼人 + 预言家 + 女巫 + 1 村民 | | 5 | 1 狼人 + 预言家 + 女巫 + 2 村民 | | 6 | 2 狼人 + 预言家 + 女巫 + 2 村民 | | 7 | 2 狼人 + 预言家 + 女巫 + 猎人 + 2 村民 | | 8 | 2 狼人 + 预言家 + 女巫 + 猎人 + 3 村民 | > **小局型提示**:4 人局节奏快,通常两个白天内分出胜负;5 人局中预言家两夜只能查验 4 个候选中的 2 个,狼人始终藏在未被查验的两人里,更考验发言博弈。独狼局(4/5 人)没有狼人频道,狼人直接点击目标投票即可。 ## AI 玩家(房主可在大厅添加) 人不够时可以由 AI 补位,**房主**在大厅看到 AI 控制区: - **AI 数量**:`-` / `+` 增减(真人 + AI 总数 4-8 人即可开局) - **AI 难度**(大厅选择,规则式,作为 LLM 关闭/失败时的回退): - **简单**:每个阶段随机选一个合法目标(只保证合法,不保证聪明) - **标准(默认)**:规则式决策 + 怀疑分。狼按怀疑分/随机选非狼目标(不透视身份);预言家优先查未查验且怀疑分高的人、白天优先投已知狼;女巫首夜倾向救(已知狼不救)、不轻易毒;猎人带走怀疑分最高者;村民白天按怀疑分投票 - AI 白天还会发 0-2 条闲聊(「我跟投」「X 有嫌疑」等模板话术,1.5-5 秒随机延迟) - **LLM(大模型,可选,全局开关)**:在[管理后台](#管理后台--llm-接入)启用并配置后,**所有**狼人杀 AI 的**决策与白天发言**改由大模型生成(比规则式更会「演戏」);未配置或调用失败时自动回退到大厅所选的规则式难度 - AI 玩家列表显示 🤖 前缀;AI 与真人完全对等(正常被刀/被投/留遗言/触发猎人) - AI 决策在服务端模拟(每个动作 0.4-1.9 秒随机延迟,模拟思考时间),不占 WebSocket 连接 ### 管理后台 + LLM 接入 狼人杀 AI 支持接入**任意 OpenAI 兼容的大模型**(OpenAI / DeepSeek / 通义千问 / 本地 Ollama 等),让 AI 的决策和发言由大模型生成。 **入口**:游戏菜单页页脚的「⚙️ 管理后台」链接,或菜单页地址后加 `/admin`,例如 `http://服务器IP:3000/admin`。 **访问口令**:默认 `123456`(首次进入后台需输入;可在后台「修改访问口令」处更改,改后旧口令失效)。 **配置项**(纯手填): | 字段 | 说明 | | --- | --- | | 启用 LLM | 开关。开启后狼人杀 AI 的决策与发言走大模型;关闭则用大厅所选的规则式难度(简单/标准) | | AI 提供商 | 备注,如 `DeepSeek` / `OpenAI` / `Ollama`(仅展示,不参与调用) | | 模型名称 | 如 `deepseek-chat` / `gpt-4o-mini` / `qwen-turbo` | | OpenAI 兼容地址 | 基址,通常含 `/v1`,如 `https://api.deepseek.com/v1`;本地 Ollama 用 `http://127.0.0.1:11434/v1` | | API Key | 密码框,读取时脱敏显示(`****后4位`);留空表示保持原 Key | **「测试连接」**会用当前填写的地址 / Key / 模型发一条测试消息,返回模型回复,便于快速排错。 **LLM 调用与回退**: - 调用方式:`POST {baseUrl}/chat/completions`,`Authorization: Bearer {apiKey}`,10 秒超时 - 请求体为「身份 + 当前局面 + 任务」的提示词,要求模型只返回一段 JSON 决策 - **任何失败(超时 / 网络 / 非 200 / JSON 解析失败 / 动作非法)都自动回退大厅所选的规则式难度**,绝不让 AI 卡住对局 **配置持久化**:配置存在 `config/ai-config.json`。Docker 部署时 `docker-compose.yml` 已把 `./config` 挂载到容器 `/app/config`,改配置不随容器重建丢失;直接 `node` 运行时配置写入 `config/ai-config.json`。 > **更新代码前注意**:通过后台修改配置会写入 `config/ai-config.json`(该文件也在 Git 仓库里)。服务器上 `git pull` 前若提示该文件有本地修改导致冲突,先执行 `git checkout -- config/ai-config.json`(会重置为默认:token `123456`、LLM 关闭),拉取后再进后台重新配置即可。 **安全**:后台 API 需带 `?token=访问口令`,错误/缺失返回 401;API Key 读取时脱敏。局域网内使用无需额外鉴权,若要公网暴露请自行加 HTTPS 或反向代理鉴权。 ## 角色说明 | 角色 | 能力 | | --- | --- | | 狼人 | 夜晚集体投票选择击杀目标,全体狼票一致则目标死亡,分票为平安夜 | | 预言家 | 每晚查验一名玩家(不能是自己)的身份,查验历史可查看 | | 女巫 | 一瓶解药(救当夜受害者,不能自救)+ 一瓶毒药(毒杀任意玩家,不能毒自己),同夜不能既解又毒 | | 猎人 | 被狼人击杀或被投票放逐时,可开枪带走一名玩家(被毒死无法开枪) | | 村民 | 白天认真发言、投出关键一票 | ## 流程说明 ``` 天黑公告(5s) → 狼人睁眼(60s):狼队私聊讨论,点击玩家投票,全部狼投票后立即结算 → 预言家(30s):点击一名玩家查验,可跳过 → 女巫(30s):解药 / 毒药 / 不用药 → 死亡结算(6s):公布死者或平安夜 → [猎人开枪(30s):受害者是猎人且非中毒时] → 遗言(30s):仅逝者可发言,逝者点「结束发言」即提前结束 → 白天(120s):全员发言讨论,点击存活玩家投票(可改票,全员投完或超时自动计票) → 计票结果(8s):最高票者被放逐,平票为平安日 (白天/狼人回合中,过半存活者点「结束发言」可提前进入下一阶段) → [被放逐者遗言 → 猎人开枪] → 回到天黑,直至胜负产生 ``` ## 胜负条件 - 狼人全部死亡 → **好人阵营胜利** - 存活狼人数 ≥ 存活好人人数 → **狼人阵营胜利** 结算界面公开所有人身份,胜利方全员 +1 胜场(房间内累计,大厅可见)。任意人点击「再来一局」返回大厅重新分配身份。 ## 特殊规则 | 场景 | 处理 | | --- | --- | | 玩家中途断线 | 系统提示「离线(按离开处理)」,视为死亡,座位保留;**用相同昵称重新打开页面即可恢复连接**(身份、状态不丢失,已死亡则观战)。该玩家卡片**左下角会亮起「离线」角标**(黄点闪烁) | | 点「返回菜单」 | **主动退出房间**:大厅里座位立即移除;对局中座位直接删掉并按死亡处理(不会退化成可重连的「离线」),随后立即重估阶段与胜负 | | 大厅超时 | 房间建立后 **5 分钟**内仍未开局,服务端自动解散房间并通知原因(每隔 15 秒检查一次) | | 刷新页面 | 对局中刷新 = 断线重连,按昵称匹配恢复;昵称不同则无法进入进行中的对局 | | 进行中对局加入 | 拒绝加入,提示等待本局结束(防止中途插入破坏身份平衡) | | 公共发言时段 | 仅白天和遗言阶段可发言(夜间禁言),每人每 2 秒最多 1 条 | | 平票 | 平安日,无人被放逐 | | 狼票分裂 / 有狼未投 | 平安夜,无人被刀。需**全体存活狼**投同一人才刀人(分票、超时、掉线未投都不算一致;独狼局无分票,独狼票即目标) | | 女巫自救 / 毒自己 | 服务端直接拒绝 | | 预言家遗言 | 遗言阶段为自由发言,预言家死亡时可在遗言中公布查验结果(小局型常用策略) | | AI 掉线/死亡 | AI 是服务端模拟玩家,不存在断线;死亡后不再行动 | | 隐藏身份投票 | 大厅内任一玩家可发起开关,**真人票过半**即生效;开启后开局不再自动弹身份卡(点「查看身份」按钮才显示,6 秒后自动关闭),避免新手的身份被旁边的人一眼看到 | | 结束发言 / 跳过 | 白天与狼人回合:过半存活者点「结束发言」即提前进入下一阶段;遗言阶段:逝者本人点「结束发言」即结束等待 | | 投票踢人 | 对局中点击其他玩家卡片的「踢人」发起投票,**存活人数(不含被踢者)过半同意**即踢出并计为死亡;30 秒未达票数自动作废 | | 真人全部离线 | 房间自动回收,不会留下一屋子 AI 占位 | | 误操作保护 | 投票、开枪、踢人、加/减 AI、隐藏身份开关等操作均弹二次确认 | ## 网络质量显示 顶栏常驻 `fps · ms` 角标:帧率由浏览器实时统计,延迟每 2 秒向服务端往返探测一次(`pi` / `po`)。 颜色随延迟变化(< 80ms 绿、< 200ms 黄、≥ 200ms 红),方便区分「网络卡」还是「设备卡」。 ## 可调参数(`werewolf/server.js` 顶部,支持环境变量覆盖) ```js const NIGHT_SECONDS = 5; // 天黑公告 const WOLF_SECONDS = 60; // 狼人讨论 + 投票 const SEER_SECONDS = 30; // 预言家查验 const WITCH_SECONDS = 30; // 女巫用药 const DEATH_SECONDS = 6; // 死亡结算停留 const LAST_WORDS_SECONDS = 30; // 遗言 const HUNTER_SECONDS = 30; // 猎人开枪 const DAY_SECONDS = 120; // 白天讨论 + 投票 const RESULT_SECONDS = 8; // 计票结果停留 const KICK_SECONDS = 30; // 投票踢人时长 const LOBBY_TTL_SECONDS = 300; // 大厅超时未开局自动解散(秒) const MIN_PLAYERS = 4; const MAX_PLAYERS = 8; ``` 环境变量示例:`NIGHT_SECONDS=2 WOLF_SECONDS=10 npm run wolf`(调试节奏)。 另有 `ROLE_SEED` 用于固定身份顺序(逗号分隔、按加入顺序分配,不设置则随机),例如 `ROLE_SEED=seer,villager,wolf,witch,hunter,wolf,villager,villager npm run wolf`。 ## 通信协议(JSON over WebSocket) ### 客户端 → 服务端 | 类型 | 字段 | 说明 | | --- | --- | --- | | `join` | `name`, `room` | 加入房间;对局中同昵称视为重连恢复 | | `start` | — | 开始游戏(仅房主,lobby 阶段且总人数达标,真人+AI) | | `add_ai` | — | 添加 1 个 AI 玩家(仅房主,lobby 阶段) | | `remove_ai` | — | 移除最后添加的 AI 玩家(仅房主,lobby 阶段) | | `set_difficulty` | `difficulty: simple\|standard` | 设置 AI 难度(仅房主,lobby 阶段) | | `wolf_vote` | `targetId` | 狼人投票击杀(可改票,全员投完立即结算) | | `wolf_chat` | `text` | 狼人频道(仅狼人可见) | | `seer_check` | `targetId` | 预言家查验(不能是自己,每夜一次) | | `witch_action` | `action: save\|poison`, `targetId` | 女巫解药 / 毒药 | | `hunter_shot` | `targetId` | 猎人开枪 | | `skip` | — | 跳过当前夜间行动(预言家 / 女巫 / 猎人) | | `chat` | `text` | 公共发言(遗言阶段仅逝者可发) | | `vote` | `targetId` | 白天投票(不能投自己,可改票) | | `ready` | — | 「结束发言」/跳过:白天与狼人回合需过半存活者,遗言阶段仅逝者本人 | | `toggle_hide_role` | — | 大厅内发起/取消「隐藏身份」投票(真人票过半即生效) | | `kick_start` | `targetId` | 对局中发起踢人投票(仅存活玩家,一局同时只能有一个) | | `kick_vote` | `agree: boolean` | 对当前踢人投票表态同意 / 反对 | | `pi` | `ts` | 延迟探测(前端每 2 秒一次,服务端原样回带时间戳) | | `leave` | — | 主动退出房间(点「返回菜单」时发送;大厅直接移除座位,对局中按死亡处理) | | `again` | — | 再来一局(game_over 阶段,回大厅) | ### 服务端 → 客户端 | 类型 | 字段 | 说明 | | --- | --- | --- | | `joined` | `you`, `roomId`, `hostId`, `minPlayers`, `inGame` | 加入成功回执 | | `players` | `list[]` | 玩家快照(存活、死因、离线、房主、胜场、是否已投票、是否已「结束发言」) | | `hide_role` | `on`, `yes`, `total`, `voters[]` | 隐藏身份开关状态与实时得票(大厅与对局中都会广播) | | `kick_vote` | `targetId`, `targetName`, `yes`, `need`, `seconds`, `voters[]` | 踢人投票进度(同意票 / 所需票数 / 剩余秒数) | | `kick_result` | `targetId`, `kicked` | 踢人投票结果:`kicked: true` 为成功踢出,`false` 为未达票数作废 | | `phase` | `phase` + 阶段数据 | 阶段切换,见下 | | `role` | `role`, `wolfIds` | 私发:开局身份卡(狼人附队友名单) | | `wolf_turn` | `wolfIds`, `seconds` | 私发狼人:夜间行动开始 | | `wolf_voted` | `targetId` | 私发:你的狼票已记录 | | `wolf_result` | `targetId`, `peaceful` | 私发狼人:今晚刀人结果 | | `seer_turn` | `seconds`, `history` | 私发预言家:查验开始 + 历史 | | `seer_result` | `targetId`, `isWolf` | 私发预言家:查验结果 | | `witch_turn` | `victimId`, `antidote`, `poison`, `seconds` | 私发女巫:用药开始 | | `hunter_turn` | `seconds` | 私发猎人:开枪机会 | | `chat` | `name`, `text`, `system`, `channel` | 聊天 / 系统消息;`channel: wolf` 为狼人频道 | | `po` | `ts` | 延迟探测回执(顶栏 `fps · ms` 角标用) | | `room_closed` | `message` | 房间被解散(大厅超时未开局),前端回登录页并提示原因,随后连接关闭 | | `error` | `message` | 错误提示 | `phase` 阶段数据: | phase | 附加字段 | | --- | --- | | `night` | `night`, `seconds` | | `wolf` / `seer` / `witch` / `hunter` | `seconds` | | `death` | `dead[]: {id, cause}`, `peaceful`, `seconds` | | `last_words` | `playerIds`, `seconds` | | `day` | `day`, `seconds` | | `vote_result` | `counts[]: {targetId, count}`, `lynchedId`, `peaceful`, `seconds` | | `game_over` | `winner: good\|wolf`, `roles[]: {id, name, role}` | > 安全设计:身份、查验结果、狼聊全部只私发给有权玩家;所有操作服务端校验「当前阶段 + 操作者身份」,客户端无法伪造他人动作或篡改流程。 --- # 你画我猜 一台电脑运行服务端,同一 WiFi 下的手机、平板、电脑用浏览器打开链接即可一起玩(2 - 8 人)。 ## 玩法说明 一轮游戏按以下流程进行: 1. **选词**:系统随机指定一名玩家为画手,并给出 3 个候选词,画手需在 **15 秒**内选一个(超时自动选第一个)。 2. **作画**:画手用画板把词画出来,其他人在右侧输入框输入猜测,限时 **75 秒**。 3. **猜词**:猜错的词会作为聊天广播给所有人;猜对的人会立即得分。 4. **结算**:全部人猜中或时间耗尽后,公布答案并停留 **6 秒**,随后自动进入下一轮,画手顺位轮换。 ### 计分规则 | 行为 | 得分 | | --- | --- | | 猜对(按剩余时间递减) | 最低 30 分,最快约 120 分 | | 每有一名玩家猜对,画手获得 | 40 分 | ### 断线与重连 | 场景 | 处理 | | --- | --- | | 对局中掉线 / 退出 | **座位保留**(分数、画手顺位不丢),用相同昵称重新进入即恢复本局,画布自动同步 | | 客户端网络断开 | **自动重连**(1/2/4/8 秒指数退避),重连后自动恢复座位并同步整个画布,无需刷新页面 | | 画手掉线 | 当前回合提前结束(提示「画手离线了」),下一轮自动跳过其顺位 | | 猜词方掉线 | 不计入「待猜」名单,不会因为有人离线而拖到回合时间耗尽才结算 | | 全部玩家离线 | 房间自动回收 | | 玩家列表 | 离线的玩家显示「离线」灰色标记 | > 画布同步保障:笔迹逐段实时转发(攒 8 点或 30ms 即发送);切后台超过 3 秒回前台时自动请求全量笔迹重同步,避免移动端后台期间丢消息导致画面卡住。 ### 网络质量显示 顶栏常驻 `fps · ms` 角标:帧率由浏览器实时统计,延迟每 2 秒向服务端往返探测一次(`pi` / `po`)。 颜色随延迟变化(< 80ms 绿、< 200ms 黄、≥ 200ms 红),方便区分「网络卡」还是「设备卡」。 ### 可调参数(`draw-and-guess/server.js` 顶部,支持环境变量覆盖) ```js const ROUND_SECONDS = 75; // 每回合作画/猜词时长(秒) const CHOOSE_SECONDS = 15; // 画手选词时长(秒) const BETWEEN_SECONDS = 6; // 回合结算停留时长(秒) const MIN_PLAYERS = 2; // 开局最少人数 const MAX_PLAYERS = 8; // 单房间人数上限 ``` 环境变量示例:`ROUND_SECONDS=40 CHOOSE_SECONDS=10 npm run draw`(调试节奏)。 词库位于同文件的 `WORDS` 数组,可直接增删词汇(建议 2 - 4 个字、便于作画)。 ## 常见问题 **Q:能上公网给异地朋友玩吗?** 可以。本项目已内置 Docker 部署配置,用 1Panel + Gitee 即可部署到云服务器,详见下方[「部署到云服务器」](#部署到云服务器1panel--gitee)。部署后玩家无需同一 WiFi,4G/5G 直接访问即可。 **Q:数据会保存吗?** 不会。所有房间、身份、分数都在内存中,服务重启即清空。 --- # 贪吃蛇大作战 零依赖的网页联机贪吃蛇:吃光点变长,把别人的头撞死在你的身体上,它的身体会炸成光点给你吃。详细文档见 [snake-arena/README.md](snake-arena/README.md)。 - 吃光点成长,越长视野越远 - **加速冲刺**:鼠标左键 / 空格 / **A / Shift**,手机端点住屏幕上的 **⚡ 圆形按钮**(点击、长按都行) - **两种模式**:`传统`(原汁原味)与 `道具`(随机刷出 🧲磁铁 / 🛡护盾 / 疾跑),房间模式由第一个进入的人决定 - 道具效果:磁铁吸附附近光点、护盾抵挡一次致命碰撞(撞墙/撞蛇)、疾跑期间冲刺不消耗长度 - 头撞别人身体或边界出局;让别人撞你的身体是唯一击杀方式 - 有真人进房间自动派 8 个 AI 机器人陪玩,一个人也不冷清 - 支持多人同房间、自定义房间名、排行榜、小地图、断线自动重连 - 顶栏常驻 `fps · ms` 角标(帧率 + 往返延迟),与狼人杀 / 你画我猜同款 - 零 npm 依赖(WebSocket 为手写实现),`node snake-arena/server.js` 即可运行(默认 3003 端口) --- # 部署到云服务器(1Panel + Gitee) 项目已内置 Docker 部署配置(`Dockerfile` + `docker-compose.yml` + `run-all.js`),单容器同时跑菜单页和三个游戏。以 **1Panel + 阿里云 + Gitee 公开仓库** 为例,IP 直连访问,无需域名和备案。 ## 部署文件说明 | 文件 | 作用 | | --- | --- | | `Dockerfile` | 基于 `node:20-alpine`,安装依赖并拷贝三个游戏(npm 源默认 npmmirror,大陆构建加速) | | `docker-compose.yml` | 定义 `lan-games` 服务,`restart: always`,映射 3000/3001/3002/3003 | | `run-all.js` | 单容器内同时启动四个服务(菜单:3000、狼人杀:3001、你画我猜:3002、贪吃蛇:3003),任一进程退出则整体退出交由 Docker 自动重启 | | `.dockerignore` | 构建时排除 `node_modules`、`.git` 等 | ## 前置条件 - 云服务器已安装 **1Panel**(1Panel 自带 Docker) - 一个 **Gitee 公开仓库**(如 `https://gitee.com/你的用户名/game.git`) - 本地代码已推送到该仓库 ## 首次部署 ### 第 1 步:本地推代码到 Gitee ```bash git add -A git commit -m "feat: 初始化局域网双游戏合集与 Docker 部署配置" git remote add origin https://gitee.com/你的用户名/game.git git push -u origin master ``` > 推送会弹出 Gitee 登录凭证输入,按提示填写即可。 ### 第 2 步:服务器上拉取代码 1Panel → **终端**,执行: ```bash git clone https://gitee.com/你的用户名/game.git /lan-games ``` 公开仓库无需输入密码。 ### 第 3 步:创建并启动容器(首次) 两种方式选一种即可: **方式 A:1Panel UI(图形化,适合熟悉1Panel的用户)** 1. 1Panel → **容器 → 编排 → 创建编排** 2. 编排文件选择服务器上的 `/lan-games/docker-compose.yml` 3. 点击「启动」,首次会自动构建镜像(约 1–2 分钟),状态变为「运行中」即可 **方式 B:1Panel 终端命令(推荐,跨1Panel版本行为一致)** ```bash cd /lan-games # 首次构建并后台启动 docker compose build --no-cache docker compose up -d # 验证 docker compose ps # 应显示 lan-games 状态 Up docker compose logs --tail=20 lan-games # 看启动日志 curl -s http://127.0.0.1:3000 | head -1 # 菜单页能打开即可 ``` > 这两条 `docker compose` 命令就是 1Panel 「重新构建并部署」按钮背后实际执行的逻辑;UI上找不到按钮时用这条命令最稳。 ### 第 4 步:放行端口(两层都要,缺一不可) | 层级 | 操作 | | --- | --- | | **云厂商安全组** | 阿里云控制台 → ECS → 安全组 → 入方向规则 → 放行 **TCP 3000、3001、3002、3003**(来源 0.0.0.0/0) | | **1Panel 防火墙** | 1Panel → 主机 → 防火墙 → 放行 3000、3001、3002、3003 | > 安全组是云厂商的外层防火墙,1Panel 是系统层防火墙,两层都放行玩家才能访问。 ### 第 5 步:玩家访问 | 入口 | 地址 | | --- | --- | | **游戏菜单页(推荐)** | `http://服务器公网IP:3000` | | 狼人杀(直达) | `http://服务器公网IP:3001` | | 你画我猜(直达) | `http://服务器公网IP:3002` | | 贪吃蛇大作战(直达) | `http://服务器公网IP:3003` | 把 **3000 菜单页**的地址发给朋友即可,点卡片进游戏,不用记端口。 手机用 4G/5G 直接访问即可,**无需与服务器在同一 WiFi**。把地址发给朋友,大家填相同房间号即可开局。 --- ## 日常更新代码 ### 本地:提交并推送 ```bash git add -A git commit -m "fix: 修改说明" git push ``` ### 服务器:拉取并重建 **方式 A:1Panel UI** ``` 1. 容器 → 编排 → 找到 lan-games 2. 点「重新构建并部署」 ``` > 不同 1Panel 版本按钮位置差异较大(有的在编排详情页右上角,有的在「⋯」下拉菜单里,有的要先「编辑」进 compose 编辑页再保存+重建)。如果实在找不到,用方式 B。 **方式 B:1Panel 终端命令(推荐,跨版本一致)** ```bash cd /lan-games git pull # 拉最新代码 git log --oneline -1 # 确认拉到的是预期版本(应显示最新 commit 的短哈希) # 重建并重启 docker compose down docker compose build --no-cache docker compose up -d # 验证(任选一条) docker compose ps curl -s http://127.0.0.1:3000 | head -1 ``` > 代码在构建时被打进镜像,所以每次改代码后都必须**重新构建**(仅重启容器不会加载新代码)。 --- ## 回滚到上一版 ### 方式 A:1Panel UI ``` 1. 1Panel 终端: cd /lan-games && git log --oneline # 查看提交历史 2. 1Panel 终端: git checkout <上一个提交的短哈希> 3. 编排页 → 「重新构建并部署」 ``` ### 方式 B:1Panel 终端命令(推荐) ```bash cd /lan-games git log --oneline # 找到要回滚的目标哈希 git checkout <目标哈希> # 重建docker compose down docker compose build --no-cache docker compose up -d ``` 要回到最新版本:`git checkout master && docker compose build --no-cache && docker compose up -d`。 --- ## 常用运维操作 | 操作 | 位置 | | --- | --- | | 看运行日志 | UI: 容器 → 容器详情 → 日志;或终端: `docker compose logs -f lan-games` | | 重启服务 | UI: 容器 → 编排/容器 → 重启;或终端: `docker compose restart` | | 停止/删除 | UI: 容器 → 编排 → 停止/删除;或终端: `docker compose down` | | 服务器开机自启 | `restart: always` 已配置,容器随 1Panel/Docker 自启,无需额外设置 | ## 部署常见问题 **Q:构建失败 / 拉镜像很慢?** 国内服务器拉取 `node:20-alpine` 可能较慢。可在 1Panel → 容器 → 配置里给 Docker 配置镜像加速器(registry-mirror),或耐心等待首次构建。 **Q:浏览器访问一直转圈/超时?** 99% 是端口没放行。先确认**云厂商安全组**和 **1Panel 防火墙**两层都放行了对应端口,再用 `curl http://127.0.0.1:3001` 在服务器上自测是否返回页面。 **Q:想改游戏时长参数?** 改对应 `server.js` 顶部常量后,按「日常更新代码」流程提交、拉取、重建即可。也可在 `docker-compose.yml` 里加 `environment` 用环境变量覆盖(见狼人杀/你画我猜各自的「可调参数」)。 **Q:中国大陆服务器用 IP 直连需要备案吗?** 不需要。备案只对**域名**解析到大陆服务器强制要求,直接 `http://公网IP:端口` 访问无需备案。若以后想绑定域名,域名必须先完成 ICP 备案。