refactor: clean up
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
type Part,
|
||||
createRegion,
|
||||
type MutableSignal,
|
||||
type GameModule,
|
||||
} from 'boardgame-core';
|
||||
|
||||
const BOARD_SIZE = 3;
|
||||
@@ -40,6 +41,11 @@ export type TicTacToeState = ReturnType<typeof createInitialState>;
|
||||
const registration = createGameCommandRegistry<TicTacToeState>();
|
||||
export const registry = registration.registry;
|
||||
|
||||
export const gameModule: GameModule<TicTacToeState> = {
|
||||
registry,
|
||||
createInitialState,
|
||||
};
|
||||
|
||||
registration.add('setup', async function () {
|
||||
const { context } = this;
|
||||
while (true) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { h } from 'preact';
|
||||
import { GameUI } from 'boardgame-phaser';
|
||||
import * as ticTacToe from './game/tic-tac-toe';
|
||||
import { gameModule } from './game/tic-tac-toe';
|
||||
import './style.css';
|
||||
import App from "@/ui/App";
|
||||
import {GameScene} from "@/scenes/GameScene";
|
||||
|
||||
const ui = new GameUI({
|
||||
container: document.getElementById('ui-root')!,
|
||||
root: <App gameModule={ticTacToe} gameScene={GameScene}/>,
|
||||
root: <App gameModule={gameModule} gameScene={GameScene}/>,
|
||||
});
|
||||
|
||||
ui.mount();
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import {DisposableBag, IDisposable} from "@/utils/disposable";
|
||||
import type {GameHost} from "../../../../../boardgame-core/src";
|
||||
import {effect} from "@preact/signals";
|
||||
|
||||
export abstract class GameHostScene<T extends Record<string,unknown>> extends Phaser.Scene implements IDisposable{
|
||||
protected disposables = new DisposableBag();
|
||||
|
||||
protected gameHost!: GameHost<T>;
|
||||
|
||||
init(data: { gameHost: GameHost<T> }): void {
|
||||
this.gameHost = data.gameHost;
|
||||
}
|
||||
create(){
|
||||
this.events.on('shutdown', this.dispose, this);
|
||||
}
|
||||
dispose() {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
public get state(): T {
|
||||
return this.gameHost.state.value;
|
||||
}
|
||||
|
||||
protected watch(fn: () => void | (()=>void)){
|
||||
this.disposables.add(effect(fn));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import Phaser from 'phaser';
|
||||
import type {TicTacToeState, TicTacToePart} from '@/game/tic-tac-toe';
|
||||
import {ReadonlySignal} from "@preact/signals";
|
||||
import {GameHostScene} from "@/scenes/GameHostScene";
|
||||
import {spawnEffect, Spawner} from "@/utils/spawner";
|
||||
import { GameHostScene } from 'boardgame-phaser';
|
||||
import { spawnEffect, type Spawner } from 'boardgame-phaser';
|
||||
import type { ReadonlySignal } from '@preact/signals-core';
|
||||
import {commands} from "@/game/tic-tac-toe";
|
||||
|
||||
const CELL_SIZE = 120;
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import {useComputed } from '@preact/signals';
|
||||
import { createGameHost, GameModule } from "boardgame-core";
|
||||
import Phaser from "phaser";
|
||||
import {h} from "preact";
|
||||
import {PhaserGame, PhaserScene} from "@/ui/PhaserGame";
|
||||
import { useComputed } from '@preact/signals';
|
||||
import { createGameHost, type GameModule } from 'boardgame-core';
|
||||
import Phaser from 'phaser';
|
||||
import { h } from 'preact';
|
||||
import { PhaserGame, PhaserScene } from 'boardgame-phaser';
|
||||
|
||||
export default function App<TState extends Record<string, unknown>>(props: { gameModule: GameModule<TState>, gameScene: { new(): Phaser.Scene } }) {
|
||||
|
||||
export default function App<T extends Record<string, unknown>>(props: { gameModule: GameModule<T>, gameScene: {new(): Phaser.Scene} }) {
|
||||
|
||||
const gameHost = useComputed(() => {
|
||||
const gameHost = createGameHost(props.gameModule);
|
||||
gameHost.setup('setup');
|
||||
return {
|
||||
gameHost
|
||||
}
|
||||
return { gameHost };
|
||||
});
|
||||
|
||||
|
||||
const scene = useComputed(() => new props.gameScene());
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen">
|
||||
<div className="flex-1 relative">
|
||||
<PhaserGame>
|
||||
<PhaserScene sceneKey="GameScene" scene={scene.value} autoStart data={gameHost.value}/>
|
||||
<PhaserScene sceneKey="GameScene" scene={scene.value} autoStart data={gameHost.value} />
|
||||
</PhaserGame>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import Phaser from "phaser";
|
||||
import {createContext, h} from "preact";
|
||||
import {Signal, useSignalEffect, signal, useSignal} from "@preact/signals";
|
||||
import {useContext} from "preact/hooks";
|
||||
|
||||
export const phaserContext = createContext<Signal<Phaser.Game | undefined>>(signal());
|
||||
|
||||
export const defaultPhaserConfig: Phaser.Types.Core.GameConfig = {
|
||||
type: Phaser.AUTO,
|
||||
width: 560,
|
||||
height: 560,
|
||||
parent: 'phaser-container',
|
||||
backgroundColor: '#f9fafb',
|
||||
scene: [],
|
||||
};
|
||||
|
||||
export function PhaserGame(props: { config?: Partial<Phaser.Types.Core.GameConfig>, children?: any}){
|
||||
|
||||
const gameSignal = useSignal<Phaser.Game>();
|
||||
|
||||
useSignalEffect(() => {
|
||||
const phaserGame = new Phaser.Game(props.config || defaultPhaserConfig);
|
||||
gameSignal.value = phaserGame;
|
||||
|
||||
return () => {
|
||||
gameSignal.value = undefined;
|
||||
phaserGame.destroy(true);
|
||||
}
|
||||
});
|
||||
|
||||
return <div id="phaser-container" className="w-full h-full" >
|
||||
<phaserContext.Provider value={gameSignal}>
|
||||
{props.children}
|
||||
</phaserContext.Provider>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function PhaserScene(props: { sceneKey: string, scene: Phaser.Scene, autoStart: boolean, data?: object}){
|
||||
const context = useContext(phaserContext);
|
||||
useSignalEffect(() => {
|
||||
const game = context.value;
|
||||
if(!game) return;
|
||||
|
||||
game.scene.add(props.sceneKey, props.scene, props.autoStart, props.data);
|
||||
return () => {
|
||||
game.scene.remove(props.sceneKey);
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
export type DisposableItem = IDisposable | (() => void);
|
||||
|
||||
export class DisposableBag implements IDisposable {
|
||||
private _disposables = new Set<DisposableItem>();
|
||||
private _isDisposed = false;
|
||||
|
||||
/**
|
||||
* Returns true if the bag has already been disposed.
|
||||
*/
|
||||
get isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a disposable or a cleanup function to the bag.
|
||||
*/
|
||||
add(item: DisposableItem): void {
|
||||
if (this._isDisposed) {
|
||||
this._execute(item);
|
||||
return;
|
||||
}
|
||||
this._disposables.add(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes all items currently in the bag and clears the collection.
|
||||
*/
|
||||
dispose(): void {
|
||||
if (this._isDisposed) return;
|
||||
|
||||
this._isDisposed = true;
|
||||
|
||||
for (const item of this._disposables) {
|
||||
try {
|
||||
this._execute(item);
|
||||
} catch (error) {
|
||||
console.error("Error during resource disposal:", error);
|
||||
}
|
||||
}
|
||||
|
||||
this._disposables.clear();
|
||||
}
|
||||
|
||||
private _execute(item: DisposableItem): void {
|
||||
if (typeof item === 'function') {
|
||||
item();
|
||||
} else {
|
||||
item.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import Phaser from "phaser";
|
||||
import {effect} from "@preact/signals";
|
||||
|
||||
type GO = Phaser.GameObjects.GameObject;
|
||||
export interface Spawner<TData, TObject extends GO = GO> {
|
||||
getData(): Iterable<TData>;
|
||||
getKey(t: TData): string;
|
||||
onSpawn(t: TData): TObject | null;
|
||||
onDespawn(obj: TObject): void;
|
||||
onUpdate(t: TData, obj: TObject): void;
|
||||
}
|
||||
|
||||
export function spawnEffect<TData, TObject extends GO = GO>(spawner: Spawner<TData, TObject>){
|
||||
const objects = new Map<string, TObject>();
|
||||
return effect(() => {
|
||||
const current = new Set<string>();
|
||||
for (const t of spawner.getData()) {
|
||||
const key = spawner.getKey(t);
|
||||
current.add(key);
|
||||
if (!objects.has(key)) {
|
||||
const obj = spawner.onSpawn(t);
|
||||
if(obj) objects.set(key, obj);
|
||||
}else{
|
||||
spawner.onUpdate(t, objects.get(key)!);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, obj] of objects) {
|
||||
if (!current.has(key)) {
|
||||
spawner.onDespawn(obj);
|
||||
objects.delete(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user