Spaces:
Running
Running
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<ReturnType<typeof GAME.run>>; | |
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<void> { | |
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<void> { | |
const parsed: ParsedDocument = { | |
world: worldContent, | |
scripts, | |
}; | |
await this.startFromParsed(parsed); | |
} | |
private async startFromParsed(parsed: ParsedDocument): Promise<void> { | |
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<void> { | |
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(); | |