import type { GameState } from '@ai-tycoon/shared'; import { processTick } from './tick'; export interface GameEngineCallbacks { getState: () => GameState; setState: (partial: Partial) => void; onTick?: (tickCount: number) => void; onEraChange?: (era: GameState['meta']['currentEra']) => void; } export class GameEngine { private callbacks: GameEngineCallbacks; private lastFrameTime = 0; private accumulator = 0; private animFrameId: number | null = null; private tickIntervalMs = 1000; constructor(callbacks: GameEngineCallbacks) { this.callbacks = callbacks; } start(): void { if (this.animFrameId !== null) return; this.lastFrameTime = performance.now(); this.accumulator = 0; this.loop(this.lastFrameTime); } stop(): void { if (this.animFrameId !== null) { cancelAnimationFrame(this.animFrameId); this.animFrameId = null; } } setSpeed(speed: number): void { this.tickIntervalMs = 1000 / speed; } processOfflineTicks(missedTicks: number): { revenue: number; expenses: number; ticksProcessed: number } { let totalRevenue = 0; let totalExpenses = 0; for (let i = 0; i < missedTicks; i++) { const state = this.callbacks.getState(); const result = processTick(state); this.callbacks.setState(result); totalRevenue += result.economy?.revenuePerTick ?? 0; totalExpenses += result.economy?.expensesPerTick ?? 0; } return { revenue: totalRevenue, expenses: totalExpenses, ticksProcessed: missedTicks }; } private loop = (now: number): void => { const delta = now - this.lastFrameTime; this.lastFrameTime = now; const state = this.callbacks.getState(); if (!state.meta.isPaused) { this.accumulator += delta; let ticksThisFrame = 0; const maxTicksPerFrame = 10; while (this.accumulator >= this.tickIntervalMs && ticksThisFrame < maxTicksPerFrame) { const currentState = this.callbacks.getState(); const result = processTick(currentState); this.callbacks.setState(result); this.accumulator -= this.tickIntervalMs; ticksThisFrame++; this.callbacks.onTick?.(currentState.meta.tickCount + 1); } if (this.accumulator > this.tickIntervalMs * maxTicksPerFrame) { this.accumulator = 0; } } this.animFrameId = requestAnimationFrame(this.loop); }; }