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:
Hördle Bot
2025-12-01 20:22:28 +01:00
parent 61846a6982
commit 27fa689b18
2 changed files with 97 additions and 47 deletions

View File

@@ -61,6 +61,17 @@ export function useGameState(
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();
@@ -86,63 +97,90 @@ export function useGameState(
await savePlayerState(genreKey, newState, loadedStats);
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) {
console.warn('[gameState] Failed to load from backend, falling back to localStorage:', error);
}
// Fallback to localStorage
const storageKey = getStorageKey();
const stored = localStorage.getItem(storageKey);
// Fallback to localStorage only on error
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;
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);
// 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);
setGameState(newState);
localStorage.setItem(storageKey, JSON.stringify(newState));
}
// 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,
};
const newStats = createNewStatistics();
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) => {
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;