mirror of
https://github.com/Xevion/smart-rgb.git
synced 2025-12-09 22:08:37 -06:00
163 lines
5.0 KiB
TypeScript
163 lines
5.0 KiB
TypeScript
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<GameOutcome | null>(null);
|
|
const [spawnPhaseActive, setSpawnPhaseActive] = useState(false);
|
|
const [spawnCountdown, setSpawnCountdown] = useState<{
|
|
startedAtMs: number;
|
|
durationSecs: number;
|
|
} | null>(null);
|
|
const [initialGameState, setInitialGameState] = useState<any | null>(null);
|
|
const [initialLeaderboard, setInitialLeaderboard] = useState<LeaderboardSnapshot | null>(null);
|
|
const [renderer, setRenderer] = useState<GameRenderer | null>(null);
|
|
const [highlightedNation, setHighlightedNation] = useState<number | null>(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 */}
|
|
<GameCanvas
|
|
className="h-screen w-screen"
|
|
initialState={initialGameState}
|
|
onRendererReady={setRenderer}
|
|
onNationHover={setHighlightedNation}
|
|
/>
|
|
|
|
{/* Game UI */}
|
|
<div className="game-ui">
|
|
{/* Spawn Phase Overlay */}
|
|
<SpawnPhaseOverlay isVisible={spawnPhaseActive} countdown={spawnCountdown} />
|
|
|
|
<Leaderboard initialSnapshot={initialLeaderboard} highlightedNation={highlightedNation} onNationHover={setHighlightedNation} />
|
|
<AttackControls>
|
|
<Attacks onNationHover={setHighlightedNation} />
|
|
</AttackControls>
|
|
<GameMenu
|
|
onExit={handleExit}
|
|
onSettings={() => {
|
|
// TODO: Implement settings
|
|
}}
|
|
/>
|
|
|
|
{/* Game End Overlay */}
|
|
{gameOutcome && (
|
|
<Suspense fallback={null}>
|
|
<GameEndOverlay outcome={gameOutcome} onSpectate={() => setGameOutcome(null)} onExit={handleExit} />
|
|
</Suspense>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|