import * as GAME from "vibegame"; import type { System, Plugin, Component, BuilderOptions } from "vibegame"; import { gameStore } from "../stores/game"; import { uiStore } from "../stores/ui"; import { consoleBuffer } from "../server/console-buffer"; import { HTMLDocumentParser, type ParsedDocument, } from "./html-document-parser"; type GameInstance = Awaited>; export class GameEngine { private static instance: GameEngine | null = null; private gameInstance: GameInstance | null = null; private constructor() {} static getInstance(): GameEngine { if (!GameEngine.instance) { GameEngine.instance = new GameEngine(); } return GameEngine.instance; } async startFromDocument(htmlContent: string): Promise { if (this.gameInstance) { consoleBuffer.onGameReloadStart(); this.stop(); await new Promise((resolve) => setTimeout(resolve, 100)); } gameStore.setStarting(true); console.info("🎮 Starting game..."); uiStore.setError(null); try { const parsed = HTMLDocumentParser.parseDocument(htmlContent); this.renderDocument(parsed); await this.initializeGame(parsed.scripts); console.info("✅ Game started!"); consoleBuffer.onGameReloadComplete(); } catch (error: unknown) { const errorMsg = error instanceof Error ? error.message : String(error); uiStore.setError(errorMsg); console.error(`❌ Error: ${errorMsg}`); gameStore.setInstance(null); this.gameInstance = null; } finally { gameStore.setStarting(false); } } async start(worldContent: string, scripts: string[] = []): Promise { const parsed: ParsedDocument = { world: worldContent, scripts, }; await this.startFromParsed(parsed); } private async startFromParsed(parsed: ParsedDocument): Promise { if (this.gameInstance) { consoleBuffer.onGameReloadStart(); this.stop(); await new Promise((resolve) => setTimeout(resolve, 100)); } gameStore.setStarting(true); console.info("🎮 Starting game..."); uiStore.setError(null); try { this.renderDocument(parsed); await this.initializeGame(parsed.scripts); console.info("✅ Game started!"); consoleBuffer.onGameReloadComplete(); } catch (error: unknown) { const errorMsg = error instanceof Error ? error.message : String(error); uiStore.setError(errorMsg); console.error(`❌ Error: ${errorMsg}`); gameStore.setInstance(null); this.gameInstance = null; } finally { gameStore.setStarting(false); } } private renderDocument(parsed: ParsedDocument): void { const container = document.getElementById("world-container"); if (!container) { throw new Error("World container not found"); } this.clearContainer(container); container.innerHTML = parsed.world; } private clearContainer(container: HTMLElement): void { while (container.firstChild) { container.removeChild(container.firstChild); } } private async initializeGame(scripts: string[]): Promise { GAME.resetBuilder(); const gameProxy = this.createGameProxy(); (window as unknown as { GAME: typeof gameProxy }).GAME = gameProxy; let scriptExecutionFailed = false; for (const script of scripts) { try { const cleanedScript = script.replace(/GAME\.run\(\)/g, ""); eval(cleanedScript); } catch (scriptError) { scriptExecutionFailed = true; const errorMsg = scriptError instanceof Error ? scriptError.message : String(scriptError); console.error("Script error:", errorMsg); } } (window as unknown as { GAME: typeof gameProxy | null }).GAME = null; if (scriptExecutionFailed) { throw new Error("Script execution failed - game not started"); } this.gameInstance = await GAME.run(); gameStore.setInstance(this.gameInstance); } private createGameProxy() { return { withSystem: (system: System) => { GAME.withSystem(system); return this.createGameProxy(); }, withPlugin: (plugin: Plugin) => { GAME.withPlugin(plugin); return this.createGameProxy(); }, withComponent: (name: string, component: Component) => { GAME.withComponent(name, component); return this.createGameProxy(); }, configure: (options: BuilderOptions) => { GAME.configure(options); return this.createGameProxy(); }, withoutDefaultPlugins: () => { GAME.withoutDefaultPlugins(); return this.createGameProxy(); }, run: () => { console.warn( "GAME.run() is not available in user scripts - the framework handles game lifecycle", ); return Promise.resolve({ stop: () => {}, destroy: () => {}, step: () => {}, getState: () => null, }); }, defineComponent: GAME.defineComponent, defineQuery: GAME.defineQuery, Types: GAME.Types, }; } stop(): void { if (this.gameInstance) { try { this.gameInstance.destroy(); console.info("Game instance destroyed"); } catch (error) { console.error("Error destroying game:", error); } this.gameInstance = null; gameStore.setInstance(null); } const container = document.getElementById("world-container"); if (container) { this.clearContainer(container); } GAME.resetBuilder(); } isRunning(): boolean { return this.gameInstance !== null; } } export const gameEngine = GameEngine.getInstance();