fix: Prevent replaying already solved puzzles across domains
- Add checks in handleGuess, handleSkip, and handleGiveUp to prevent actions on solved/failed puzzles - Add protection in addGuess to prevent adding guesses to solved puzzles - Fix and simplify backend state loading logic - Ensure solved puzzles cannot be replayed when switching domains
This commit is contained in:
@@ -108,6 +108,10 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
|
|||||||
|
|
||||||
const handleGuess = (song: any) => {
|
const handleGuess = (song: any) => {
|
||||||
if (isProcessingGuess) return;
|
if (isProcessingGuess) return;
|
||||||
|
// Prevent guessing if already solved or failed
|
||||||
|
if (gameState?.isSolved || gameState?.isFailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsProcessingGuess(true);
|
setIsProcessingGuess(true);
|
||||||
setLastAction('GUESS');
|
setLastAction('GUESS');
|
||||||
@@ -159,6 +163,9 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSkip = () => {
|
const handleSkip = () => {
|
||||||
|
// Prevent skipping if already solved or failed
|
||||||
|
if (gameState?.isSolved || gameState?.isFailed) return;
|
||||||
|
|
||||||
// If user hasn't played audio yet on first attempt, start it instead of skipping
|
// If user hasn't played audio yet on first attempt, start it instead of skipping
|
||||||
if (gameState.guesses.length === 0 && !hasPlayedAudio) {
|
if (gameState.guesses.length === 0 && !hasPlayedAudio) {
|
||||||
handleStartAudio();
|
handleStartAudio();
|
||||||
@@ -187,6 +194,9 @@ export default function Game({ dailyPuzzle, genre = null, isSpecial = false, max
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleGiveUp = () => {
|
const handleGiveUp = () => {
|
||||||
|
// Prevent giving up if already solved or failed
|
||||||
|
if (gameState?.isSolved || gameState?.isFailed) return;
|
||||||
|
|
||||||
setLastAction('SKIP');
|
setLastAction('SKIP');
|
||||||
addGuess("SKIPPED", false);
|
addGuess("SKIPPED", false);
|
||||||
giveUp(); // Ensure game is marked as failed and score reset to 0
|
giveUp(); // Ensure game is marked as failed and score reset to 0
|
||||||
|
|||||||
134
lib/gameState.ts
134
lib/gameState.ts
@@ -61,6 +61,17 @@ export function useGameState(
|
|||||||
yearGuessed: false
|
yearGuessed: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createNewStatistics = (): Statistics => ({
|
||||||
|
solvedIn1: 0,
|
||||||
|
solvedIn2: 0,
|
||||||
|
solvedIn3: 0,
|
||||||
|
solvedIn4: 0,
|
||||||
|
solvedIn5: 0,
|
||||||
|
solvedIn6: 0,
|
||||||
|
solvedIn7: 0,
|
||||||
|
failed: 0,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const today = getTodayISOString();
|
const today = getTodayISOString();
|
||||||
|
|
||||||
@@ -86,63 +97,90 @@ export function useGameState(
|
|||||||
await savePlayerState(genreKey, newState, loadedStats);
|
await savePlayerState(genreKey, newState, loadedStats);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No backend state found - check localStorage before creating new state
|
||||||
|
const storageKey = getStorageKey();
|
||||||
|
const stored = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
setGameState(parsed as GameState);
|
||||||
|
// Also save to backend for cross-domain sync
|
||||||
|
const statsKey = getStatsKey();
|
||||||
|
const storedStats = localStorage.getItem(statsKey);
|
||||||
|
const stats = storedStats ? JSON.parse(storedStats) : createNewStatistics();
|
||||||
|
if (!statistics) {
|
||||||
|
setStatistics(stats);
|
||||||
|
}
|
||||||
|
await savePlayerState(genreKey, parsed as GameState, stats);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No state found - create new state
|
||||||
|
const newState = createNewState(today);
|
||||||
|
setGameState(newState);
|
||||||
|
localStorage.setItem(getStorageKey(), JSON.stringify(newState));
|
||||||
|
const newStats = createNewStatistics();
|
||||||
|
setStatistics(newStats);
|
||||||
|
localStorage.setItem(getStatsKey(), JSON.stringify(newStats));
|
||||||
|
await savePlayerState(genreKey, newState, newStats);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[gameState] Failed to load from backend, falling back to localStorage:', error);
|
console.warn('[gameState] Failed to load from backend, falling back to localStorage:', error);
|
||||||
}
|
|
||||||
|
// Fallback to localStorage only on error
|
||||||
// Fallback to localStorage
|
const storageKey = getStorageKey();
|
||||||
const storageKey = getStorageKey();
|
const stored = localStorage.getItem(storageKey);
|
||||||
const stored = localStorage.getItem(storageKey);
|
|
||||||
|
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const parsed = JSON.parse(stored);
|
const parsed = JSON.parse(stored);
|
||||||
if (parsed.date === today) {
|
if (parsed.date === today) {
|
||||||
// Migration for existing states without score
|
// Migration for existing states without score
|
||||||
if (parsed.score === undefined) {
|
if (parsed.score === undefined) {
|
||||||
parsed.score = INITIAL_SCORE;
|
parsed.score = INITIAL_SCORE;
|
||||||
parsed.replayCount = 0;
|
parsed.replayCount = 0;
|
||||||
parsed.skipCount = 0;
|
parsed.skipCount = 0;
|
||||||
parsed.scoreBreakdown = [{ value: INITIAL_SCORE, reason: 'Start value' }];
|
parsed.scoreBreakdown = [{ value: INITIAL_SCORE, reason: 'Start value' }];
|
||||||
parsed.yearGuessed = false;
|
parsed.yearGuessed = false;
|
||||||
|
}
|
||||||
|
setGameState(parsed as GameState);
|
||||||
|
|
||||||
|
// Load statistics from localStorage
|
||||||
|
const statsKey = getStatsKey();
|
||||||
|
const storedStats = localStorage.getItem(statsKey);
|
||||||
|
if (storedStats) {
|
||||||
|
const parsedStats = JSON.parse(storedStats);
|
||||||
|
if (parsedStats.solvedIn7 === undefined) {
|
||||||
|
parsedStats.solvedIn7 = 0;
|
||||||
|
}
|
||||||
|
setStatistics(parsedStats);
|
||||||
|
} else {
|
||||||
|
const newStats = createNewStatistics();
|
||||||
|
setStatistics(newStats);
|
||||||
|
localStorage.setItem(statsKey, JSON.stringify(newStats));
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
setGameState(parsed as GameState);
|
|
||||||
} else {
|
|
||||||
// New day
|
|
||||||
const newState = createNewState(today);
|
|
||||||
setGameState(newState);
|
|
||||||
localStorage.setItem(storageKey, JSON.stringify(newState));
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// No state
|
// No state found - create new state
|
||||||
const newState = createNewState(today);
|
const newState = createNewState(today);
|
||||||
setGameState(newState);
|
setGameState(newState);
|
||||||
localStorage.setItem(storageKey, JSON.stringify(newState));
|
localStorage.setItem(storageKey, JSON.stringify(newState));
|
||||||
}
|
const newStats = createNewStatistics();
|
||||||
|
|
||||||
// Load statistics from localStorage
|
|
||||||
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);
|
setStatistics(newStats);
|
||||||
localStorage.setItem(statsKey, JSON.stringify(newStats));
|
localStorage.setItem(getStatsKey(), JSON.stringify(newStats));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -203,6 +241,8 @@ export function useGameState(
|
|||||||
|
|
||||||
const addGuess = (guess: string, correct: boolean) => {
|
const addGuess = (guess: string, correct: boolean) => {
|
||||||
if (!gameState) return;
|
if (!gameState) return;
|
||||||
|
// Prevent adding guesses if already solved or failed
|
||||||
|
if (gameState.isSolved || gameState.isFailed) return;
|
||||||
|
|
||||||
const newGuesses = [...gameState.guesses, guess];
|
const newGuesses = [...gameState.guesses, guess];
|
||||||
const isSolved = correct;
|
const isSolved = correct;
|
||||||
|
|||||||
Reference in New Issue
Block a user