diff --git a/QWEN.md b/QWEN.md index d70804b..797d208 100644 --- a/QWEN.md +++ b/QWEN.md @@ -17,9 +17,9 @@ `boardgame-core`的内容可以在`framework/node_modules/boardgame-core`找到。 这个文件夹被.gitignore忽略,查看时需要绕开这一限制。 -## 编写游戏 +## 编写GameModule -游戏逻辑以gameModule的形式定义: +游戏逻辑以GameModule的形式定义: ```typescript import {createGameCommandRegistry, IGameContext} from "boardgame-core"; @@ -38,17 +38,118 @@ export async function start(game: IGameContext) { export const registry = createGameCommandRegistry(); ``` -使用以下步骤创建游戏: +使用以下步骤创建GameModule: ### 1. 定义状态 -游戏状态使用一个大的`mutative`对象来描述,所有的状态更新通过`game.produce`或`game.produceAsync`来实现。 - 通常使用一个`regions: Record`和一个`parts: Record>`来记录桌游物件的摆放。 +```typescript +import {Region} from "boardgame-core"; + +type GameState = { + regions: { + white: Region, + black: Region, + board: Region, + }, + pieces: Record>, + currentPlayer: PlayerType, + winner: WinnerType, +}; +``` + +游戏的部件可以从`csv`加载。详情见`boop-game/node_modules/inline-schema/`。 +``` +/// parts.csv +type, player, count +string, string, number +cat, white, 8 +cat, black, 8 + +/// parts.csv.d.ts +type PartsTable = { + type: string; + player: string; + count: number; + }[]; + +declare const data: PartsTable; +export default data; +``` + ### 2. 定义流程 使用`async function start(game: IGameContext)`作为入口。 +使用`game.value`读取游戏当前状态 +```typescript +async function gameEnd(game: IGameContext) { + return game.value.winner; +} +``` + +需要修改状态时,使用`game.produce`或`game.produceAsync`。 + +```typescript +async function start(game: IGameContext) { + await game.produceAsync(state => { + state.currentPlayer = 'white'; + }); +} +``` + 需要等待玩家交互时,使用`await game.prompt(schema, validator, player)`。 +```typescript +async function turn(game: IGameContext, currentPlayer: PlayerType) { + const {player, row, col} = await game.prompt( + 'play ', + (player, row, col) => { + if(player !== currentPlayer) + throw `Wrong player!` + return {player, row, col}; + } + ) +} +``` + +### 3. 编写测试 + +使用`vitest`编写测试,测试应当使用`GameHost`来模拟游戏环境。 + +## 编写Phaser App + +使用`framework/src/ui/PhaserBridge`来创建Phaser App。 + +使用`framework/src/scenes/GameHostScene`来创建游戏场景。 + +使用`GameHost`来控制游戏状态。 + +```typescript +export class GameHost, TResult=unknown> { + // 获取游戏状态的只读快照 + get state(): TState{} + // 运行状态 + readonly status: ReadonlySignal; + + // 运行中途需要玩家输入时使用 + readonly activePromptSchema: ReadonlySignal; + readonly activePromptPlayer: ReadonlySignal; + + // 玩家响应activePrompt的输入,若报错则返回string,否则返回null + onInput(input: string): string | null {} + + // 添加中断,context.produceAsync会等待所有中断结束之后再继续 + addInterruption(promise: Promise): void {} + + // 开始或者重新开始游戏 + start(): Promise{} + + // 销毁 + dispose(): void {} + + // 事件侦听 + on(event: 'start' | 'dispose', listener: () => void): () => void {} +} +``` \ No newline at end of file diff --git a/packages/framework/src/scenes/GameHostScene.ts b/packages/framework/src/scenes/GameHostScene.ts index d994676..add7342 100644 --- a/packages/framework/src/scenes/GameHostScene.ts +++ b/packages/framework/src/scenes/GameHostScene.ts @@ -18,6 +18,10 @@ export abstract class GameHostScene> public get gameHost(): GameHost { return this._gameHost; } + + public get state(): TState { + return this.gameHost?.state.value; + } addInterruption(promise: Promise){ this.gameHost?.addInterruption(promise); @@ -41,11 +45,6 @@ export abstract class GameHostScene> this.disposables.dispose(); } - /** 获取当前状态的只读快照 */ - public get state(): TState { - return this.gameHost.context.value; - } - public addDisposable(disposable: IDisposable){ this.disposables.add(disposable); }