87 lines
3.3 KiB
TypeScript
87 lines
3.3 KiB
TypeScript
export interface GameStats {
|
|
streak: number;
|
|
lastDate: string; // ISO date YYYY-MM-DD of last solve
|
|
total: number;
|
|
bestTime: number; // seconds, 0 = never recorded
|
|
lastTime: number; // seconds for the most recent solve (0 if none)
|
|
solvedDates: string[]; // history of solved ISO dates (for heatmap)
|
|
}
|
|
|
|
const DEFAULT: GameStats = { streak: 0, lastDate: "", total: 0, bestTime: 0, lastTime: 0, solvedDates: [] };
|
|
|
|
function storageKey(game: string) { return `stats-${game}`; }
|
|
|
|
export function loadStats(game: string): GameStats {
|
|
if (typeof window === "undefined") return DEFAULT;
|
|
try {
|
|
const raw = localStorage.getItem(storageKey(game));
|
|
if (!raw) return DEFAULT;
|
|
return { ...DEFAULT, ...JSON.parse(raw) };
|
|
} catch { return DEFAULT; }
|
|
}
|
|
|
|
export function recordSolve(game: string, date: string, secs: number): GameStats {
|
|
const prev = loadStats(game);
|
|
|
|
// Compute yesterday ISO using local-date arithmetic (avoids UTC offset bug)
|
|
const [y, m, d] = date.split("-").map(Number);
|
|
const prev2 = new Date(y, m - 1, d - 1);
|
|
const yesterdayISO = `${prev2.getFullYear()}-${String(prev2.getMonth() + 1).padStart(2, "0")}-${String(prev2.getDate()).padStart(2, "0")}`;
|
|
|
|
let streak = prev.streak;
|
|
if (prev.lastDate === date) {
|
|
// Already solved today — don't double-count streak
|
|
} else if (prev.lastDate === yesterdayISO) {
|
|
streak += 1;
|
|
} else {
|
|
streak = 1;
|
|
}
|
|
|
|
// Build solved dates history (keep last 120 days, no duplicates)
|
|
const prevDates = prev.solvedDates ?? [];
|
|
const solvedDates = prevDates.includes(date) ? prevDates : [...prevDates, date].slice(-120);
|
|
|
|
const stats: GameStats = {
|
|
streak,
|
|
lastDate: date,
|
|
total: prev.lastDate === date ? prev.total : prev.total + 1,
|
|
bestTime: prev.bestTime === 0 || secs < prev.bestTime ? secs : prev.bestTime,
|
|
lastTime: secs,
|
|
solvedDates,
|
|
};
|
|
localStorage.setItem(storageKey(game), JSON.stringify(stats));
|
|
return stats;
|
|
}
|
|
|
|
// ── Ritual streak (global: all 5 games solved in a day) ───────────────────────
|
|
|
|
export interface RitualStats {
|
|
streak: number; // consecutive days with all 5 solved
|
|
lastDate: string; // last day all 5 were solved
|
|
}
|
|
|
|
const RITUAL_KEY = "stats-ritual";
|
|
|
|
export function getRitualStreak(): RitualStats {
|
|
if (typeof window === "undefined") return { streak: 0, lastDate: "" };
|
|
try {
|
|
const raw = localStorage.getItem(RITUAL_KEY);
|
|
return raw ? { ...{ streak: 0, lastDate: "" }, ...JSON.parse(raw) } : { streak: 0, lastDate: "" };
|
|
} catch { return { streak: 0, lastDate: "" }; }
|
|
}
|
|
|
|
/** Call this when all 5 games are confirmed solved today. Updates global ritual streak. */
|
|
export function updateRitualStreak(date: string): RitualStats {
|
|
if (typeof window === "undefined") return { streak: 0, lastDate: "" };
|
|
const prev = getRitualStreak();
|
|
if (prev.lastDate === date) return prev; // already counted today
|
|
|
|
const [y, m, d] = date.split("-").map(Number);
|
|
const yesterday = new Date(y, m - 1, d - 1);
|
|
const yesterdayISO = `${yesterday.getFullYear()}-${String(yesterday.getMonth() + 1).padStart(2, "0")}-${String(yesterday.getDate()).padStart(2, "0")}`;
|
|
|
|
const streak = prev.lastDate === yesterdayISO ? prev.streak + 1 : 1;
|
|
const updated: RitualStats = { streak, lastDate: date };
|
|
localStorage.setItem(RITUAL_KEY, JSON.stringify(updated));
|
|
return updated;
|
|
}
|