From e673f60657e074f406f1bf341205fd7fb32605a1 Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 6 Apr 2026 10:39:10 +0800 Subject: [PATCH] refactor: update api --- src/core/game-host.ts | 20 ++++++++++++-------- src/samples/boop/commands.ts | 6 +----- src/samples/tic-tac-toe.ts | 35 ++++++++++++++++------------------- tests/core/game-host.test.ts | 31 ++++++++++++++++--------------- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/core/game-host.ts b/src/core/game-host.ts index 0f20223..213ee80 100644 --- a/src/core/game-host.ts +++ b/src/core/game-host.ts @@ -4,14 +4,13 @@ import type { CommandRegistry, PromptEvent, CommandRunnerContextExport, - CommandResult } from '@/utils/command'; import type { MutableSignal } from '@/utils/mutable-signal'; -import {createGameContext, IGameContext} from './game'; +import {createGameCommandRegistry, createGameContext, IGameContext} from './game'; export type GameHostStatus = 'created' | 'running' | 'disposed'; -export class GameHost> { +export class GameHost, TResult=unknown> { readonly context: IGameContext; readonly status: ReadonlySignal; readonly activePromptSchema: ReadonlySignal; @@ -19,6 +18,7 @@ export class GameHost> { private _state: MutableSignal; private _commands: CommandRunnerContextExport>; + private _start: (ctx: IGameContext) => Promise; private _status: Signal; private _activePromptSchema: Signal; private _activePromptPlayer: Signal; @@ -29,12 +29,14 @@ export class GameHost> { constructor( registry: CommandRegistry>, createInitialState: () => TState, + start: (ctx: IGameContext) => Promise ) { this._createInitialState = createInitialState; this._eventListeners = new Map(); const initialState = createInitialState(); this.context = createGameContext(registry, initialState); + this._start = start; this._status = new Signal('created'); this.status = this._status; @@ -94,7 +96,7 @@ export class GameHost> { this._state.clearInterruptions(); } - start(startCommand: string): Promise> { + start(): Promise { if (this._isDisposed) { throw new Error('GameHost is disposed'); } @@ -104,7 +106,7 @@ export class GameHost> { const initialState = this._createInitialState(); this._state.value = initialState as any; - const promise = this._commands.run(startCommand); + const promise = this._start(this.context); this._status.value = 'running'; this._emitEvent('start'); @@ -147,16 +149,18 @@ export class GameHost> { } } -export type GameModule> = { - registry: CommandRegistry>; +export type GameModule, TResult=unknown> = { + registry?: CommandRegistry>; createInitialState: () => TState; + start: (ctx: IGameContext) => Promise; } export function createGameHost>( gameModule: GameModule ): GameHost { return new GameHost( - gameModule.registry, + gameModule.registry || createGameCommandRegistry(), gameModule.createInitialState, + gameModule.start ); } diff --git a/src/samples/boop/commands.ts b/src/samples/boop/commands.ts index e1f828f..6410e6a 100644 --- a/src/samples/boop/commands.ts +++ b/src/samples/boop/commands.ts @@ -159,7 +159,7 @@ const checkGraduates = registry.register({ run: handleCheckGraduates }); -async function handleStart(game: BoopGame) { +export async function start(game: BoopGame) { while (true) { const currentPlayer = game.value.currentPlayer; const turnOutput = await turn(game, currentPlayer); @@ -175,10 +175,6 @@ async function handleStart(game: BoopGame) { return game.value; } -const start = registry.register({ - schema: 'start', - run: handleStart -}); async function handleCheckFullBoard(game: BoopGame, turnPlayer: PlayerType){ // 检查8-piece规则: 如果玩家所有8个棋子都在棋盘上且没有获胜,强制升级一个小猫 diff --git a/src/samples/tic-tac-toe.ts b/src/samples/tic-tac-toe.ts index 25867a2..e46342f 100644 --- a/src/samples/tic-tac-toe.ts +++ b/src/samples/tic-tac-toe.ts @@ -36,27 +36,24 @@ export type TicTacToeState = ReturnType; export type TicTacToeGame = IGameContext; export const registry = createGameCommandRegistry(); -registry.register({ - schema: 'start', - async run(game: TicTacToeGame) { - while (true) { - const currentPlayer = game.value.currentPlayer; - const turnNumber = game.value.turn + 1; - const turnOutput = await turn(game, currentPlayer, turnNumber); +export async function start(game: TicTacToeGame) { + while (true) { + const currentPlayer = game.value.currentPlayer; + const turnNumber = game.value.turn + 1; + const turnOutput = await turn(game, currentPlayer, turnNumber); - game.produce(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; + game.produce(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; +} const turn = registry.register({ schema: 'turn ', diff --git a/tests/core/game-host.test.ts b/tests/core/game-host.test.ts index d790974..98b2b61 100644 --- a/tests/core/game-host.test.ts +++ b/tests/core/game-host.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest'; import { registry, createInitialState, + start, TicTacToeState, WinnerType, PlayerType @@ -12,7 +13,7 @@ import { MutableSignal } from '@/utils/mutable-signal'; function createTestHost() { const host = createGameHost( - { registry, createInitialState } + { registry, createInitialState, start } ); return { host }; } @@ -59,7 +60,7 @@ describe('GameHost', () => { const { host } = createTestHost(); const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); const promptEvent = await promptPromise; expect(promptEvent.schema.name).toBe('play'); @@ -81,7 +82,7 @@ describe('GameHost', () => { const { host } = createTestHost(); const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); const promptEvent = await promptPromise; @@ -106,7 +107,7 @@ describe('GameHost', () => { const { host } = createTestHost(); const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); const promptEvent = await promptPromise; const schema = host.activePromptSchema.value; @@ -131,7 +132,7 @@ describe('GameHost', () => { // First setup - make one move let promptPromise = waitForPromptEvent(host); - let runPromise = host.context._commands.run('start'); + let runPromise = host.start(); let promptEvent = await promptPromise; // Make a move @@ -150,7 +151,7 @@ describe('GameHost', () => { const newPromptPromise = waitForPromptEvent(host); // Reset - should reset state and start new game - host.start('start'); + host.start(); // State should be back to initial expect(host.context._state.value.currentPlayer).toBe('X'); @@ -168,12 +169,12 @@ describe('GameHost', () => { const { host } = createTestHost(); const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); await promptPromise; // Setup should cancel the active prompt and reset state - host.start('start'); + host.start(); // The original runPromise should be rejected due to cancellation try { @@ -192,7 +193,7 @@ describe('GameHost', () => { const { host } = createTestHost(); host.dispose(); - expect(() => host.start('start')).toThrow('GameHost is disposed'); + expect(() => host.start()).toThrow('GameHost is disposed'); }); }); @@ -208,7 +209,7 @@ describe('GameHost', () => { const { host } = createTestHost(); const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); await promptPromise; @@ -245,7 +246,7 @@ describe('GameHost', () => { const promptPromise = waitForPromptEvent(host); // Initial setup via reset - host.start('start'); + host.start(); expect(setupCount).toBe(1); // State should be running @@ -294,7 +295,7 @@ describe('GameHost', () => { // Make a move const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); const promptEvent = await promptPromise; promptEvent.tryCommit({ name: 'play', params: ['X', 1, 1], options: {}, flags: {} }); @@ -320,7 +321,7 @@ describe('GameHost', () => { // Start a command that triggers prompt const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); await promptPromise; @@ -369,7 +370,7 @@ describe('GameHost', () => { }); // Start setup command (runs game loop until completion) - const setupPromise = host.context._commands.run('start'); + const setupPromise = host.start(); for (let i = 0; i < moves.length; i++) { // Wait until the next prompt event arrives @@ -415,7 +416,7 @@ describe('GameHost', () => { const { host } = createTestHost(); const promptPromise = waitForPromptEvent(host); - const runPromise = host.context._commands.run('start'); + const runPromise = host.start(); const promptEvent = await promptPromise; expect(promptEvent.currentPlayer).toBe('X');