用途
boardgame-io 技能用于基于 boardgame.io 框架(v0.50.x API)开发回合制游戏,包括棋盘游戏、卡牌游戏、骰子游戏与策略游戏。当用户需要创建、扩展或调试基于 boardgame.io 的游戏时使用,支持 JavaScript 或 TypeScript,可带 UI 或不带 UI。
主要能力
该技能引导完整开发工作流:设计游戏状态、定义规则、构建 UI、接入多人联机以及测试。它强调框架的核心运行机制——move/hook 函数在客户端(乐观更新)与服务器上以相同方式运行,所有状态通过网络序列化,并为撤销与机器人重放状态。
技能列出若干“黄金规则”以防止常见 bug:
- G 必须可 JSON 序列化,使用普通对象、数组、数字、字符串、布尔值、null,不使用类实例、函数,数组内不出现 undefined,用 ID/索引代替引用。
- 不直接修改 ctx,它由框架管理;结束回合、切换阶段等应调用 events.*;在 moves 中只读取 ctx(如 ctx.currentPlayer、ctx.numPlayers、ctx.playOrder、ctx.phase、ctx.activePlayers),不写入。
- 不使用 Math.random() 或 Date.now(),改用注入的 random API(如 random.D6()、random.Shuffle(deck)),它是有种子且确定性的,这是重放、撤销与机器人所必需的。
- 每个 move 要么修改 G 且不返回,要么返回新的 G,不能两者兼有。
- Moves 必须纯净且无副作用,不进行网络调用、不在热路径上 console.log、不读取外部状态,相同输入产生相同结果。
- 对 ctx.activePlayers 做空值保护,无活动阶段时它为 null。
- 玩家 ID 是字符串('0'、'1' 等),按字符串键存储每玩家状态,如 G.players[ctx.currentPlayer]。
- 用 INVALID_MOVE(来自 boardgame.io/core)拒绝非法操作,而不是抛出异常,并在 UI 处理器中让无效输入成为无操作。
v0.50 API 形态:每个回调接收单个解构的第一个参数,例如 moveName: ({ G, ctx, playerID, events, random }, ...args) => { ... }。0.50 之前将 (G, ctx) 作为两个参数传入的代码已过时,不应生成,遇到时应更新。
使用方式
技能提供五步构建流程:
- 明确游戏并设计 G:在写代码前列出玩家、棋盘/棋子表示、每个玩家动作对应一种 move 类型、回合顺序、胜负平条件,选择能表达这些的最简可序列化 G。
- 定义游戏逻辑(src/game.js):使用深导入 import { INVALID_MOVE } from 'boardgame.io/dist/esm/core.js',定义 setup、moves、turn、endIf 等。
- 构建棋盘 UI(src/board.jsx):React 组件通过 props 接收一切,渲染 G/ctx 并调用 props.moves.*,不在 UI 中计算游戏逻辑,只做显示层检查。
- 接入客户端(src/index.jsx):根据游戏方式选择模式——热座(无 multiplayer 选项)、本地双视图(Local() + 每个实例一个座位)、在线(SocketIO({ server: 'localhost:8000' }))。设置 multiplayer 后,没有 playerID 的客户端是旁观者,只能看不能操作;每个交互视图都需要座位。isActive 仅在本地玩家可行动时为 true,所有交互元素都应以此门控。
- 按需添加服务器与测试:热座或 Local() 游戏无需服务器;在线游戏使用 server.cjs(CommonJS),通过 require('boardgame.io/server') 引入,游戏保持 ESM。
导入路径方面:boardgame.io@0.50.x 没有 Node exports map,boardgame.io/client、/react、/multiplayer、/core 等子路径在打包器(Vite/webpack)中可解析,但在纯 Node 中会报 ERR_UNSUPPORTED_DIR_IMPORT。在 Node 运行的代码(测试、脚本)及其导入的文件(通常是 game.js)中,应使用深 ESM 构建,如 boardgame.io/dist/esm/client.js、boardgame.io/dist/esm/core.js。服务器仅支持 CJS,需从 .cjs 文件使用 require('boardgame.io/server')。仅浏览器文件(index.jsx、board.jsx)可使用普通子路径。
静态资源(卡牌美术、精灵图)遵循同样规则:不要在纯 Node 测试也会加载的模块(cards.js、game.js)中 import img from './card.jpg',Node 会报 ERR_UNKNOWN_FILE_EXTENSION。应使用 Vite 的 URL 资源模式:const img = (file) => new URL(./assets/cards/${file}, import.meta.url).href。
测试游戏逻辑可无头进行,无需 React:使用 import { Client } from 'boardgame.io/dist/esm/client.js',然后调用 client.moves.* 并检查 client.getState()。
构建新游戏时,技能强调“复制,不要重写”:从可运行的游戏(journey-west 或 card-game 模板)开始,只改主题(卡牌、名称、故事文本、CSS 颜色),保持引擎架构一致(相同的 game.js 结构、bot.js 模式、board.jsx 布局、index.jsx 设置),每次改动后在真实浏览器中测试。单人对 AI 的成熟架构为:index.jsx 使用 multiplayer: Local() + createBossClient(机器人订阅并在玩家 1 回合行动);game.js 使用 endIf 判胜负、playerView 处理隐藏信息、turn.onBegin 处理抽牌/重置、events.endTurn() 切换回合;bot.js 是脚本化 AI,在其回合调用 client.moves.*。移除 multiplayer: Local() 会杀死 AI 对手,不要以此“修复”点击问题。故事模式可作为包装层,用 G.mode('menu' | 'story' | 'battle')在 board.jsx 中切换 UI 视图,战斗引擎不感知故事。
技能提供两个现成项目骨架(assets/templates/,均为 vite + react,含测试与可选服务器):minimal/ 用于棋盘游戏与快速上手(井字棋),演示 moves、INVALID_MOVE、回合限制、endIf、一键测试;card-game/ 用于卡牌游戏、隐藏手牌/牌堆、战斗,演示 playerView 秘密状态、random.Shuffle、client: false 法术、G.step 回合子阶段、每个动作的状态行反馈、重启按钮、整局自动游玩测试。任何含隐藏信息或每回合子阶段的游戏,建议从 card-game/ 开始。
参考文档按需阅读:references/game-definition.md 涵盖阶段、舞台、setActivePlayers、自定义回合顺序、事件、机器人/AI、长格式 moves、playerView/秘密状态;references/client-server.md 涵盖 React 客户端选项、纯 JS 客户端、SocketIO 多人、服务器配置、大厅(创建/加入对局)、持久化;references/advanced.md 涵盖随机 API、撤销策略、测试模式、TypeScript、调试面板、性能;references/game-feel.md 涵盖音效、动画、动作展示/叙述、游戏手感。
适用场景
适用于任何创建、扩展或调试基于 boardgame.io 的游戏请求,包括井字棋、国际象棋、卡牌、骰子、策略游戏,棋牌、桌游、回合制游戏,使用 JavaScript 或 TypeScript,带或不带 UI。
限制
技能资料中未提供关于收费、价格、平台支持范围或效果保证的信息。技能本身为开发指导,不包含运行时服务。部分功能(如在线多人、大厅、持久化)需要自行搭建服务器。具体 API 选项名称与 hook 支持矩阵需查阅对应参考文档。