'use client'; import { useState, useEffect } from 'react'; import { getTodayISOString } from './dateUtils'; export interface GameState { date: string; guesses: string[]; // Array of song titles or IDs guessed isSolved: boolean; isFailed: boolean; lastPlayed: number; // Timestamp score: number; replayCount: number; skipCount: number; scoreBreakdown: Array<{ value: number; reason: string }>; yearGuessed: boolean; } export interface Statistics { solvedIn1: number; solvedIn2: number; solvedIn3: number; solvedIn4: number; solvedIn5: number; solvedIn6: number; solvedIn7: number; failed: number; } const STORAGE_KEY_PREFIX = 'hoerdle_game_state'; const STATS_KEY_PREFIX = 'hoerdle_statistics'; const INITIAL_SCORE = 90; export function useGameState(genre: string | null = null, maxAttempts: number = 7) { const [gameState, setGameState] = useState(null); const [statistics, setStatistics] = useState(null); const getStorageKey = () => genre ? `${STORAGE_KEY_PREFIX}_${genre}` : STORAGE_KEY_PREFIX; const getStatsKey = () => genre ? `${STATS_KEY_PREFIX}_${genre}` : STATS_KEY_PREFIX; const createNewState = (date: string): GameState => ({ date, guesses: [], isSolved: false, isFailed: false, lastPlayed: Date.now(), score: INITIAL_SCORE, replayCount: 0, skipCount: 0, scoreBreakdown: [{ value: INITIAL_SCORE, reason: 'Start value' }], yearGuessed: false }); useEffect(() => { // Load game state const storageKey = getStorageKey(); const stored = localStorage.getItem(storageKey); const today = getTodayISOString(); if (stored) { const parsed = JSON.parse(stored); if (parsed.date === today) { // Migration for existing states without score if (parsed.score === undefined) { parsed.score = INITIAL_SCORE; parsed.replayCount = 0; parsed.skipCount = 0; parsed.scoreBreakdown = [{ value: INITIAL_SCORE, reason: 'Start value' }]; parsed.yearGuessed = false; // Retroactively deduct points for existing guesses if possible, // but simpler to just start at 90 for active games to avoid confusion } setGameState(parsed as GameState); } else { // New day const newState = createNewState(today); setGameState(newState); localStorage.setItem(storageKey, JSON.stringify(newState)); } } else { // No state const newState = createNewState(today); setGameState(newState); localStorage.setItem(storageKey, JSON.stringify(newState)); } // Load statistics const statsKey = getStatsKey(); const storedStats = localStorage.getItem(statsKey); if (storedStats) { const parsedStats = JSON.parse(storedStats); // Migration for existing stats without solvedIn7 if (parsedStats.solvedIn7 === undefined) { parsedStats.solvedIn7 = 0; } setStatistics(parsedStats); } else { const newStats: Statistics = { solvedIn1: 0, solvedIn2: 0, solvedIn3: 0, solvedIn4: 0, solvedIn5: 0, solvedIn6: 0, solvedIn7: 0, failed: 0, }; setStatistics(newStats); localStorage.setItem(statsKey, JSON.stringify(newStats)); } }, [genre]); // Re-run when genre changes const saveState = (newState: GameState) => { setGameState(newState); localStorage.setItem(getStorageKey(), JSON.stringify(newState)); }; const updateStatistics = (attempts: number, solved: boolean) => { if (!statistics) return; const newStats = { ...statistics }; if (solved) { switch (attempts) { case 1: newStats.solvedIn1++; break; case 2: newStats.solvedIn2++; break; case 3: newStats.solvedIn3++; break; case 4: newStats.solvedIn4++; break; case 5: newStats.solvedIn5++; break; case 6: newStats.solvedIn6++; break; case 7: newStats.solvedIn7++; break; default: break; } } else { newStats.failed++; } setStatistics(newStats); localStorage.setItem(getStatsKey(), JSON.stringify(newStats)); }; const addGuess = (guess: string, correct: boolean) => { if (!gameState) return; const newGuesses = [...gameState.guesses, guess]; const isSolved = correct; const isFailed = !correct && newGuesses.length >= maxAttempts; let newScore = gameState.score; const newBreakdown = [...gameState.scoreBreakdown]; if (correct) { newScore += 20; newBreakdown.push({ value: 20, reason: 'Correct Answer' }); } else { if (guess === 'SKIPPED') { newScore -= 5; newBreakdown.push({ value: -5, reason: 'Skip' }); } else { newScore -= 3; newBreakdown.push({ value: -3, reason: 'Wrong guess' }); } } // If failed, reset score to 0 if (isFailed) { if (newScore > 0) { newBreakdown.push({ value: -newScore, reason: 'Game Over' }); newScore = 0; } } // Ensure score doesn't go below 0 newScore = Math.max(0, newScore); const newState = { ...gameState, guesses: newGuesses, isSolved, isFailed, lastPlayed: Date.now(), score: newScore, scoreBreakdown: newBreakdown, // Update skip count if skipped skipCount: guess === 'SKIPPED' ? gameState.skipCount + 1 : gameState.skipCount }; saveState(newState); // Update statistics when game ends if (isSolved || isFailed) { updateStatistics(newGuesses.length, isSolved); } }; const giveUp = () => { if (!gameState || gameState.isSolved || gameState.isFailed) return; let newScore = 0; const newBreakdown = [...gameState.scoreBreakdown]; if (gameState.score > 0) { newBreakdown.push({ value: -gameState.score, reason: 'Gave Up' }); } const newState = { ...gameState, isFailed: true, score: 0, scoreBreakdown: newBreakdown, lastPlayed: Date.now() }; saveState(newState); updateStatistics(gameState.guesses.length, false); }; const addReplay = () => { if (!gameState || gameState.isSolved || gameState.isFailed) return; let newScore = gameState.score - 1; // Ensure score doesn't go below 0 newScore = Math.max(0, newScore); const newBreakdown = [...gameState.scoreBreakdown, { value: -1, reason: 'Replay snippet' }]; const newState = { ...gameState, replayCount: gameState.replayCount + 1, score: newScore, scoreBreakdown: newBreakdown }; saveState(newState); }; const addYearBonus = (correct: boolean) => { if (!gameState) return; let newScore = gameState.score; const newBreakdown = [...gameState.scoreBreakdown]; if (correct) { newScore += 10; newBreakdown.push({ value: 10, reason: 'Bonus: Correct Year' }); } else { newBreakdown.push({ value: 0, reason: 'Bonus: Wrong Year' }); } const newState = { ...gameState, score: newScore, scoreBreakdown: newBreakdown, yearGuessed: true }; saveState(newState); }; const skipYearBonus = () => { if (!gameState) return; const newBreakdown = [...gameState.scoreBreakdown, { value: 0, reason: 'Bonus: Skipped' }]; const newState = { ...gameState, scoreBreakdown: newBreakdown, yearGuessed: true }; saveState(newState); }; return { gameState, statistics, addGuess, giveUp, addReplay, addYearBonus, skipYearBonus }; }