diff --git a/src/core/game-host.ts b/src/core/game-host.ts index 26f04c4..da2de92 100644 --- a/src/core/game-host.ts +++ b/src/core/game-host.ts @@ -3,8 +3,9 @@ import { CommandSchema, CommandRegistry, PromptEvent, + parseCommandSchema, } from '@/utils/command'; -import {createGameCommandRegistry, createGameContext, IGameContext} from './game'; +import {createGameCommandRegistry, createGameContext, IGameContext, PromptDef} from './game'; export type GameHostStatus = 'created' | 'running' | 'disposed'; @@ -74,6 +75,16 @@ export class GameHost, TResult=unknown> { } return this._context._commands._tryCommit(input); } + + tryAnswerPrompt(def: PromptDef, ...args: TArgs){ + if(typeof def.schema === 'string') def.schema = parseCommandSchema(def.schema); + return this._context._commands._tryCommit({ + name: def.schema.name, + params: args, + options: {}, + flags: {} + }); + } /** * 为下一个 produceAsync 注册中断 Promise(通常用于 UI 动画)。 diff --git a/src/core/game.ts b/src/core/game.ts index 978c902..18fce66 100644 --- a/src/core/game.ts +++ b/src/core/game.ts @@ -15,7 +15,7 @@ export interface IGameContext = {} > { produceAsync(fn: (draft: TState) => void): Promise; run(input: string): Promise>; runParsed(command: Command): Promise>; - prompt: (schema: CommandSchema | string, validator: PromptValidator, currentPlayer?: string | null) => Promise; + prompt: (def: PromptDef, validator: PromptValidator, currentPlayer?: string | null) => Promise; addInterruption(promise: Promise): void; // test only @@ -47,8 +47,8 @@ export function createGameContext = {} >( runParsed(command: Command) { return commands.runParsed(command); }, - prompt(schema, validator, currentPlayer) { - return commands.prompt(schema, validator, currentPlayer); + prompt(def, validator, currentPlayer) { + return commands.prompt(def.schema, validator, currentPlayer); }, addInterruption(promise) { state.addInterruption(promise); @@ -63,6 +63,13 @@ export function createGameContext = {} >( return context; } +export type PromptDef = { + schema: CommandSchema | string, +} +export function createPromptDef(schema: CommandSchema | string): PromptDef { + return { schema }; +} + export function createGameCommandRegistry = {} >() { return createCommandRegistry>(); } \ No newline at end of file diff --git a/src/core/part-factory.ts b/src/core/part-factory.ts index 0fbb77b..94d0ac2 100644 --- a/src/core/part-factory.ts +++ b/src/core/part-factory.ts @@ -1,7 +1,7 @@ import {Part} from "./part"; import {Immutable} from "mutative"; -export function createPartsFromTable(items: T[], getId: (item: T, index: number) => string, getCount?: ((item: T) => number) | number){ +export function createPartsFromTable(items: readonly T[], getId: (item: T, index: number) => string, getCount?: ((item: T) => number) | number){ const pool: Record> = {}; for (const entry of items) { const count = getCount ? (typeof getCount === 'function' ? getCount(entry) : getCount) : 1; diff --git a/src/samples/boop/commands.ts b/src/samples/boop/commands.ts index 4402c34..c062e2c 100644 --- a/src/samples/boop/commands.ts +++ b/src/samples/boop/commands.ts @@ -5,7 +5,9 @@ PlayerType, WinnerType, WIN_LENGTH, - MAX_PIECES_PER_PLAYER, BoopGame + MAX_PIECES_PER_PLAYER, + BoopGame, + prompts } from "./data"; import {createGameCommandRegistry} from "@/core/game"; import {moveToRegion} from "@/core/region"; @@ -186,8 +188,8 @@ async function handleCheckFullBoard(game: BoopGame, turnPlayer: PlayerType){ } const partId = await game.prompt( - 'choose ', - (player: PlayerType, row: number, col: number) => { + prompts.choose, + (player, row, col) => { if (player !== turnPlayer) { throw `Invalid player: ${player}. Expected ${turnPlayer}.`; } @@ -218,8 +220,8 @@ const checkFullBoard = registry.register({ async function handleTurn(game: BoopGame, turnPlayer: PlayerType) { const {row, col, type} = await game.prompt( - 'play [type:string]', - (player: PlayerType, row: number, col: number, type?: PieceType) => { + prompts.play, + (player, row, col, type) => { const pieceType = type === 'cat' ? 'cat' : 'kitten'; if (player !== turnPlayer) { diff --git a/src/samples/boop/data.ts b/src/samples/boop/data.ts index fcb33ef..bff32e7 100644 --- a/src/samples/boop/data.ts +++ b/src/samples/boop/data.ts @@ -2,7 +2,7 @@ import {createRegion, moveToRegion, Region} from "@/core/region"; import {createPartsFromTable} from "@/core/part-factory"; import {Part} from "@/core/part"; -import {IGameContext} from "@/core/game"; +import {createPromptDef, IGameContext} from "@/core/game"; export const BOARD_SIZE = 6; export const MAX_PIECES_PER_PLAYER = 8; @@ -14,6 +14,12 @@ export type WinnerType = PlayerType | 'draw' | null; export type RegionType = 'white' | 'black' | 'board' | ''; export type BoopPartMeta = { player: PlayerType; type: PieceType }; export type BoopPart = Part; +export const prompts = { + play: createPromptDef<[PlayerType, number, number, PieceType?]>( + 'play [type:string]'), + choose: createPromptDef<[PlayerType, number, number]>( + 'choose ') +} export function createInitialState() { const pieces = createPartsFromTable( diff --git a/src/samples/tic-tac-toe.ts b/src/samples/tic-tac-toe.ts index c1673af..5de066f 100644 --- a/src/samples/tic-tac-toe.ts +++ b/src/samples/tic-tac-toe.ts @@ -2,6 +2,7 @@ import { createGameCommandRegistry, Part, createRegion, IGameContext } from '@/index'; +import {createPromptDef} from "@/core/game"; const BOARD_SIZE = 3; const MAX_TURNS = BOARD_SIZE * BOARD_SIZE; @@ -35,6 +36,10 @@ export function createInitialState() { export type TicTacToeState = ReturnType; export type TicTacToeGame = IGameContext; export const registry = createGameCommandRegistry(); +export const prompts = { + play: createPromptDef<[PlayerType, number, number]>( + 'play ') +} export async function start(game: TicTacToeGame) { while (true) { @@ -59,8 +64,8 @@ const turn = registry.register({ schema: 'turn ', async run(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) { const {player, row, col} = await game.prompt( - 'play ', - (player: string, row: number, col: number) => { + prompts.play, + (player, row, col) => { if (player !== turnPlayer) { throw `Invalid player: ${player}. Expected ${turnPlayer}.`; } else if (!isValidMove(row, col)) { diff --git a/tests/core/game.test.ts b/tests/core/game.test.ts index 904cd65..4acb49a 100644 --- a/tests/core/game.test.ts +++ b/tests/core/game.test.ts @@ -51,7 +51,7 @@ describe('createGameContext', () => { const ctx = createGameContext(registry); registry.register('test ', async function (_ctx, value) { - return this.prompt('prompt ', () => 'ok'); + return this.prompt({schema: 'prompt '}, () => 'ok'); }); const promptPromise = new Promise(resolve => {