--- name: Create Game Module description: Create a runnable logic module for a board game with 'boardgame-core', '@preact/signals' and 'mutative' --- # 如何编写游戏模组 ## 必须导出的接口 ```typescript import { IGameContext, createGameCommandRegistry } from '@/index'; export type GameState = { /* 你的状态类型 */ }; export type Game = IGameContext; export function createInitialState(): GameState { /* ... */ } export async function start(game: Game) { /* 游戏主循环 */ } ``` 或导出为 `GameModule` 对象: ```typescript export const gameModule: GameModule = { registry, createInitialState, start }; ``` ## 流程 ### 0. 确认规则 在 `rule.md` 中描述:主题、配件、布置形式、游戏流程、玩家行动、胜利条件。 ### 1. 创建类型 (`types.ts`) - 使用字符串枚举表示游戏概念(如 `PlayerType = 'X' | 'O'`) - 使用 `Part` 表示配件(棋子、卡牌等) - 使用 `Region` 表示区域(棋盘、牌堆等) - 状态必须可序列化(不支持函数、`Map`、`Set`) ```typescript import { Part, Region } from '@/index'; export type Piece = Part<{ owner: 'X' | 'O' }>; export type GameState = { board: Region; pieces: Record; currentPlayer: 'X' | 'O'; turn: number; winner: 'X' | 'O' | null; }; ``` ### 2. 创建配件 少量配件直接在代码创建: ```typescript const pieces = createParts({ owner: 'X' }, (i) => `piece-${i}`, 5); ``` 大量配件使用 CSV: ```csv # parts.csv type,player,count string,string,int kitten,white,8 ``` ```typescript import parts from "./parts.csv"; const pieces = createPartsFromTable(parts, (item, i) => `${item.type}-${i}`, (item) => item.count); ``` ### 3. 创建 Prompts ```typescript export const prompts = { play: createPromptDef<['X' | 'O', number, number]>( 'play ', '选择下子位置' ), }; ``` Schema 语法:`` 必需,`[param]` 可选,`[param:type]` 带验证。详见 [API 参考](./references/api.md)。 ### 4. 创建游戏流程 ```typescript export async function start(game: Game) { while (!game.value.winner) { const { row, col } = await game.prompt( prompts.play, (player, row, col) => { if (player !== game.value.currentPlayer) throw '无效玩家'; if (!isValidMove(row, col)) throw '无效位置'; if (isCellOccupied(game, row, col)) throw '位置已被占用'; return { row, col }; }, game.value.currentPlayer ); game.produce((state) => { state.pieces[`p-${row}-${col}`] = { /* ... */ }; }); const winner = checkWinner(game); if (winner) { game.produce((state) => { state.winner = winner; }); break; } game.produce((state) => { state.currentPlayer = state.currentPlayer === 'X' ? 'O' : 'X'; state.turn++; }); } return game.value; } ``` **注意事项:** - `game.produce(fn)` 同步更新,`game.produceAsync(fn)` 异步更新(等待动画) - 验证器抛出字符串表示失败,返回值表示成功 - 玩家取消时 `game.prompt()` 抛出异常 - 循环必须有明确退出条件 ### 5. 创建测试 位于 `tests/samples/.test.ts`: ```typescript import { describe, it, expect } from 'vitest'; import { createGameContext } from '@/core/game'; import { registry, createInitialState } from './my-game'; describe('My Game', () => { function createTestContext() { return createGameContext(registry, createInitialState()); } it('should perform action correctly', async () => { const game = createTestContext(); await game.run('play X 0 0'); expect(game.value.pieces['p-0-0']).toBeDefined(); }); it('should fail on invalid input', async () => { const game = createTestContext(); const result = await game.run('invalid'); expect(result.success).toBe(false); }); it('should complete a full game cycle', async () => { // 模拟完整游戏流程,验证胜利条件 }); }); ``` **要求:** - 每种游戏结束条件、玩家行动、边界情况各至少一条测试 - 使用 `createGameContext(registry, initialState)` 创建上下文 - 使用 `game.run('command')` 执行命令,验证 `game.value` 状态 - 测试命名使用 `should...` 格式,异步测试用 `async/await` **运行测试:** ```bash npm run test:run # 所有测试 npx vitest run tests/samples/my-game.test.ts # 特定文件 npx vitest run -t "should perform" # 特定用例 ``` ## 完整示例 参考 `src/samples/boop/` 和 `src/samples/regicide/`。 ## 相关资源 - [API 参考](./references/api.md) - 完整 API 文档 - [AGENTS.md](../../AGENTS.md) - 项目代码规范