refactor: PromptDef
This commit is contained in:
parent
fe3bef0a01
commit
6cfb3b6df8
|
|
@ -3,8 +3,9 @@ import {
|
||||||
CommandSchema,
|
CommandSchema,
|
||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
PromptEvent,
|
PromptEvent,
|
||||||
|
parseCommandSchema,
|
||||||
} from '@/utils/command';
|
} from '@/utils/command';
|
||||||
import {createGameCommandRegistry, createGameContext, IGameContext} from './game';
|
import {createGameCommandRegistry, createGameContext, IGameContext, PromptDef} from './game';
|
||||||
|
|
||||||
export type GameHostStatus = 'created' | 'running' | 'disposed';
|
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);
|
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 动画)。
|
* 为下一个 produceAsync 注册中断 Promise(通常用于 UI 动画)。
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export interface IGameContext<TState extends Record<string, unknown> = {} > {
|
||||||
produceAsync(fn: (draft: TState) => void): Promise<void>;
|
produceAsync(fn: (draft: TState) => void): Promise<void>;
|
||||||
run<T>(input: string): Promise<CommandResult<T>>;
|
run<T>(input: string): Promise<CommandResult<T>>;
|
||||||
runParsed<T>(command: Command): 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;
|
addInterruption(promise: Promise<void>): void;
|
||||||
|
|
||||||
// test only
|
// test only
|
||||||
|
|
@ -47,8 +47,8 @@ export function createGameContext<TState extends Record<string, unknown> = {} >(
|
||||||
runParsed<T>(command: Command) {
|
runParsed<T>(command: Command) {
|
||||||
return commands.runParsed<T>(command);
|
return commands.runParsed<T>(command);
|
||||||
},
|
},
|
||||||
prompt(schema, validator, currentPlayer) {
|
prompt(def, validator, currentPlayer) {
|
||||||
return commands.prompt(schema, validator, currentPlayer);
|
return commands.prompt(def.schema, validator, currentPlayer);
|
||||||
},
|
},
|
||||||
addInterruption(promise) {
|
addInterruption(promise) {
|
||||||
state.addInterruption(promise);
|
state.addInterruption(promise);
|
||||||
|
|
@ -63,6 +63,13 @@ export function createGameContext<TState extends Record<string, unknown> = {} >(
|
||||||
return context;
|
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> = {} >() {
|
export function createGameCommandRegistry<TState extends Record<string, unknown> = {} >() {
|
||||||
return createCommandRegistry<IGameContext<TState>>();
|
return createCommandRegistry<IGameContext<TState>>();
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {Part} from "./part";
|
import {Part} from "./part";
|
||||||
import {Immutable} from "mutative";
|
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>> = {};
|
const pool: Record<string, Part<T>> = {};
|
||||||
for (const entry of items) {
|
for (const entry of items) {
|
||||||
const count = getCount ? (typeof getCount === 'function' ? getCount(entry) : getCount) : 1;
|
const count = getCount ? (typeof getCount === 'function' ? getCount(entry) : getCount) : 1;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
PlayerType,
|
PlayerType,
|
||||||
WinnerType,
|
WinnerType,
|
||||||
WIN_LENGTH,
|
WIN_LENGTH,
|
||||||
MAX_PIECES_PER_PLAYER, BoopGame
|
MAX_PIECES_PER_PLAYER,
|
||||||
|
BoopGame,
|
||||||
|
prompts
|
||||||
} from "./data";
|
} from "./data";
|
||||||
import {createGameCommandRegistry} from "@/core/game";
|
import {createGameCommandRegistry} from "@/core/game";
|
||||||
import {moveToRegion} from "@/core/region";
|
import {moveToRegion} from "@/core/region";
|
||||||
|
|
@ -186,8 +188,8 @@ async function handleCheckFullBoard(game: BoopGame, turnPlayer: PlayerType){
|
||||||
}
|
}
|
||||||
|
|
||||||
const partId = await game.prompt(
|
const partId = await game.prompt(
|
||||||
'choose <player> <row:number> <col:number>',
|
prompts.choose,
|
||||||
(player: PlayerType, row: number, col: number) => {
|
(player, row, col) => {
|
||||||
if (player !== turnPlayer) {
|
if (player !== turnPlayer) {
|
||||||
throw `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
throw `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
||||||
}
|
}
|
||||||
|
|
@ -218,8 +220,8 @@ const checkFullBoard = registry.register({
|
||||||
|
|
||||||
async function handleTurn(game: BoopGame, turnPlayer: PlayerType) {
|
async function handleTurn(game: BoopGame, turnPlayer: PlayerType) {
|
||||||
const {row, col, type} = await game.prompt(
|
const {row, col, type} = await game.prompt(
|
||||||
'play <player> <row:number> <col:number> [type:string]',
|
prompts.play,
|
||||||
(player: PlayerType, row: number, col: number, type?: PieceType) => {
|
(player, row, col, type) => {
|
||||||
const pieceType = type === 'cat' ? 'cat' : 'kitten';
|
const pieceType = type === 'cat' ? 'cat' : 'kitten';
|
||||||
|
|
||||||
if (player !== turnPlayer) {
|
if (player !== turnPlayer) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import {createRegion, moveToRegion, Region} from "@/core/region";
|
import {createRegion, moveToRegion, Region} from "@/core/region";
|
||||||
import {createPartsFromTable} from "@/core/part-factory";
|
import {createPartsFromTable} from "@/core/part-factory";
|
||||||
import {Part} from "@/core/part";
|
import {Part} from "@/core/part";
|
||||||
import {IGameContext} from "@/core/game";
|
import {createPromptDef, IGameContext} from "@/core/game";
|
||||||
|
|
||||||
export const BOARD_SIZE = 6;
|
export const BOARD_SIZE = 6;
|
||||||
export const MAX_PIECES_PER_PLAYER = 8;
|
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 RegionType = 'white' | 'black' | 'board' | '';
|
||||||
export type BoopPartMeta = { player: PlayerType; type: PieceType };
|
export type BoopPartMeta = { player: PlayerType; type: PieceType };
|
||||||
export type BoopPart = Part<BoopPartMeta>;
|
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() {
|
export function createInitialState() {
|
||||||
const pieces = createPartsFromTable(
|
const pieces = createPartsFromTable(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {
|
||||||
createGameCommandRegistry, Part, createRegion,
|
createGameCommandRegistry, Part, createRegion,
|
||||||
IGameContext
|
IGameContext
|
||||||
} from '@/index';
|
} from '@/index';
|
||||||
|
import {createPromptDef} from "@/core/game";
|
||||||
|
|
||||||
const BOARD_SIZE = 3;
|
const BOARD_SIZE = 3;
|
||||||
const MAX_TURNS = BOARD_SIZE * BOARD_SIZE;
|
const MAX_TURNS = BOARD_SIZE * BOARD_SIZE;
|
||||||
|
|
@ -35,6 +36,10 @@ export function createInitialState() {
|
||||||
export type TicTacToeState = ReturnType<typeof createInitialState>;
|
export type TicTacToeState = ReturnType<typeof createInitialState>;
|
||||||
export type TicTacToeGame = IGameContext<TicTacToeState>;
|
export type TicTacToeGame = IGameContext<TicTacToeState>;
|
||||||
export const registry = createGameCommandRegistry<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) {
|
export async function start(game: TicTacToeGame) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -59,8 +64,8 @@ const turn = registry.register({
|
||||||
schema: 'turn <player> <turnNumber:number>',
|
schema: 'turn <player> <turnNumber:number>',
|
||||||
async run(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) {
|
async run(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) {
|
||||||
const {player, row, col} = await game.prompt(
|
const {player, row, col} = await game.prompt(
|
||||||
'play <player> <row:number> <col:number>',
|
prompts.play,
|
||||||
(player: string, row: number, col: number) => {
|
(player, row, col) => {
|
||||||
if (player !== turnPlayer) {
|
if (player !== turnPlayer) {
|
||||||
throw `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
throw `Invalid player: ${player}. Expected ${turnPlayer}.`;
|
||||||
} else if (!isValidMove(row, col)) {
|
} else if (!isValidMove(row, col)) {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ describe('createGameContext', () => {
|
||||||
const ctx = createGameContext(registry);
|
const ctx = createGameContext(registry);
|
||||||
|
|
||||||
registry.register('test <value>', async function (_ctx, value) {
|
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 => {
|
const promptPromise = new Promise<PromptEvent>(resolve => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue