fix: fix entrance rig and details
This commit is contained in:
parent
01407b5ede
commit
df2b839e07
|
|
@ -7,7 +7,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="phaser-container"></div>
|
||||
<div id="ui-root"></div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
type Part,
|
||||
createRegion,
|
||||
type MutableSignal,
|
||||
isCellOccupied as isCellOccupiedUtil,
|
||||
} from 'boardgame-core';
|
||||
|
||||
const BOARD_SIZE = 3;
|
||||
|
|
@ -21,7 +22,7 @@ const WINNING_LINES: number[][][] = [
|
|||
export type PlayerType = 'X' | 'O';
|
||||
export type WinnerType = PlayerType | 'draw' | null;
|
||||
|
||||
export type TicTacToePart = Part & { player: PlayerType };
|
||||
export type TicTacToePart = Part<{ player: PlayerType }>;
|
||||
|
||||
export function createInitialState() {
|
||||
return {
|
||||
|
|
@ -29,7 +30,7 @@ export function createInitialState() {
|
|||
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
|
||||
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
|
||||
]),
|
||||
parts: {} as Record<string, TicTacToePart>,
|
||||
parts: [] as TicTacToePart[],
|
||||
currentPlayer: 'X' as PlayerType,
|
||||
winner: null as WinnerType,
|
||||
turn: 0,
|
||||
|
|
@ -93,8 +94,7 @@ registration.add('turn <player> <turn:number>', async function (cmd) {
|
|||
});
|
||||
|
||||
export function isCellOccupied(host: MutableSignal<TicTacToeState>, row: number, col: number): boolean {
|
||||
const board = host.value.board;
|
||||
return board.partMap[`${row},${col}`] !== undefined;
|
||||
return isCellOccupiedUtil(host.value.parts, 'board', [row, col]);
|
||||
}
|
||||
|
||||
export function hasWinningLine(positions: number[][]): boolean {
|
||||
|
|
@ -106,7 +106,7 @@ export function hasWinningLine(positions: number[][]): boolean {
|
|||
}
|
||||
|
||||
export function checkWinner(host: MutableSignal<TicTacToeState>): WinnerType {
|
||||
const parts = Object.values(host.value.parts);
|
||||
const parts = host.value.parts;
|
||||
|
||||
const xPositions = parts.filter((p: TicTacToePart) => p.player === 'X').map((p: TicTacToePart) => p.position);
|
||||
const oPositions = parts.filter((p: TicTacToePart) => p.player === 'O').map((p: TicTacToePart) => p.position);
|
||||
|
|
@ -119,8 +119,7 @@ export function checkWinner(host: MutableSignal<TicTacToeState>): WinnerType {
|
|||
}
|
||||
|
||||
export function placePiece(host: MutableSignal<TicTacToeState>, row: number, col: number, player: PlayerType) {
|
||||
const board = host.value.board;
|
||||
const moveNumber = Object.keys(host.value.parts).length + 1;
|
||||
const moveNumber = host.value.parts.length + 1;
|
||||
const piece: TicTacToePart = {
|
||||
id: `piece-${player}-${moveNumber}`,
|
||||
regionId: 'board',
|
||||
|
|
@ -128,8 +127,8 @@ export function placePiece(host: MutableSignal<TicTacToeState>, row: number, col
|
|||
player,
|
||||
};
|
||||
host.produce(state => {
|
||||
state.parts[piece.id] = piece;
|
||||
board.childIds.push(piece.id);
|
||||
board.partMap[`${row},${col}`] = piece.id;
|
||||
state.parts.push(piece);
|
||||
state.board.childIds.push(piece.id);
|
||||
state.board.partMap[`${row},${col}`] = piece.id;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { h, render } from 'preact';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import Phaser from 'phaser';
|
||||
import { createGameContext } from 'boardgame-core';
|
||||
import { GameUI, PromptDialog, CommandLog } from 'boardgame-phaser';
|
||||
|
|
@ -12,10 +13,12 @@ const gameContext = createGameContext<TicTacToeState>(registry, createInitialSta
|
|||
const promptSignal = signal<null | Awaited<ReturnType<typeof gameContext.commands.promptQueue.pop>>>(null);
|
||||
const commandLog = signal<Array<{ input: string; result: string; timestamp: number }>>([]);
|
||||
|
||||
// 监听 prompt 事件
|
||||
gameContext.commands.on('prompt', (event) => {
|
||||
promptSignal.value = event;
|
||||
});
|
||||
|
||||
// 包装 run 方法以记录命令日志
|
||||
const originalRun = gameContext.commands.run.bind(gameContext.commands);
|
||||
(gameContext.commands as any).run = async (input: string) => {
|
||||
const result = await originalRun(input);
|
||||
|
|
@ -35,42 +38,63 @@ const sceneData: GameSceneData = {
|
|||
commands: gameContext.commands,
|
||||
};
|
||||
|
||||
const phaserConfig: Phaser.Types.Core.GameConfig = {
|
||||
type: Phaser.AUTO,
|
||||
width: 560,
|
||||
height: 560,
|
||||
parent: 'phaser-container',
|
||||
backgroundColor: '#f9fafb',
|
||||
scene: [],
|
||||
};
|
||||
function App() {
|
||||
const [phaserReady, setPhaserReady] = useState(false);
|
||||
const [game, setGame] = useState<Phaser.Game | null>(null);
|
||||
|
||||
const game = new Phaser.Game(phaserConfig);
|
||||
useEffect(() => {
|
||||
const phaserConfig: Phaser.Types.Core.GameConfig = {
|
||||
type: Phaser.AUTO,
|
||||
width: 560,
|
||||
height: 560,
|
||||
parent: 'phaser-container',
|
||||
backgroundColor: '#f9fafb',
|
||||
scene: [],
|
||||
};
|
||||
|
||||
game.scene.add('GameScene', GameScene, true, sceneData);
|
||||
const phaserGame = new Phaser.Game(phaserConfig);
|
||||
phaserGame.scene.add('GameScene', GameScene, true, sceneData);
|
||||
|
||||
setGame(phaserGame);
|
||||
setPhaserReady(true);
|
||||
|
||||
return () => {
|
||||
phaserGame.destroy(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (phaserReady) {
|
||||
gameContext.commands.run('setup');
|
||||
}
|
||||
}, [phaserReady]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen">
|
||||
<div className="flex-1 relative">
|
||||
<div id="phaser-container" className="w-full h-full" />
|
||||
<PromptDialog
|
||||
prompt={promptSignal.value}
|
||||
onSubmit={(input: string) => {
|
||||
gameContext.commands._tryCommit(input);
|
||||
promptSignal.value = null;
|
||||
}}
|
||||
onCancel={() => {
|
||||
gameContext.commands._cancel('cancelled');
|
||||
promptSignal.value = null;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 bg-gray-100 border-t">
|
||||
<CommandLog entries={commandLog} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ui = new GameUI({
|
||||
container: document.getElementById('ui-root')!,
|
||||
root: h('div', { className: 'flex flex-col h-screen' },
|
||||
h('div', { className: 'flex-1 relative' },
|
||||
h('div', { id: 'phaser-container', className: 'w-full h-full' }),
|
||||
h(PromptDialog, {
|
||||
prompt: promptSignal.value,
|
||||
onSubmit: (input: string) => {
|
||||
gameContext.commands._tryCommit(input);
|
||||
promptSignal.value = null;
|
||||
},
|
||||
onCancel: () => {
|
||||
gameContext.commands._cancel('cancelled');
|
||||
promptSignal.value = null;
|
||||
},
|
||||
}),
|
||||
),
|
||||
h('div', { className: 'p-4 bg-gray-100 border-t' },
|
||||
h(CommandLog, { entries: commandLog }),
|
||||
),
|
||||
),
|
||||
root: <App />,
|
||||
});
|
||||
|
||||
ui.mount();
|
||||
|
||||
gameContext.commands.run('setup');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Phaser from 'phaser';
|
||||
import type { TicTacToeState, TicTacToePart } from '@/game/tic-tac-toe';
|
||||
import type { TicTacToeState, TicTacToePart, PlayerType } from '@/game/tic-tac-toe';
|
||||
import { isCellOccupied } from 'boardgame-core';
|
||||
import { ReactiveScene, bindRegion, createInputMapper, createPromptHandler } from 'boardgame-phaser';
|
||||
import type { PromptEvent, MutableSignal, IGameContext } from 'boardgame-core';
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
|
|||
}
|
||||
|
||||
create(): void {
|
||||
super.create();
|
||||
this.boardContainer = this.add.container(0, 0);
|
||||
this.gridGraphics = this.add.graphics();
|
||||
this.drawGrid();
|
||||
|
|
@ -49,18 +51,18 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
|
|||
this.updateTurnText(currentPlayer);
|
||||
});
|
||||
|
||||
this.setupBindings();
|
||||
this.setupInput();
|
||||
}
|
||||
|
||||
protected setupBindings(): void {
|
||||
bindRegion<TicTacToePart>(
|
||||
bindRegion<TicTacToeState, { player: PlayerType }>(
|
||||
this.state,
|
||||
(state) => state.parts,
|
||||
this.state.value.board,
|
||||
this.state.value.parts,
|
||||
{
|
||||
cellSize: { x: CELL_SIZE, y: CELL_SIZE },
|
||||
offset: BOARD_OFFSET,
|
||||
factory: (part: TicTacToePart, pos: Phaser.Math.Vector2) => {
|
||||
factory: (part, pos: Phaser.Math.Vector2) => {
|
||||
const text = this.add.text(pos.x + CELL_SIZE / 2, pos.y + CELL_SIZE / 2, part.player, {
|
||||
fontSize: '64px',
|
||||
fontFamily: 'Arial',
|
||||
|
|
@ -85,8 +87,7 @@ export class GameScene extends ReactiveScene<TicTacToeState> {
|
|||
if (this.state.value.winner) return null;
|
||||
|
||||
const currentPlayer = this.state.value.currentPlayer;
|
||||
const board = this.state.value.board;
|
||||
if (board.partMap[`${row},${col}`]) return null;
|
||||
if (isCellOccupied(this.state.value.parts, 'board', [row, col])) return null;
|
||||
|
||||
return `play ${currentPlayer} ${row} ${col}`;
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue