import { GAME_CONFIG, seedStr, updateResetCountdown, fetchConfig, fetchGridForSeed, fetchAndApplyActivePlayers, updateEconomyDisplay, loadEconScores, loadVictoryPoints, loadDbInfo, loadElementBonus, loadMilitaryDeductions, applyRealtimeSnapshot, applyCellUpdate, applyTeamQuotaUpdate, applyMilitaryDeductionsUpdate, refreshFromServer, refreshGridDisplay, loadPlayfieldMask, draw, } from "./game.js"; import { tryRestoreSession, showAuthOverlay, hideAuthOverlay, } from "./auth.js"; import { startRealtime, restartRealtime, } from "./realtime.js"; // ── DOM refs ────────────────────────────────────────────────────────────────── const hint = document.getElementById("hint"); const cooldownEl = document.getElementById("cooldownConfig"); const burgerBtn = document.getElementById("burgerBtn"); const closeMenuBtn = document.getElementById("closeMenuBtn"); const infoColumn = document.getElementById("infoColumn"); // ── Polling ─────────────────────────────────────────────────────────────────── let configPollTimer = 0; let scorePollTimer = 0; let resetTimer = null; let victoryPollTimer = 0; let gridPollTimer = 0; let fallbackPollingEnabled = false; let syncInFlight = false; function clearFallbackTimers() { clearTimeout(configPollTimer); clearTimeout(scorePollTimer); if (victoryPollTimer) { clearInterval(victoryPollTimer); victoryPollTimer = 0; } if (gridPollTimer) { clearInterval(gridPollTimer); gridPollTimer = 0; } } function scheduleConfigPoll() { if (!fallbackPollingEnabled) return; clearTimeout(configPollTimer); const ms = Math.max(5_000, GAME_CONFIG.configReloadIntervalSeconds * 1_000); configPollTimer = window.setTimeout(async () => { try { const changed = await fetchConfig(); if (changed) await refreshFromServer(); } catch { /* ignore */ } scheduleConfigPoll(); }, ms); } const ECON_TICK_SECONDS = 5; function scheduleScorePoll() { if (!fallbackPollingEnabled) return; clearTimeout(scorePollTimer); scorePollTimer = window.setTimeout(async () => { await fetchAndApplyActivePlayers(); await loadEconScores(); await loadElementBonus(); await loadMilitaryDeductions(); scheduleScorePoll(); }, ECON_TICK_SECONDS * 1_000); } function startFallbackPolling() { if (fallbackPollingEnabled) return; fallbackPollingEnabled = true; scheduleConfigPoll(); scheduleScorePoll(); victoryPollTimer = setInterval(loadVictoryPoints, 30_000); gridPollTimer = setInterval(refreshGridDisplay, 1_000); } function stopFallbackPolling() { fallbackPollingEnabled = false; clearFallbackTimers(); } async function syncFromServer() { if (syncInFlight) return; syncInFlight = true; try { await refreshFromServer(); await loadVictoryPoints(); } finally { syncInFlight = false; } } function handleRealtimeMessage(message) { if (!message || typeof message !== "object") return; const worldSeed = typeof message.worldSeed === "string" ? message.worldSeed : null; if (worldSeed && seedStr && worldSeed !== seedStr) { syncFromServer(); return; } switch (message.type) { case "snapshot": applyRealtimeSnapshot(message); break; case "cell-updated": if (message.cell) applyCellUpdate(message.cell); break; case "team-quota-updated": applyTeamQuotaUpdate(message.team, message.actionsRemaining); break; case "military-deductions-updated": applyMilitaryDeductionsUpdate(message.deductions); break; case "config-updated": case "seed-changed": syncFromServer(); break; default: break; } } function startRealtimeFlow() { startRealtime({ onOpen: () => { stopFallbackPolling(); }, onClose: () => { startFallbackPolling(); }, onMessage: handleRealtimeMessage, }); } // ── Burger / mobile menu ────────────────────────────────────────────────────── function openMenu() { infoColumn.classList.add("infoColumn--open"); } function closeMenu() { infoColumn.classList.remove("infoColumn--open"); } burgerBtn.addEventListener("click", openMenu); closeMenuBtn.addEventListener("click", closeMenu); // Close when clicking outside the panel (on the galaxy overlay) document.addEventListener("click", (ev) => { if ( infoColumn.classList.contains("infoColumn--open") && !infoColumn.contains(ev.target) && ev.target !== burgerBtn ) { closeMenu(); } }); // ── Boot ────────────────────────────────────────────────────────────────────── async function boot() { // Load the SVG playfield mask before any drawing or data fetch await loadPlayfieldMask(); const restored = await tryRestoreSession(); if (!restored) { showAuthOverlay(); } else { hideAuthOverlay(); } try { await fetchConfig(); try { await fetchGridForSeed(seedStr); } catch (e) { if (e?.message !== "grid_stale") throw e; await fetchConfig(); await fetchGridForSeed(seedStr); } await fetchAndApplyActivePlayers(); await loadEconScores(); await loadVictoryPoints(); await loadDbInfo(); await loadElementBonus(); await loadMilitaryDeductions(); updateEconomyDisplay(); } catch { hint.textContent = "API unavailable — start the Node server (docker-compose up --build)."; cooldownEl.textContent = "?"; } draw(); if (resetTimer) clearInterval(resetTimer); resetTimer = setInterval(updateResetCountdown, 1_000); updateResetCountdown(); startRealtimeFlow(); window.addEventListener("auth:changed", () => { restartRealtime(); }); } // ── Start ───────────────────────────────────────────────────────────────────── boot();