'use client'; import { useState, useEffect } from 'react'; import { getTodayISOString } from './dateUtils'; import { loadPlayerState, savePlayerState, getGenreKey } from './playerStorage'; 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 INITIAL_SCORE = 90; export function useGameState( genre: string | null = null, maxAttempts: number = 7, isSpecial: boolean = false ) { const [gameState, setGameState] = useState(null); const [statistics, setStatistics] = useState(null); // Get genre key for backend storage // For specials, genre contains the special name const genreKey = getGenreKey(isSpecial ? null : genre, isSpecial, isSpecial ? genre || undefined : undefined); 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 }); const createNewStatistics = (): Statistics => ({ solvedIn1: 0, solvedIn2: 0, solvedIn3: 0, solvedIn4: 0, solvedIn5: 0, solvedIn6: 0, solvedIn7: 0, failed: 0, }); useEffect(() => { const today = getTodayISOString(); // Always recompute genreKey to ensure it's current const currentGenreKey = getGenreKey(isSpecial ? null : genre, isSpecial, isSpecial ? genre || undefined : undefined); // Try to load from backend first const loadFromBackend = async () => { try { const backendState = await loadPlayerState(currentGenreKey); if (backendState) { const { gameState: loadedState, statistics: loadedStats } = backendState; // Check if the loaded state is for today if (loadedState.date === today) { setGameState(loadedState); setStatistics(loadedStats); return; // Successfully loaded from backend } else { // State is for a different day - create new state const newState = createNewState(today); setGameState(newState); setStatistics(loadedStats); // Keep statistics across days // Save new state to backend await savePlayerState(currentGenreKey, newState, loadedStats); return; } } else { // No backend state found - create new state // This is the normal case for first-time players or new genre const newState = createNewState(today); setGameState(newState); const newStats = createNewStatistics(); setStatistics(newStats); // Save to backend for cross-domain sync await savePlayerState(currentGenreKey, newState, newStats); return; } } catch (error) { console.error('[gameState] Failed to load from backend:', error); // On error, create new state and try to save to backend // This handles network errors gracefully const newState = createNewState(today); setGameState(newState); const newStats = createNewStatistics(); setStatistics(newStats); // Try to save to backend (may fail, but we try) try { await savePlayerState(currentGenreKey, newState, newStats); } catch (saveError) { console.error('[gameState] Failed to save new state to backend:', saveError); } } }; loadFromBackend(); }, [genre, isSpecial]); // Re-run when genre or isSpecial changes const saveState = async (newState: GameState) => { setGameState(newState); // Save to backend only if (statistics) { try { // Always use the current genreKey (recompute it in case genre/isSpecial changed) const currentGenreKey = getGenreKey(isSpecial ? null : genre, isSpecial, isSpecial ? genre || undefined : undefined); await savePlayerState(currentGenreKey, newState, statistics); } catch (error) { console.error('[gameState] Failed to save to backend:', error); // No fallback - backend is required for cross-domain sync } } }; const updateStatistics = async (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); // Save to backend only if (gameState) { try { // Always use the current genreKey (recompute it in case genre/isSpecial changed) const currentGenreKey = getGenreKey(isSpecial ? null : genre, isSpecial, isSpecial ? genre || undefined : undefined); await savePlayerState(currentGenreKey, gameState, newStats); } catch (error) { console.error('[gameState] Failed to save statistics to backend:', error); // No fallback - backend is required for cross-domain sync } } }; const addGuess = (guess: string, correct: boolean) => { if (!gameState) return; // Prevent adding guesses if already solved or failed if (gameState.isSolved || gameState.isFailed) 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 }; }