refactor: use GameHost for simplification
This commit is contained in:
parent
32509d7812
commit
4fa06098ba
|
|
@ -1,21 +1,22 @@
|
|||
import { h, render } from 'preact';
|
||||
import { signal, computed } from '@preact/signals-core';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { useEffect, useState, useCallback } from 'preact/hooks';
|
||||
import Phaser from 'phaser';
|
||||
import { createGameContext } from 'boardgame-core';
|
||||
import { GameUI, PromptDialog, CommandLog, createPromptHandler } from 'boardgame-phaser';
|
||||
import { createGameHost } from 'boardgame-core';
|
||||
import { GameUI, PromptDialog, CommandLog } from 'boardgame-phaser';
|
||||
import { createInitialState, registry, type TicTacToeState } from './game/tic-tac-toe';
|
||||
import { GameScene } from './scenes/GameScene';
|
||||
import './style.css';
|
||||
|
||||
const gameContext = createGameContext<TicTacToeState>(registry, createInitialState);
|
||||
// 创建 GameHost 实例,自动管理状态和 prompt
|
||||
const gameHost = createGameHost(
|
||||
{ registry, createInitialState },
|
||||
'setup',
|
||||
{ autoStart: false }
|
||||
);
|
||||
|
||||
const commandLog = signal<Array<{ input: string; result: string; timestamp: number }>>([]);
|
||||
|
||||
// Single PromptHandler — the only consumer of promptQueue
|
||||
let promptHandler: ReturnType<typeof createPromptHandler> | null = null;
|
||||
const promptSignal = signal<import('boardgame-core').PromptEvent | null>(null);
|
||||
|
||||
// 记录命令日志的辅助函数
|
||||
function logCommand(input: string, result: { success: boolean; result?: unknown; error?: string }) {
|
||||
commandLog.value = [
|
||||
|
|
@ -33,6 +34,7 @@ function App() {
|
|||
const [game, setGame] = useState<Phaser.Game | null>(null);
|
||||
const [scene, setScene] = useState<GameScene | null>(null);
|
||||
const [gameState, setGameState] = useState<TicTacToeState | null>(null);
|
||||
const [promptSchema, setPromptSchema] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const phaserConfig: Phaser.Types.Core.GameConfig = {
|
||||
|
|
@ -45,81 +47,70 @@ function App() {
|
|||
};
|
||||
|
||||
const phaserGame = new Phaser.Game(phaserConfig);
|
||||
// 通过 init 传递 gameContext
|
||||
// 通过 init 传递 gameHost
|
||||
const gameScene = new GameScene();
|
||||
phaserGame.scene.add('GameScene', gameScene, true, { gameContext });
|
||||
phaserGame.scene.add('GameScene', gameScene, true, { gameHost });
|
||||
|
||||
setGame(phaserGame);
|
||||
setScene(gameScene);
|
||||
setPhaserReady(true);
|
||||
|
||||
return () => {
|
||||
gameHost.dispose();
|
||||
phaserGame.destroy(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (phaserReady && scene) {
|
||||
// Initialize the single PromptHandler
|
||||
promptHandler = createPromptHandler({
|
||||
commands: gameContext.commands,
|
||||
onPrompt: (prompt) => {
|
||||
promptSignal.value = prompt;
|
||||
// Also update the scene's prompt reference
|
||||
scene.promptSignal.current = prompt;
|
||||
},
|
||||
onCancel: () => {
|
||||
promptSignal.value = null;
|
||||
scene.promptSignal.current = null;
|
||||
},
|
||||
});
|
||||
promptHandler.start();
|
||||
|
||||
// Wire the scene's submit function to this PromptHandler
|
||||
scene.setSubmitPrompt((cmd: string) => {
|
||||
const error = promptHandler!.submit(cmd);
|
||||
if (error === null) {
|
||||
logCommand(cmd, { success: true });
|
||||
promptSignal.value = null;
|
||||
scene.promptSignal.current = null;
|
||||
} else {
|
||||
logCommand(cmd, { success: false, error });
|
||||
}
|
||||
return error;
|
||||
// 监听 prompt 状态变化
|
||||
const disposePromptSchema = gameHost.activePromptSchema.subscribe((schema) => {
|
||||
setPromptSchema(schema);
|
||||
scene.promptSchema.current = schema;
|
||||
});
|
||||
|
||||
// 监听状态变化
|
||||
const dispose = gameContext.state.subscribe(() => {
|
||||
setGameState({ ...gameContext.state.value });
|
||||
const disposeState = gameHost.state.subscribe(() => {
|
||||
setGameState(gameHost.state.value as TicTacToeState);
|
||||
});
|
||||
|
||||
// 运行游戏设置
|
||||
gameContext.commands.run('setup').then(result => {
|
||||
logCommand('setup', result);
|
||||
gameHost.setup('setup').then(() => {
|
||||
logCommand('setup', { success: true });
|
||||
}).catch(err => {
|
||||
logCommand('setup', { success: false, error: err.message });
|
||||
});
|
||||
|
||||
return () => {
|
||||
dispose();
|
||||
promptHandler?.destroy();
|
||||
promptHandler = null;
|
||||
disposePromptSchema();
|
||||
disposeState();
|
||||
};
|
||||
}
|
||||
}, [phaserReady, scene]);
|
||||
|
||||
const handlePromptSubmit = useCallback((input: string) => {
|
||||
if (promptHandler) {
|
||||
promptHandler.submit(input);
|
||||
const error = gameHost.onInput(input);
|
||||
if (error === null) {
|
||||
logCommand(input, { success: true });
|
||||
setPromptSchema(null);
|
||||
if (scene) {
|
||||
scene.promptSchema.current = null;
|
||||
}
|
||||
} else {
|
||||
logCommand(input, { success: false, error });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handlePromptCancel = useCallback(() => {
|
||||
if (promptHandler) {
|
||||
promptHandler.cancel('User cancelled');
|
||||
gameHost.commands._cancel('User cancelled');
|
||||
setPromptSchema(null);
|
||||
if (scene) {
|
||||
scene.promptSchema.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
gameContext.commands.run('reset').then(result => {
|
||||
gameHost.commands.run('reset').then(result => {
|
||||
logCommand('reset', result);
|
||||
});
|
||||
}, []);
|
||||
|
|
@ -147,7 +138,7 @@ function App() {
|
|||
)}
|
||||
|
||||
<PromptDialog
|
||||
prompt={promptSignal.value}
|
||||
prompt={promptSchema ? { schema: promptSchema, tryCommit: () => null, cancel: () => {} } : null}
|
||||
onSubmit={handlePromptSubmit}
|
||||
onCancel={handlePromptCancel}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Phaser from 'phaser';
|
||||
import type { TicTacToeState, TicTacToePart, PlayerType } from '@/game/tic-tac-toe';
|
||||
import type { GameHost, CommandSchema, IGameContext, MutableSignal } from 'boardgame-core';
|
||||
import { ReactiveScene, bindRegion, createInputMapper, InputMapper } from 'boardgame-phaser';
|
||||
|
||||
const CELL_SIZE = 120;
|
||||
|
|
@ -11,13 +12,31 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
|
|||
private gridGraphics!: Phaser.GameObjects.Graphics;
|
||||
private inputMapper!: InputMapper;
|
||||
private turnText!: Phaser.GameObjects.Text;
|
||||
/** Receives the active prompt from the single PromptHandler in main.tsx */
|
||||
promptSignal: { current: any } = { current: null };
|
||||
/** Receives the active prompt schema from main.tsx */
|
||||
promptSchema: { current: CommandSchema | null } = { current: null };
|
||||
/** GameHost instance passed from main.tsx */
|
||||
private gameHost!: GameHost<TicTacToeState>;
|
||||
|
||||
constructor() {
|
||||
super('GameScene');
|
||||
}
|
||||
|
||||
init(data: { gameHost: GameHost<TicTacToeState> } | { gameContext: IGameContext<TicTacToeState> }): void {
|
||||
if ('gameHost' in data) {
|
||||
this.gameHost = data.gameHost;
|
||||
// Create a compatible gameContext from GameHost
|
||||
this.gameContext = {
|
||||
state: this.gameHost.state as MutableSignal<TicTacToeState>,
|
||||
commands: this.gameHost.commands,
|
||||
} as IGameContext<TicTacToeState>;
|
||||
this.state = this.gameContext.state;
|
||||
this.commands = this.gameContext.commands;
|
||||
} else {
|
||||
// Fallback for direct gameContext passing
|
||||
super.init(data);
|
||||
}
|
||||
}
|
||||
|
||||
protected onStateReady(_state: TicTacToeState): void {
|
||||
}
|
||||
|
||||
|
|
@ -84,10 +103,8 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
|
|||
private setupInput(): void {
|
||||
this.inputMapper = createInputMapper(this, {
|
||||
onSubmit: (cmd: string) => {
|
||||
// Delegate to the single PromptHandler via the shared commands reference.
|
||||
// The actual PromptHandler instance lives in main.tsx and is set up once.
|
||||
// We call through a callback that main.tsx provides via the scene's public interface.
|
||||
return this.submitToPrompt(cmd);
|
||||
// Directly submit to GameHost
|
||||
return this.gameHost.onInput(cmd);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -106,21 +123,6 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by main.tsx to wire up the single PromptHandler's submit function.
|
||||
*/
|
||||
private _submitToPrompt: ((cmd: string) => string | null) | null = null;
|
||||
|
||||
setSubmitPrompt(fn: (cmd: string) => string | null): void {
|
||||
this._submitToPrompt = fn;
|
||||
}
|
||||
|
||||
private submitToPrompt(cmd: string): string | null {
|
||||
return this._submitToPrompt
|
||||
? this._submitToPrompt(cmd)
|
||||
: null; // no handler wired yet, accept silently
|
||||
}
|
||||
|
||||
private drawGrid(): void {
|
||||
const g = this.gridGraphics;
|
||||
g.lineStyle(3, 0x6b7280);
|
||||
|
|
|
|||
Loading…
Reference in New Issue