import { useState, useEffect, lazy, Suspense } from "react"; import { Attacks } from "@/shared/components/game/AttacksList"; import { AttackControls } from "@/shared/components/game/AttackControls"; import { Leaderboard } from "@/shared/components/game/Leaderboard"; import { GameMenu } from "@/shared/components/game/Menu"; import { SpawnPhaseOverlay } from "@/shared/components/overlays/SpawnPhase"; import { GameCanvas } from "@/shared/components/game/Canvas"; import { useGameAPI } from "@/shared/api/GameAPIContext"; import { useAnalytics } from "@/shared/analytics"; import type { GameOutcome, LeaderboardSnapshot } from "@/shared/api/types"; import type { GameRenderer } from "@/shared/render/GameRenderer"; const GameEndOverlay = lazy(() => import("@/shared/components/overlays/GameEnd")); interface GameContainerProps { onReturnToMenu: () => void; } export function GameContainer({ onReturnToMenu }: GameContainerProps) { const [gameOutcome, setGameOutcome] = useState(null); const [spawnPhaseActive, setSpawnPhaseActive] = useState(false); const [spawnCountdown, setSpawnCountdown] = useState<{ startedAtMs: number; durationSecs: number; } | null>(null); const [initialGameState, setInitialGameState] = useState(null); const [initialLeaderboard, setInitialLeaderboard] = useState(null); const [renderer, setRenderer] = useState(null); const [highlightedNation, setHighlightedNation] = useState(null); const api = useGameAPI(); const analytics = useAnalytics(); // Check for existing game state on mount (for reload recovery) // Skip this in browser since WASM doesn't persist state useEffect(() => { if (!api || typeof api.getGameState !== "function") return; // Only check for state recovery on desktop (Tauri) // In browser, this always returns null so skip the async call if (import.meta.env.MODE === "browser") return; api.getGameState().then((state) => { // Only recover if we actually have render data (indicates a running game) if (state && state.render_init) { console.log("Recovered game state after reload:", state); setInitialGameState(state.render_init); setInitialLeaderboard(state.leaderboard_snapshot); } }); }, [api]); // Start the game on mount useEffect(() => { if (api) { api.startGame(); } // Track game started if (analytics) { analytics.track("game_started", { mode: "singleplayer", }); } }, [api, analytics]); // Subscribe to spawn phase events useEffect(() => { if (!api) return; const unsubUpdate = api.onSpawnPhaseUpdate((update) => { setSpawnPhaseActive(true); setSpawnCountdown(update.countdown); }); const unsubEnd = api.onSpawnPhaseEnded(() => { setSpawnPhaseActive(false); setSpawnCountdown(null); }); return () => { unsubUpdate(); unsubEnd(); }; }, [api]); // Subscribe to game end events useEffect(() => { if (!api) return; const unsubscribe = api.onGameEnded((outcome) => { console.log("Game outcome received:", outcome); setGameOutcome(outcome); setSpawnPhaseActive(false); // Track game ended if (analytics) { analytics.track("game_ended", { outcome: outcome.toString().toLowerCase(), }); } }); return () => unsubscribe(); }, [api, analytics]); // Track renderer initialization with GPU info useEffect(() => { if (renderer && analytics) { const rendererInfo = renderer.getRendererInfo(); analytics.track("renderer_initialized", rendererInfo); } }, [renderer, analytics]); // Sync highlighted nation with renderer useEffect(() => { if (renderer) { renderer.setHighlightedNation(highlightedNation); } }, [highlightedNation, renderer]); const handleExit = () => { if (api) { api.quitGame(); } setGameOutcome(null); onReturnToMenu(); }; return ( <> {/* Game Canvas - always rendered at root */} {/* Game UI */}
{/* Spawn Phase Overlay */} { // TODO: Implement settings }} /> {/* Game End Overlay */} {gameOutcome && ( setGameOutcome(null)} onExit={handleExit} /> )}
); }