refactor: update GameHost to use generic state and result types

Decouple GameHost from GameState and GameResult types by introducing
generic parameters for TState and TResult. This improves type safety
and flexibility when defining GameModules.
This commit is contained in:
hyper 2026-04-23 16:03:49 +08:00
parent 5c1ef232fd
commit fb3c98b1ef
2 changed files with 19 additions and 30 deletions

View File

@ -1,29 +1,28 @@
import { ReadonlySignal, Signal } from "@preact/signals-core";
import { createPromptContext } from "@/utils/command";
import { createGameContext, IGameContext } from "./game";
import { PromptContext } from "@/utils/command/command-prompt";
import { ReadonlySignal, Signal } from "@preact/signals-core";
import { createGameContext, IGameContext } from "./game";
export type GameHostStatus = "created" | "running" | "disposed";
export class GameHost<TModule extends GameModule> {
readonly state: ReadonlySignal<GameState<TModule>>;
export class GameHost<
TState extends Record<string, unknown>,
TResult = unknown,
TModule extends GameModule<TState, TResult> = GameModule<TState, TResult>,
> {
readonly state: ReadonlySignal<TState>;
readonly status: ReadonlySignal<GameHostStatus>;
readonly prompts: PromptContext;
private _context: IGameContext<GameState<TModule>>;
private _start: (
ctx: IGameContext<GameState<TModule>>,
) => Promise<GameResult<TModule>>;
private _context: IGameContext<TState>;
private _start: (ctx: IGameContext<TState>) => Promise<TResult>;
private _status: Signal<GameHostStatus>;
private _createInitialState: () => GameState<TModule>;
private _createInitialState: () => TState;
private _eventListeners: Map<"start" | "dispose", Set<() => void>>;
private _isDisposed = false;
constructor(public readonly gameModule: TModule) {
const { createInitialState, start } = gameModule as unknown as GameModule<
GameState<TModule>,
GameResult<TModule>
>;
const { createInitialState, start } = gameModule;
this._createInitialState = createInitialState;
this._eventListeners = new Map();
@ -53,7 +52,7 @@ export class GameHost<TModule extends GameModule> {
this._context._state.clearInterruptions();
}
start(seed?: number): Promise<GameResult<TModule>> {
start(seed?: number): Promise<TResult> {
if (this._isDisposed) {
throw new Error("GameHost is disposed");
}
@ -115,15 +114,11 @@ export type GameModule<
createInitialState: () => TState;
start: (ctx: IGameContext<TState>) => Promise<TResult>;
};
export type GameState<TModule extends GameModule> = ReturnType<
TModule["createInitialState"]
>;
export type GameResult<TModule extends GameModule> = Awaited<
ReturnType<TModule["start"]>
>;
export function createGameHost<TModule extends GameModule>(
gameModule: TModule,
): GameHost<TModule> {
export function createGameHost<
TState extends Record<string, unknown> = Record<string, unknown>,
TResult = unknown,
TModule extends GameModule<TState, TResult> = GameModule<TState, TResult>,
>(gameModule: TModule): GameHost<TState, TResult, TModule> {
return new GameHost(gameModule);
}

View File

@ -7,13 +7,7 @@
export type { IGameContext } from "./core/game";
export { createPromptDef } from "./core/game";
export type {
GameHost,
GameHostStatus,
GameModule,
GameState,
GameResult,
} from "./core/game-host";
export type { GameHost, GameHostStatus, GameModule } from "./core/game-host";
export { createGameHost } from "./core/game-host";
export type { Part } from "./core/part";