# 如何编写游戏模组 ## 要求 游戏模组需要以下接口导出: ``` import {createGameCommandRegistry, IGameContext} from "boardgame-core"; // 定义类型 export type GameState = { //... } export type Game = IGameContext; // 创建mutative游戏初始状态 export function createInitialState(): GameState { //... } // 运行游戏 export async function start(game: Game) { // ... } ``` ## 流程 0. 确认规则。 规则应当存放在`rule.md`。 描述一个桌面游戏的以下要素: - 主题 - 配件 - 游戏布置形式 - 游戏流程 - 玩家行动 - 胜利条件与终局结算 1. 创建类型:创建`types.ts`并导出游戏所用的类型。 - 为游戏概念创建字符串枚举类型。 - 使用`@/core/part.ts`为游戏配件创建对象类型。 - 使用`@/core/region.ts`为游戏区域创建容器类型。 - 设计游戏的全局状态类型。 游戏使用`mutative`不可变类型驱动。 2. 创建prompts: 使用prompt来描述需要玩家进行的行动命令schema。 - prompt包含一个id和若干params。 - 每个param通常指定某个配件id、某个枚举字符串、或者某个数字。 ```typescript export const prompts = { play: createPromptDef<[PlayerType, number, number]>( 'play ', '选择下子位置') } ``` 3. 创建游戏流程: ```typescript export async function start(game: TicTacToeGame) { while (true) { // game.value可获取当前的全局状态 const currentPlayer = game.value.currentPlayer; const turnNumber = game.value.turn + 1; const turnOutput = await turn(game, currentPlayer, turnNumber); // 更新状态 await game.produceAsync(state => { state.winner = turnOutput.winner; if (!state.winner) { state.currentPlayer = state.currentPlayer === 'X' ? 'O' : 'X'; state.turn = turnNumber; } }); if (game.value.winner) break; } return game.value; } ``` ```typescript async function run(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) { // 获取玩家输入 const {player, row, col} = await game.prompt( prompts.play, (player, row, col) => { if (player !== turnPlayer) { throw `Invalid player: ${player}. Expected ${turnPlayer}.`; } else if (!isValidMove(row, col)) { throw `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`; } else if (isCellOccupied(game, row, col)) { throw `Cell (${row}, ${col}) is already occupied.`; } else { return {player, row, col}; } }, game.value.currentPlayer ); placePiece(game, row, col, turnPlayer); } ``` 4. 创建测试 基于`@/core/game.ts`的createGameContext来测试。 为每种游戏结束条件准备至少一条测试。 为每种玩家行动至少准备一条测试。