refactor: PromptDef
This commit is contained in:
parent
fe3bef0a01
commit
6cfb3b6df8
|
|
@ -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<TState extends Record<string, unknown>, TResult=unknown> {
|
|||
}
|
||||
return this._context._commands._tryCommit(input);
|
||||
}
|
||||
|
||||
tryAnswerPrompt<TArgs extends any[]>(def: PromptDef<TArgs>, ...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 动画)。
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface IGameContext<TState extends Record<string, unknown> = {} > {
|
|||
produceAsync(fn: (draft: TState) => void): Promise<void>;
|
||||
run<T>(input: string): Promise<CommandResult<T>>;
|
||||
runParsed<T>(command: Command): Promise<CommandResult<T>>;
|
||||
prompt: <TResult,TArgs extends any[]=any[]>(schema: CommandSchema | string, validator: PromptValidator<TResult,TArgs>, currentPlayer?: string | null) => Promise<TResult>;
|
||||
prompt: <TResult,TArgs extends any[]=any[]>(def: PromptDef<TArgs>, validator: PromptValidator<TResult,TArgs>, currentPlayer?: string | null) => Promise<TResult>;
|
||||
addInterruption(promise: Promise<void>): void;
|
||||
|
||||
// test only
|
||||
|
|
@ -47,8 +47,8 @@ export function createGameContext<TState extends Record<string, unknown> = {} >(
|
|||
runParsed<T>(command: Command) {
|
||||
return commands.runParsed<T>(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<TState extends Record<string, unknown> = {} >(
|
|||
return context;
|
||||
}
|
||||
|
||||
export type PromptDef<TArgs extends any[]=any[]> = {
|
||||
schema: CommandSchema | string,
|
||||
}
|
||||
export function createPromptDef<TArgs extends any[]=any[]>(schema: CommandSchema | string): PromptDef<TArgs> {
|
||||
return { schema };
|
||||
}
|
||||
|
||||
export function createGameCommandRegistry<TState extends Record<string, unknown> = {} >() {
|
||||
return createCommandRegistry<IGameContext<TState>>();
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {Part} from "./part";
|
||||
import {Immutable} from "mutative";
|
||||
|
||||
export function createPartsFromTable<T>(items: T[], getId: (item: T, index: number) => string, getCount?: ((item: T) => number) | number){
|
||||
export function createPartsFromTable<T>(items: readonly T[], getId: (item: T, index: number) => string, getCount?: ((item: T) => number) | number){
|
||||
const pool: Record<string, Part<T>> = {};
|
||||
for (const entry of items) {
|
||||
const count = getCount ? (typeof getCount === 'function' ? getCount(entry) : getCount) : 1;
|
||||
|
|
|
|||
|
|
@ -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> <row:number> <col:number>',
|
||||
(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 <player> <row:number> <col:number> [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) {
|
||||
|
|
|
|||
|
|
@ -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<BoopPartMeta>;
|
||||
export const prompts = {
|
||||
play: createPromptDef<[PlayerType, number, number, PieceType?]>(
|
||||
'play <player> <row:number> <col:number> [type:string]'),
|
||||
choose: createPromptDef<[PlayerType, number, number]>(
|
||||
'choose <player> <row:number> <col:number>')
|
||||
}
|
||||
|
||||
export function createInitialState() {
|
||||
const pieces = createPartsFromTable(
|
||||
|
|
|
|||
|
|
@ -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<typeof createInitialState>;
|
||||
export type TicTacToeGame = IGameContext<TicTacToeState>;
|
||||
export const registry = createGameCommandRegistry<TicTacToeState>();
|
||||
export const prompts = {
|
||||
play: createPromptDef<[PlayerType, number, number]>(
|
||||
'play <player> <row:number> <col:number>')
|
||||
}
|
||||
|
||||
export async function start(game: TicTacToeGame) {
|
||||
while (true) {
|
||||
|
|
@ -59,8 +64,8 @@ const turn = registry.register({
|
|||
schema: 'turn <player> <turnNumber:number>',
|
||||
async run(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) {
|
||||
const {player, row, col} = await game.prompt(
|
||||
'play <player> <row:number> <col:number>',
|
||||
(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)) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe('createGameContext', () => {
|
|||
const ctx = createGameContext(registry);
|
||||
|
||||
registry.register('test <value>', async function (_ctx, value) {
|
||||
return this.prompt<string>('prompt <answer>', () => 'ok');
|
||||
return this.prompt({schema: 'prompt <answer>'}, () => 'ok');
|
||||
});
|
||||
|
||||
const promptPromise = new Promise<PromptEvent>(resolve => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue