feat(framework): add generic types to GameHostScene

Update GameHostScene to support generic types for TResult and TModule,
allowing better type safety when interacting with the game host and
its modules.
This commit is contained in:
hyper 2026-04-23 16:11:15 +08:00
parent dfbdaa3499
commit f893ac9ff7
4 changed files with 47 additions and 27 deletions

View File

@ -1,17 +1,23 @@
import { ReactiveScene } from "./ReactiveScene";
import type { GameHost } from "boardgame-core";
import type { GameHost, GameModule } from "boardgame-core";
export interface GameHostSceneOptions<TState extends Record<string, unknown>> {
gameHost: GameHost<TState>;
export interface GameHostSceneOptions<
TState extends Record<string, unknown>,
TResult,
TModule extends GameModule<TState, TResult>,
> {
gameHost: GameHost<TState, TResult, TModule>;
[key: string]: unknown;
}
export abstract class GameHostScene<
TState extends Record<string, unknown>,
> extends ReactiveScene<GameHostSceneOptions<TState>> {
public get gameHost(): GameHost<TState> {
const gameHost = this.initData.gameHost as GameHost<TState>;
TResult,
TModule extends GameModule<TState, TResult>,
> extends ReactiveScene<GameHostSceneOptions<TState, TResult, TModule>> {
public get gameHost(): GameHost<TState, TResult, TModule> {
const gameHost = this.initData.gameHost;
if (!gameHost) {
throw new Error(
`GameHostScene (${this.scene.key}): gameHost 未提供。` +

View File

@ -2,6 +2,7 @@ import Phaser from "phaser";
import { GameHostScene } from "boardgame-phaser";
import { spawnEffect, type Spawner } from "boardgame-phaser";
import {
CombatResult,
type CombatState,
prompts,
} from "boardgame-core/samples/slay-the-spire-like";
@ -12,12 +13,17 @@ import {
type CombatUnitData,
} from "@/gameobjects/CombatUnitContainer";
import { CardSpawner } from "@/gameobjects/CardSpawner";
import { CombatModule } from "@/state/combatState";
const CARD_SPACING = 160;
const HAND_MARGIN = 100;
const HAND_Y = 140;
export class CombatTestScene extends GameHostScene<CombatState> {
export class CombatTestScene extends GameHostScene<
CombatState,
CombatResult | null,
CombatModule
> {
private selectedCardId: string | null = null;
private isTargeting = false;
private targetingText!: Phaser.GameObjects.Text;
@ -150,8 +156,9 @@ export class CombatTestScene extends GameHostScene<CombatState> {
}
private tryPlayCard(cardId: string, targetId?: string): void {
const error = this.gameHost.tryAnswerPrompt(
const error = this.gameHost.prompts.tryCommit(
prompts.mainAction,
"player",
cardId,
targetId,
);

View File

@ -1,8 +1,8 @@
import {
buildCombatState,
CombatState,
createRunState,
createStartWith,
createStart,
createTriggers,
data,
generateDeckFromInventory,
getAdjacentItems,
@ -10,9 +10,9 @@ import {
IRunContext,
} from "boardgame-core/samples/slay-the-spire-like";
import { createInventorySignal } from "./inventory";
import { GameModule } from "boardgame-core";
export function createCombatState() {
export type CombatModule = ReturnType<typeof createCombatModule>;
export function createCombatModule() {
const inventory = createInventorySignal(true);
const deck = generateDeckFromInventory(inventory.value);
@ -51,12 +51,13 @@ export function createCombatState() {
},
};
const start = createStartWith((triggers, ctx) => {
data.desert.addTriggers(triggers, runContext);
}, runContext);
const triggers = createTriggers(runContext);
const start = createStart(triggers, runContext);
return {
start,
createInitialState: () => combat,
} as GameModule<CombatState>;
triggers,
runContext,
};
}

View File

@ -1,19 +1,25 @@
import { createGameHost, type GameModule } from "boardgame-core";
import { GAME_CONFIG } from "@/config";
import { CombatTestScene } from "@/scenes/CombatTestScene";
import { GridViewerScene } from "@/scenes/GridViewerScene";
import { IndexScene } from "@/scenes/IndexScene";
import { InventoryTestScene } from "@/scenes/InventoryTestScene";
import { MapViewerScene } from "@/scenes/MapViewerScene";
import { ShapeViewerScene } from "@/scenes/ShapeViewerScene";
import { CombatModule, createCombatModule } from "@/state/combatState";
import { createGameHost } from "boardgame-core";
import {
CombatResult,
CombatState,
} from "boardgame-core/samples/slay-the-spire-like";
import { PhaserGame, PhaserScene } from "boardgame-phaser";
import { useMemo } from "preact/hooks";
import { IndexScene } from "@/scenes/IndexScene";
import { MapViewerScene } from "@/scenes/MapViewerScene";
import { GridViewerScene } from "@/scenes/GridViewerScene";
import { ShapeViewerScene } from "@/scenes/ShapeViewerScene";
import { GAME_CONFIG } from "@/config";
import { InventoryTestScene } from "@/scenes/InventoryTestScene";
import { CombatTestScene } from "@/scenes/CombatTestScene";
import { createCombatState } from "@/state/combatState";
import type { CombatState } from "boardgame-core/samples/slay-the-spire-like";
export default function App() {
const gameHost = useMemo(
() => createGameHost(createCombatState() as GameModule<CombatState>),
() =>
createGameHost<CombatState, CombatResult | null, CombatModule>(
createCombatModule(),
),
[],
);