Finalize scoring system, release year integration, and fix song deletion
This commit is contained in:
154
lib/gameState.ts
154
lib/gameState.ts
@@ -9,6 +9,11 @@ export interface GameState {
|
||||
isSolved: boolean;
|
||||
isFailed: boolean;
|
||||
lastPlayed: number; // Timestamp
|
||||
score: number;
|
||||
replayCount: number;
|
||||
skipCount: number;
|
||||
scoreBreakdown: Array<{ value: number; reason: string }>;
|
||||
yearGuessed: boolean;
|
||||
}
|
||||
|
||||
export interface Statistics {
|
||||
@@ -22,19 +27,31 @@ export interface Statistics {
|
||||
failed: number;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'hoerdle_game_state';
|
||||
const STATS_KEY = 'hoerdle_statistics';
|
||||
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<GameState | null>(null);
|
||||
const [statistics, setStatistics] = useState<Statistics | null>(null);
|
||||
|
||||
const STORAGE_KEY_PREFIX = 'hoerdle_game_state';
|
||||
const STATS_KEY_PREFIX = 'hoerdle_statistics';
|
||||
|
||||
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();
|
||||
@@ -42,30 +59,29 @@ export function useGameState(genre: string | null = null, maxAttempts: number =
|
||||
const today = getTodayISOString();
|
||||
|
||||
if (stored) {
|
||||
const parsed: GameState = JSON.parse(stored);
|
||||
const parsed = JSON.parse(stored);
|
||||
if (parsed.date === today) {
|
||||
setGameState(parsed);
|
||||
// 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: GameState = {
|
||||
date: today,
|
||||
guesses: [],
|
||||
isSolved: false,
|
||||
isFailed: false,
|
||||
lastPlayed: Date.now(),
|
||||
};
|
||||
const newState = createNewState(today);
|
||||
setGameState(newState);
|
||||
localStorage.setItem(storageKey, JSON.stringify(newState));
|
||||
}
|
||||
} else {
|
||||
// No state
|
||||
const newState: GameState = {
|
||||
date: today,
|
||||
guesses: [],
|
||||
isSolved: false,
|
||||
isFailed: false,
|
||||
lastPlayed: Date.now(),
|
||||
};
|
||||
const newState = createNewState(today);
|
||||
setGameState(newState);
|
||||
localStorage.setItem(storageKey, JSON.stringify(newState));
|
||||
}
|
||||
@@ -116,8 +132,6 @@ export function useGameState(genre: string | null = null, maxAttempts: number =
|
||||
case 6: newStats.solvedIn6++; break;
|
||||
case 7: newStats.solvedIn7++; break;
|
||||
default:
|
||||
// For custom attempts > 7, we currently don't have specific stats buckets
|
||||
// We could add a 'solvedInOther' or just ignore for now
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -135,12 +149,43 @@ export function useGameState(genre: string | null = null, maxAttempts: number =
|
||||
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);
|
||||
@@ -151,5 +196,66 @@ export function useGameState(genre: string | null = null, maxAttempts: number =
|
||||
}
|
||||
};
|
||||
|
||||
return { gameState, statistics, addGuess };
|
||||
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);
|
||||
};
|
||||
|
||||
return { gameState, statistics, addGuess, giveUp, addReplay, addYearBonus };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user