Files
huajishe-tts/docs/dev-log/08-local-lan-multiplayer.md

5.9 KiB
Raw Permalink Blame History

08 · 本地局域网联机

做什么

用户要求做联机对战,但不是部署到云服务器,而是本地局域网(用户机器上跑 server同一 LAN 内的其他设备/浏览器通过 ws://[lan-ip]:8000 连接)。

目标:

  1. Server 能跑起来tsx 模式即可,不一定要 Docker
  2. Web 客户端有"本地" / "联机" 模式切换
  3. 联机模式下输入 server URL + 房间 ID + 玩家凭证
  4. 两台浏览器开同一房间,互相操作能实时同步

遇到什么问题

问题 1指挥官方向错了

最初按 plan 走 P2.1"部署到公共域名",写了 Dockerfile + docker-compose.yml + 生产配置。但用户没有公共域名的服务器访问权限给我,也不想在云上部署。需要先在本地 LAN 跑起来验证。

解决撤回云部署路径专注本地开发。Dockerfile 作为 v2 部署的素材保留,但当前目标是:

  • server 用 pnpm --filter @tts-like/server devtsx watch
  • 客户端配置 http://localhost:8000(同一机器)或 http://192.168.x.x:8000LAN 内其他机器)

问题 2SocketIO URL 协议搞错

最初我用 ws://localhost:8000,浏览器报错 ERR_NAME_NOT_RESOLVED for http://ws/socket.io/...。 原因SocketIO 接受 HTTP URL内部自动升级到 WebSocket。ws:// 是浏览器用来直接连 WebSocket 的协议,不是 SocketIO 期望的。

解决:改成 http://localhost:8000

问题 3player 1 调 move 被拒

点击 收牌player 1 操作)时报 disallowed move: collect。 原因boardgame.io 默认 currentPlayer = '0'SocketIO 传输层只允许 currentPlayer 调 move。War 没配置 turn所以永远是 player 0 active。

解决:在 War 加 turn: { activePlayers: ActivePlayers.ALL }

import { ActivePlayers } from 'boardgame.io/core';
// ...
turn: { activePlayers: ActivePlayers.ALL },

⚠️ boardgame.io(主入口)导出的是 type ActivePlayers,常量对象要从 boardgame.io/core 导入。

问题 4ActivePlayers 类型/值混淆

import { ActivePlayers } from 'boardgame.io' 拿到的是 type alias接口不是 const 对象。编译报错:'ActivePlayers' only refers to a type, but is being used as a value

解决:从 boardgame.io/core 导入:

import { ActivePlayers } from 'boardgame.io/core';

问题 5Docker 镜像跑不起来(与本地无关)

试过 node packages/server/dist/index.js 启动 Docker 镜像,报错:

ERR_UNSUPPORTED_DIR_IMPORT: Directory import '...boardgame.io/server'

原因ESM 模式下 Node 拒绝目录导入(即便 package.json 里有 main 字段)。

解决:暂时不用 Docker 镜像撤回。Task #8 标题改为"本地局域网联机"。Dockerfile 保留v2 部署时再修。

📝 Docker 镜像运行的方案(留作未来):

  • 方案 A保持 ESM在 server/package.json 加 "main": "./dist/index.js" + babel-plugin-transform-modules-commonjs 编译产物
  • 方案 B改 server 为 CJS移除 "type": "module"、tsconfig 改 "module": "CommonJS"moduleResolution: "Node16")——但 boardgame.io/server 是 CJS会让 server 内部 import 反向from ESM to CJS里 ESM 调用 CJS 通常 OKimport.meta 之类用不了)

怎么解决

步骤 1让 server 跑起来

pnpm --filter @tts-like/server dev
# tsx watch src/index.ts
# 🎲 tts-like server listening on :8000

/healthz 端点 + 注册 3 个游戏 + 环境变量读 ALLOWED_ORIGINS。

步骤 2web App 加 Online 模式

顶部加"本地 / 联机"切换按钮。联机模式下显示一个 OnlineConfigBar

  • Server URL默认 http://localhost:8000
  • Room ID默认 room1
  • Player0 / 1
  • Secret默认 p0 / p1

localStorage 持久化。

步骤 33 个游戏分别有 Online 视图

import { SocketIO } from 'boardgame.io/multiplayer';

Client({
  game,
  board: ...,
  multiplayer: SocketIO({ server: config.serverUrl }),
  debug: false,
});

// 使用时
<OnlineClient
  matchID={config.matchID}
  playerID={config.playerID}
  credentials={config.credentials}
/>

步骤 4end-to-end 验证

用 Playwright 打开 2 个 tab

  • Tab 0player 0, secret p0
  • Tab 1player 1, secret p1
  • 同一 room "room1"

操作:

  • Tab 0 点 翻牌 → 双方 deck 26→25双方 pile 各 1 张
  • Tab 1 点 收牌 → 7♣ 赢p0-deck 25→27
  • 两个 tab 状态完全同步

成果

  • Commit 585930c feat(multiplayer): LAN/local online mode via SocketIO
  • 7 文件改动,+426/-92 行
  • 服务器 tsx 跑通:
    • pnpm --filter @tts-like/server dev → 监听 :8000
    • /healthz 返回 {"status":"ok","port":8000,"uptime":3.57}
  • 2 客户端 SocketIO 状态实时同步验证通过

当前测试方式(你接下来怎么用)

同一台机器(最简单)

# 终端 1
pnpm --filter @tts-like/server dev

# 终端 2
pnpm --filter @tts-like/web dev

# 浏览器开 2 个 tab (或 2 个隐身窗口) → 都进联机模式
# Tab 1: Player 0, Secret p0
# Tab 2: Player 1, Secret p1
# 同一 Room ID (默认 room1)

跨设备LAN

  • 看 server 机器的局域网 IPifconfigipconfig192.168.x.x
  • 客户端的 Server URL 填 http://192.168.x.x:8000
  • 防火墙放行 8000 端口

用户验证

  • Open 2 browser tabs to same http://localhost:5173
  • Switch to War → 联机 → 默认配置player 0 / room1 / p0
  • 在 tab 1 改成 Player 1, Secret p1
  • 在 tab 0 点 翻牌 → 两边都看到翻牌结果
  • 在 tab 1 点 收牌 → 两边都看到收牌结果

关联

  • 03-monorepo-scaffold.md — 之前 query 错误方向,撤回 Docker 部署
  • lessons-learned.md §23 — 这一轮的踩坑SocketIO URL protocol / ActivePlayers 导入 / disallowed move
  • docs/decisions.md — 决策记录更新