diff --git a/skills/game-module.md b/skills/game-module.md new file mode 100644 index 0000000..79f3341 --- /dev/null +++ b/skills/game-module.md @@ -0,0 +1,117 @@ +# 如何编写游戏模组 + +## 要求 + +游戏模组需要以下接口导出: +``` +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来测试。 + +为每种游戏结束条件准备至少一条测试。 +为每种玩家行动至少准备一条测试。 \ No newline at end of file