/** * gameState.js — Game progress persistence * Tracks completed levels, stars earned, unlocks, and saved patches per level */ const STORAGE_KEY = 'synthquest-progress'; const PATCHES_KEY = 'synthquest-patches'; const defaultProgress = { currentWorld: 'w1', completedLevels: {}, // { levelId: { stars: 3 } } unlockedWorlds: ['w1'], totalStars: 0, }; let _progress = null; let _patches = null; // { levelId: { modules, connections } } export function loadProgress() { if (_progress) return _progress; try { const raw = localStorage.getItem(STORAGE_KEY); _progress = raw ? { ...defaultProgress, ...JSON.parse(raw) } : { ...defaultProgress }; } catch { _progress = { ...defaultProgress }; } return _progress; } export function saveProgress() { if (!_progress) return; _progress.totalStars = Object.values(_progress.completedLevels) .reduce((sum, l) => sum + (l.stars || 0), 0); try { localStorage.setItem(STORAGE_KEY, JSON.stringify(_progress)); } catch {} } export function completeLevel(levelId, stars) { const p = loadProgress(); const existing = p.completedLevels[levelId]; if (!existing || stars > existing.stars) { p.completedLevels[levelId] = { stars, completedAt: Date.now() }; } saveProgress(); } export function getLevelProgress(levelId) { const p = loadProgress(); return p.completedLevels[levelId] || null; } export function isLevelUnlocked(levelId, worldLevels) { const p = loadProgress(); const idx = worldLevels.findIndex(l => l.id === levelId); if (idx === 0) return true; const prevId = worldLevels[idx - 1]?.id; return prevId && p.completedLevels[prevId]?.stars >= 1; } export function resetProgress() { _progress = { ...defaultProgress }; _patches = {}; _hints = {}; saveProgress(); savePatches(); saveHints(); } // ==================== Level patch persistence ==================== function loadPatches() { if (_patches) return _patches; try { const raw = localStorage.getItem(PATCHES_KEY); _patches = raw ? JSON.parse(raw) : {}; } catch { _patches = {}; } return _patches; } function savePatches() { if (!_patches) return; try { localStorage.setItem(PATCHES_KEY, JSON.stringify(_patches)); } catch {} } export function saveLevelPatch(levelId, modules, connections) { const patches = loadPatches(); patches[levelId] = { modules: modules.map(m => ({ id: m.id, type: m.type, x: m.x, y: m.y, params: { ...m.params } })), connections: connections.map(c => ({ ...c })), savedAt: Date.now(), }; savePatches(); } export function getLevelPatch(levelId) { const patches = loadPatches(); return patches[levelId] || null; } export function clearLevelPatch(levelId) { const patches = loadPatches(); delete patches[levelId]; savePatches(); } // ==================== Hint tracking (persisted, no cheating!) ==================== const HINTS_KEY = 'synthquest-hints'; let _hints = null; // { levelId: true } function loadHints() { if (_hints) return _hints; try { const raw = localStorage.getItem(HINTS_KEY); _hints = raw ? JSON.parse(raw) : {}; } catch { _hints = {}; } return _hints; } function saveHints() { if (!_hints) return; try { localStorage.setItem(HINTS_KEY, JSON.stringify(_hints)); } catch {} } export function markHintUsed(levelId) { const hints = loadHints(); hints[levelId] = true; saveHints(); } export function wasHintUsed(levelId) { const hints = loadHints(); return !!hints[levelId]; } export function clearHintForLevel(levelId) { const hints = loadHints(); delete hints[levelId]; saveHints(); }