From 6931cccba27c80b5a7ee9ef04d6256cde10d4525 Mon Sep 17 00:00:00 2001 From: stevenselcuk <22283508+stevenselcuk@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:19:19 +0000 Subject: [PATCH] feat: add auto-save and auto-continue for unfinished games - Added an auto-save `useEffect` hook to serialize game state to `localStorage` under `saved_game_state` whenever relevant variables change. - Updated state hooks (`gridSize`, `lines`, `squares`, `turn`, `scores`, `lastLineId`, `toast`) to load from `localStorage` on mount. - Handled clearing the saved game from `localStorage` on game reset or game over. - Fixed a linting error involving `useState` and `getInitialGameState`. --- src/App.tsx | 69 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0224a25..69a2c29 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,9 +12,34 @@ const LEVELS = [6, 8, 12, 16, 24, 32]; const THEME_STORAGE_KEY = 'theme_selection'; const INITIAL_STARTER = getRandomStartingPlayer(); +const SAVED_GAME_KEY = 'saved_game_state'; export default function App() { - const [gridSize, setGridSize] = useState(6); + // Read saved game state once on mount + const getInitialGameState = () => { + if (typeof window !== 'undefined') { + const saved = localStorage.getItem(SAVED_GAME_KEY); + if (saved) { + try { + return JSON.parse(saved); + } catch (e) { + console.error('Failed to parse saved game state', e); + } + } + } + return null; + }; + + const [initialGameState] = useState<{ + gridSize?: number; + lines?: LinesState; + squares?: SquaresState; + turn?: Player; + scores?: { P: number; C: number }; + lastLineId?: string | null; + } | null>(getInitialGameState); + + const [gridSize, setGridSize] = useState(() => initialGameState?.gridSize ?? 6); const [maxUnlocked, setMaxUnlocked] = useState(() => { if (typeof window !== 'undefined') { const saved = localStorage.getItem('maxUnlockedSize'); @@ -38,19 +63,39 @@ export default function App() { localStorage.setItem(THEME_STORAGE_KEY, themeName); }, [themeName]); - const [lines, setLines] = useState({}); - const [squares, setSquares] = useState({}); - const [turn, setTurn] = useState(INITIAL_STARTER); - const [scores, setScores] = useState({ P: 0, C: 0 }); + const [lines, setLines] = useState(() => initialGameState?.lines ?? {}); + const [squares, setSquares] = useState(() => initialGameState?.squares ?? {}); + const [turn, setTurn] = useState(() => initialGameState?.turn ?? INITIAL_STARTER); + const [scores, setScores] = useState(() => initialGameState?.scores ?? { P: 0, C: 0 }); const [gameOver, setGameOver] = useState(false); - const [lastLineId, setLastLineId] = useState(null); - const [toast, setToast] = useState<{ message: string; isVisible: boolean }>({ - message: INITIAL_STARTER === 'P' ? 'You start!' : 'Computer starts!', - isVisible: true, + const [lastLineId, setLastLineId] = useState(() => initialGameState?.lastLineId ?? null); + const [toast, setToast] = useState<{ message: string; isVisible: boolean }>(() => { + if (initialGameState) { + return { message: 'Game loaded!', isVisible: true }; + } + return { + message: INITIAL_STARTER === 'P' ? 'You start!' : 'Computer starts!', + isVisible: true, + }; }); const [lastMoveTime, setLastMoveTime] = useState(() => Date.now()); const [isInfoOpen, setIsInfoOpen] = useState(false); + // Auto-save game state + useEffect(() => { + if (!gameOver && Object.keys(lines).length > 0) { + const stateToSave = { + gridSize, + lines, + squares, + turn, + scores, + lastLineId, + }; + localStorage.setItem(SAVED_GAME_KEY, JSON.stringify(stateToSave)); + } + }, [gridSize, lines, squares, turn, scores, gameOver, lastLineId]); + const isProcessingRef = useRef(false); const resetGame = useCallback((newSize?: number) => { @@ -67,6 +112,9 @@ export default function App() { setLastMoveTime(Date.now()); isProcessingRef.current = false; setToast({ message: starter === 'P' ? 'You start!' : 'Computer starts!', isVisible: true }); + + // Clear saved game on reset + localStorage.removeItem(SAVED_GAME_KEY); }, []); const handleLineClick = useCallback( @@ -125,6 +173,9 @@ export default function App() { const message = winner === 'P' ? 'YOU WON!' : winner === 'C' ? 'YOU LOST!' : "IT'S A DRAW!"; setToast({ message, isVisible: true }); + // Clear saved game when game ends + localStorage.removeItem(SAVED_GAME_KEY); + if (winner === 'P') { const currentIndex = LEVELS.indexOf(gridSize); if (currentIndex !== -1 && currentIndex < LEVELS.length - 1) {