153 lines
3.6 KiB
TypeScript
153 lines
3.6 KiB
TypeScript
// User statistics tracking and persistence
|
|
|
|
import { join } from "@std/path";
|
|
import { ensureDir } from "@std/fs";
|
|
|
|
export interface GameStats {
|
|
totalGames: number;
|
|
totalRounds: number;
|
|
correctAnswers: number;
|
|
incorrectAnswers: number;
|
|
averageTimePerRound: number;
|
|
bestStreak: number;
|
|
modeStats: {
|
|
letters: ModeStats;
|
|
alphanumeric: ModeStats;
|
|
full: ModeStats;
|
|
words: ModeStats;
|
|
phrases: ModeStats;
|
|
};
|
|
lastPlayed?: string;
|
|
}
|
|
|
|
export interface ModeStats {
|
|
gamesPlayed: number;
|
|
correctAnswers: number;
|
|
incorrectAnswers: number;
|
|
averageAccuracy: number;
|
|
}
|
|
|
|
export interface GameResult {
|
|
mode: "letters" | "alphanumeric" | "full" | "words" | "phrases";
|
|
rounds: number;
|
|
correct: number;
|
|
incorrect: number;
|
|
averageTime: number;
|
|
streak: number;
|
|
}
|
|
|
|
const DEFAULT_STATS: GameStats = {
|
|
totalGames: 0,
|
|
totalRounds: 0,
|
|
correctAnswers: 0,
|
|
incorrectAnswers: 0,
|
|
averageTimePerRound: 0,
|
|
bestStreak: 0,
|
|
modeStats: {
|
|
letters: {
|
|
gamesPlayed: 0,
|
|
correctAnswers: 0,
|
|
incorrectAnswers: 0,
|
|
averageAccuracy: 0,
|
|
},
|
|
alphanumeric: {
|
|
gamesPlayed: 0,
|
|
correctAnswers: 0,
|
|
incorrectAnswers: 0,
|
|
averageAccuracy: 0,
|
|
},
|
|
full: {
|
|
gamesPlayed: 0,
|
|
correctAnswers: 0,
|
|
incorrectAnswers: 0,
|
|
averageAccuracy: 0,
|
|
},
|
|
words: {
|
|
gamesPlayed: 0,
|
|
correctAnswers: 0,
|
|
incorrectAnswers: 0,
|
|
averageAccuracy: 0,
|
|
},
|
|
phrases: {
|
|
gamesPlayed: 0,
|
|
correctAnswers: 0,
|
|
incorrectAnswers: 0,
|
|
averageAccuracy: 0,
|
|
},
|
|
},
|
|
};
|
|
|
|
function getStatsPath(): string {
|
|
return join(Deno.cwd(), "data", "stats.json");
|
|
}
|
|
|
|
/**
|
|
* Load stats from JSON file
|
|
*/
|
|
export async function loadStats(): Promise<GameStats> {
|
|
try {
|
|
const statsPath = getStatsPath();
|
|
const content = await Deno.readTextFile(statsPath);
|
|
return JSON.parse(content);
|
|
} catch {
|
|
return { ...DEFAULT_STATS };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save stats to JSON file
|
|
*/
|
|
export async function saveStats(stats: GameStats): Promise<void> {
|
|
const statsPath = getStatsPath();
|
|
const dataDir = join(Deno.cwd(), "data");
|
|
|
|
await ensureDir(dataDir);
|
|
await Deno.writeTextFile(statsPath, JSON.stringify(stats, null, 2));
|
|
}
|
|
|
|
/**
|
|
* Update stats with a new game result
|
|
*/
|
|
export async function updateStats(result: GameResult): Promise<GameStats> {
|
|
const stats = await loadStats();
|
|
|
|
// Update overall stats
|
|
stats.totalGames++;
|
|
stats.totalRounds += result.rounds;
|
|
stats.correctAnswers += result.correct;
|
|
stats.incorrectAnswers += result.incorrect;
|
|
stats.averageTimePerRound =
|
|
(stats.averageTimePerRound * (stats.totalRounds - result.rounds) +
|
|
result.averageTime * result.rounds) /
|
|
stats.totalRounds;
|
|
stats.bestStreak = Math.max(stats.bestStreak, result.streak);
|
|
stats.lastPlayed = new Date().toISOString();
|
|
|
|
// Update mode-specific stats
|
|
const modeStats = stats.modeStats[result.mode];
|
|
modeStats.gamesPlayed++;
|
|
modeStats.correctAnswers += result.correct;
|
|
modeStats.incorrectAnswers += result.incorrect;
|
|
const totalAnswers = modeStats.correctAnswers + modeStats.incorrectAnswers;
|
|
modeStats.averageAccuracy =
|
|
totalAnswers > 0 ? (modeStats.correctAnswers / totalAnswers) * 100 : 0;
|
|
|
|
await saveStats(stats);
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Reset all stats
|
|
*/
|
|
export async function resetStats(): Promise<void> {
|
|
await saveStats({ ...DEFAULT_STATS });
|
|
}
|
|
|
|
/**
|
|
* Get formatted accuracy percentage
|
|
*/
|
|
export function getAccuracy(stats: GameStats): number {
|
|
const total = stats.correctAnswers + stats.incorrectAnswers;
|
|
return total > 0 ? (stats.correctAnswers / total) * 100 : 0;
|
|
}
|