From 1e1d04777fed188dc577d4a424cba35c8d18c666 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 19 Apr 2026 15:44:59 +0800 Subject: [PATCH] refactor: reformat code and fix type signatures in GameHost Reformat `src/core/game-host.ts` to use double quotes and consistent spacing. Update `createGameHost` to correctly propagate the `TResult` generic from `GameModule`. --- src/core/game-host.ts | 322 ++++++++++++++++++++++-------------------- 1 file changed, 165 insertions(+), 157 deletions(-) diff --git a/src/core/game-host.ts b/src/core/game-host.ts index 646b983..27f1b07 100644 --- a/src/core/game-host.ts +++ b/src/core/game-host.ts @@ -1,180 +1,188 @@ -import { ReadonlySignal, Signal } from '@preact/signals-core'; +import { ReadonlySignal, Signal } from "@preact/signals-core"; +import { CommandSchema, CommandRegistry, PromptEvent } from "@/utils/command"; import { - CommandSchema, - CommandRegistry, - PromptEvent, -} from '@/utils/command'; -import {createGameCommandRegistry, createGameContext, IGameContext, PromptDef} from './game'; + createGameCommandRegistry, + createGameContext, + IGameContext, + PromptDef, +} from "./game"; -export type GameHostStatus = 'created' | 'running' | 'disposed'; +export type GameHostStatus = "created" | "running" | "disposed"; -export class GameHost, TResult=unknown> { - readonly state: ReadonlySignal; - readonly status: ReadonlySignal; - readonly activePromptSchema: ReadonlySignal; - readonly activePromptPlayer: ReadonlySignal; - readonly activePromptHint: ReadonlySignal; +export class GameHost< + TState extends Record, + TResult = unknown, +> { + readonly state: ReadonlySignal; + readonly status: ReadonlySignal; + readonly activePromptSchema: ReadonlySignal; + readonly activePromptPlayer: ReadonlySignal; + readonly activePromptHint: ReadonlySignal; - private _context: IGameContext; - private _start: (ctx: IGameContext) => Promise; - private _status: Signal; - private _activePromptSchema: Signal; - private _activePromptPlayer: Signal; - private _activePromptHint: Signal; - private _createInitialState: () => TState; - private _eventListeners: Map<'start' | 'dispose', Set<() => void>>; - private _isDisposed = false; + private _context: IGameContext; + private _start: (ctx: IGameContext) => Promise; + private _status: Signal; + private _activePromptSchema: Signal; + private _activePromptPlayer: Signal; + private _activePromptHint: Signal; + private _createInitialState: () => TState; + private _eventListeners: Map<"start" | "dispose", Set<() => void>>; + private _isDisposed = false; - constructor( - registry: CommandRegistry>, - createInitialState: () => TState, - start: (ctx: IGameContext) => Promise - ) { - this._createInitialState = createInitialState; - this._eventListeners = new Map(); + 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.state = this._context._state; + const initialState = createInitialState(); + this._context = createGameContext(registry, initialState); + this._start = start; + this.state = this._context._state; - this._status = new Signal('created'); - this.status = this._status; + this._status = new Signal("created"); + this.status = this._status; - this._activePromptSchema = new Signal(null); - this.activePromptSchema = this._activePromptSchema; + this._activePromptSchema = new Signal(null); + this.activePromptSchema = this._activePromptSchema; - this._activePromptPlayer = new Signal(null); - this.activePromptPlayer = this._activePromptPlayer; - - this._activePromptHint = new Signal(null); - this.activePromptHint = this._activePromptHint; + this._activePromptPlayer = new Signal(null); + this.activePromptPlayer = this._activePromptPlayer; - this._setupPromptTracking(); + this._activePromptHint = new Signal(null); + this.activePromptHint = this._activePromptHint; + + this._setupPromptTracking(); + } + + private _setupPromptTracking() { + let currentPromptEvent: PromptEvent | null = null; + + this._context._commands.on("prompt", (e) => { + currentPromptEvent = e as PromptEvent; + this._activePromptSchema.value = currentPromptEvent.schema; + this._activePromptPlayer.value = currentPromptEvent.currentPlayer; + this._activePromptHint.value = currentPromptEvent.hintText || null; + }); + + this._context._commands.on("promptEnd", () => { + currentPromptEvent = null; + this._activePromptSchema.value = null; + this._activePromptPlayer.value = null; + this._activePromptHint.value = null; + }); + + // Initial state + this._activePromptSchema.value = null; + this._activePromptPlayer.value = null; + this._activePromptHint.value = null; + } + + tryInput(input: string): string | null { + if (this._isDisposed) { + return "GameHost is disposed"; + } + return this._context._commands._tryCommit(input); + } + + tryAnswerPrompt(def: PromptDef, ...args: TArgs) { + return this._context._commands._tryCommit({ + name: def.schema.name, + params: args, + options: {}, + flags: {}, + }); + } + + /** + * 为下一个 produceAsync 注册中断 Promise(通常用于 UI 动画)。 + * @see MutableSignal.addInterruption + */ + addInterruption(promise: Promise): void { + this._context._state.addInterruption(promise); + } + + /** + * 清除所有未完成的中断。 + * @see MutableSignal.clearInterruptions + */ + clearInterruptions(): void { + this._context._state.clearInterruptions(); + } + + start(seed?: number): Promise { + if (this._isDisposed) { + throw new Error("GameHost is disposed"); } - private _setupPromptTracking() { - let currentPromptEvent: PromptEvent | null = null; + this._context._commands._cancel(); - this._context._commands.on('prompt', (e) => { - currentPromptEvent = e as PromptEvent; - this._activePromptSchema.value = currentPromptEvent.schema; - this._activePromptPlayer.value = currentPromptEvent.currentPlayer; - this._activePromptHint.value = currentPromptEvent.hintText || null; - }); + const initialState = this._createInitialState(); + this._context._state.value = initialState as any; - this._context._commands.on('promptEnd', () => { - currentPromptEvent = null; - this._activePromptSchema.value = null; - this._activePromptPlayer.value = null; - this._activePromptHint.value = null; - }); + seed = seed || Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + this._context._rng.setSeed(seed); + const promise = this._start(this._context); - // Initial state - this._activePromptSchema.value = null; - this._activePromptPlayer.value = null; - this._activePromptHint.value = null; + this._status.value = "running"; + this._emitEvent("start"); + + return promise; + } + + dispose(): void { + if (this._isDisposed) { + return; } - tryInput(input: string): string | null { - if (this._isDisposed) { - return 'GameHost is disposed'; - } - return this._context._commands._tryCommit(input); + this._isDisposed = true; + this._context._commands._cancel(); + this._status.value = "disposed"; + + // Emit dispose event BEFORE clearing listeners + this._emitEvent("dispose"); + this._eventListeners.clear(); + } + + on(event: "start" | "dispose", listener: () => void): () => void { + if (!this._eventListeners.has(event)) { + this._eventListeners.set(event, new Set()); } - - tryAnswerPrompt(def: PromptDef, ...args: TArgs){ - return this._context._commands._tryCommit({ - name: def.schema.name, - params: args, - options: {}, - flags: {} - }); - } - - /** - * 为下一个 produceAsync 注册中断 Promise(通常用于 UI 动画)。 - * @see MutableSignal.addInterruption - */ - addInterruption(promise: Promise): void { - this._context._state.addInterruption(promise); - } - - /** - * 清除所有未完成的中断。 - * @see MutableSignal.clearInterruptions - */ - clearInterruptions(): void { - this._context._state.clearInterruptions(); - } - - start(seed?: number): Promise { - if (this._isDisposed) { - throw new Error('GameHost is disposed'); - } - - this._context._commands._cancel(); - - const initialState = this._createInitialState(); - this._context._state.value = initialState as any; - - seed = seed || Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - this._context._rng.setSeed(seed); - const promise = this._start(this._context); - - this._status.value = 'running'; - this._emitEvent('start'); - - return promise; - } - - dispose(): void { - if (this._isDisposed) { - return; - } - - this._isDisposed = true; - this._context._commands._cancel(); - this._status.value = 'disposed'; - - // Emit dispose event BEFORE clearing listeners - this._emitEvent('dispose'); - this._eventListeners.clear(); - } - - on(event: 'start' | 'dispose', listener: () => void): () => void { - if (!this._eventListeners.has(event)) { - this._eventListeners.set(event, new Set()); - } - this._eventListeners.get(event)!.add(listener); - - return () => { - this._eventListeners.get(event)?.delete(listener); - }; - } - - private _emitEvent(event: 'start' | 'dispose') { - const listeners = this._eventListeners.get(event); - if (listeners) { - for (const listener of listeners) { - listener(); - } - } + this._eventListeners.get(event)!.add(listener); + + return () => { + this._eventListeners.get(event)?.delete(listener); + }; + } + + private _emitEvent(event: "start" | "dispose") { + const listeners = this._eventListeners.get(event); + if (listeners) { + for (const listener of listeners) { + listener(); + } } + } } -export type GameModule, TResult=unknown> = { - registry?: CommandRegistry>; - createInitialState: () => TState; - start: (ctx: IGameContext) => Promise; -} +export type GameModule< + TState extends Record, + TResult = unknown, +> = { + registry?: CommandRegistry>; + createInitialState: () => TState; + start: (ctx: IGameContext) => Promise; +}; -export function createGameHost>( - gameModule: GameModule -): GameHost { - return new GameHost( - gameModule.registry || createGameCommandRegistry(), - gameModule.createInitialState, - gameModule.start - ); +export function createGameHost< + TState extends Record, + TResult = unknown, +>(gameModule: GameModule): GameHost { + return new GameHost( + gameModule.registry || createGameCommandRegistry(), + gameModule.createInitialState, + gameModule.start, + ); }