refactor: simplify input handling?

This commit is contained in:
2026-04-04 00:17:52 +08:00
parent 7501b5f592
commit 3395a315a6
4 changed files with 111 additions and 91 deletions
+25 -13
View File
@@ -12,9 +12,9 @@ const gameContext = createGameContext<TicTacToeState>(registry, createInitialSta
const commandLog = signal<Array<{ input: string; result: string; timestamp: number }>>([]);
// 创建 PromptHandler 用于处理 UI 层的 prompt
let promptHandler: ReturnType<typeof createPromptHandler<TicTacToeState>> | null = null;
const promptSignal = signal<null | Awaited<ReturnType<typeof gameContext.commands.promptQueue.pop>>>(null);
// 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 }) {
@@ -60,17 +60,34 @@ function App() {
useEffect(() => {
if (phaserReady && scene) {
// 初始化 PromptHandler
promptHandler = createPromptHandler(scene, gameContext.commands, {
// 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;
});
// 监听状态变化
const dispose = gameContext.state.subscribe(() => {
setGameState({ ...gameContext.state.value });
@@ -83,26 +100,21 @@ function App() {
return () => {
dispose();
promptHandler?.destroy();
promptHandler = null;
};
}
}, [phaserReady, scene]);
const handlePromptSubmit = useCallback((input: string) => {
if (promptHandler) {
const error = promptHandler.submit(input);
if (error === null) {
logCommand(input, { success: true });
promptSignal.value = null;
} else {
logCommand(input, { success: false, error });
}
promptHandler.submit(input);
}
}, []);
const handlePromptCancel = useCallback(() => {
if (promptHandler) {
promptHandler.cancel('User cancelled');
promptSignal.value = null;
}
}, []);
+24 -23
View File
@@ -1,7 +1,6 @@
import Phaser from 'phaser';
import type { TicTacToeState, TicTacToePart, PlayerType } from '@/game/tic-tac-toe';
import { ReactiveScene, bindRegion, createInputMapper, createPromptHandler } from 'boardgame-phaser';
import type { PromptEvent } from 'boardgame-core';
import { ReactiveScene, bindRegion, createInputMapper, InputMapper } from 'boardgame-phaser';
const CELL_SIZE = 120;
const BOARD_OFFSET = { x: 100, y: 100 };
@@ -10,10 +9,10 @@ const BOARD_SIZE = 3;
export class GameScene extends ReactiveScene<TicTacToeState> {
private boardContainer!: Phaser.GameObjects.Container;
private gridGraphics!: Phaser.GameObjects.Graphics;
private inputMapper!: ReturnType<typeof createInputMapper<TicTacToeState>>;
private promptHandler!: ReturnType<typeof createPromptHandler<TicTacToeState>>;
private activePrompt: PromptEvent | null = null;
private inputMapper!: InputMapper;
private turnText!: Phaser.GameObjects.Text;
/** Receives the active prompt from the single PromptHandler in main.tsx */
promptSignal: { current: any } = { current: null };
constructor() {
super('GameScene');
@@ -83,16 +82,14 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
}
private setupInput(): void {
this.inputMapper = createInputMapper(
this,
this.commands,
{ current: null }, // 不再需要,保留以兼容接口
(cmd: string) => {
// 使用 PromptHandler.submit() 而不是直接 tryCommit
// 这样会自动处理没有活跃 prompt 时的排队逻辑
return this.promptHandler.submit(cmd);
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);
}
);
});
this.inputMapper.mapGridClick(
{ x: CELL_SIZE, y: CELL_SIZE },
@@ -107,17 +104,21 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
return `play ${currentPlayer} ${row} ${col}`;
},
);
}
this.promptHandler = createPromptHandler(this, this.commands, {
onPrompt: (prompt) => {
this.activePrompt = prompt;
},
onCancel: () => {
this.activePrompt = null;
},
});
/**
* Called by main.tsx to wire up the single PromptHandler's submit function.
*/
private _submitToPrompt: ((cmd: string) => string | null) | null = null;
this.promptHandler.start();
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 {