BareGit

Implement full game state persistence.

- Save and load game state (including theme) to localStorage using 'scoundrel_state_v1'.
- Handle serialization of Infinity values.
- Initialize app with saved state if available; otherwise start new game.
- Update startGame to preserve the currently selected theme on reset.
Author: MetroWind <chris.corsair@gmail.com>
Date: Sat Jan 3 15:48:37 2026 -0800
Commit: 96af15073b5de46a0978e439acbcc01fe5c6e4da

Changes

diff --git a/main.js b/main.js
index 9bf3994..ac37b34 100644
--- a/main.js
+++ b/main.js
@@ -166,8 +166,39 @@ const INITIAL_STATE = {
     history: []
 };
 
+const STORAGE_KEY = 'scoundrel_state_v1';
+
+const saveState = (state) => {
+    try {
+        // Convert Infinity to a placeholder string for JSON serialization
+        const serialized = JSON.stringify(state, (key, value) => {
+            return value === Infinity ? '___Infinity___' : value;
+        });
+        localStorage.setItem(STORAGE_KEY, serialized);
+    } catch (e) {
+        console.error("Failed to save state:", e);
+    }
+};
+
+const loadState = () => {
+    try {
+        const saved = localStorage.getItem(STORAGE_KEY);
+        if (!saved) return null;
+        return JSON.parse(saved, (key, value) => {
+            return value === '___Infinity___' ? Infinity : value;
+        });
+    } catch (e) {
+        console.error("Failed to load state:", e);
+        return null;
+    }
+};
+
 const App = () => {
     const [state, setState] = useState(() => {
+        const loaded = loadState();
+        if (loaded) return loaded;
+        
+        // Fallback: check legacy theme key or default
         const saved_theme = localStorage.getItem('scoundrel_theme');
         return {
             ...INITIAL_STATE,
@@ -175,16 +206,30 @@ const App = () => {
         };
     });
 
+    // Save state on every change
     useEffect(() => {
-        localStorage.setItem('scoundrel_theme', state.theme);
-    }, [state.theme]);
-
-    useEffect(() => { startGame(); }, []);
+        saveState(state);
+    }, [state]);
+
+    // Only start a new game on mount if we didn't load a valid active game
+    useEffect(() => { 
+        const isGameActive = state.deck.length > 0 || state.room.some(c => c !== null) || state.game_over;
+        if (!isGameActive) {
+            startGame(); 
+        }
+    }, []);
 
     const startGame = () => {
         const full_deck = shuffleDeck(createDeck());
         const room = full_deck.splice(0, 4);
-        setState({ ...INITIAL_STATE, deck: full_deck, room, msg: "A new dungeon awaits." });
+        // Preserve current theme when resetting
+        setState(prev => ({ 
+            ...INITIAL_STATE, 
+            theme: prev.theme, 
+            deck: full_deck, 
+            room, 
+            msg: "A new dungeon awaits." 
+        }));
     };
 
     const saveHistory = (currentState) => {