brainrot-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +372 -0
- package/dist/AchievementNotification.d.ts +28 -0
- package/dist/AchievementNotification.d.ts.map +1 -0
- package/dist/AchievementNotification.js +74 -0
- package/dist/AchievementNotification.js.map +1 -0
- package/dist/GameSelector.d.ts +25 -0
- package/dist/GameSelector.d.ts.map +1 -0
- package/dist/GameSelector.js +105 -0
- package/dist/GameSelector.js.map +1 -0
- package/dist/HelpOverlay.d.ts +15 -0
- package/dist/HelpOverlay.d.ts.map +1 -0
- package/dist/HelpOverlay.js +134 -0
- package/dist/HelpOverlay.js.map +1 -0
- package/dist/Layout.d.ts +49 -0
- package/dist/Layout.d.ts.map +1 -0
- package/dist/Layout.js +83 -0
- package/dist/Layout.js.map +1 -0
- package/dist/Leaderboard.d.ts +46 -0
- package/dist/Leaderboard.d.ts.map +1 -0
- package/dist/Leaderboard.js +68 -0
- package/dist/Leaderboard.js.map +1 -0
- package/dist/LogViewer.d.ts +33 -0
- package/dist/LogViewer.d.ts.map +1 -0
- package/dist/LogViewer.js +179 -0
- package/dist/LogViewer.js.map +1 -0
- package/dist/LoopAlertOverlay.d.ts +15 -0
- package/dist/LoopAlertOverlay.d.ts.map +1 -0
- package/dist/LoopAlertOverlay.js +17 -0
- package/dist/LoopAlertOverlay.js.map +1 -0
- package/dist/LoopManagementPanel.d.ts +44 -0
- package/dist/LoopManagementPanel.d.ts.map +1 -0
- package/dist/LoopManagementPanel.js +220 -0
- package/dist/LoopManagementPanel.js.map +1 -0
- package/dist/SettingsMenu.d.ts +22 -0
- package/dist/SettingsMenu.d.ts.map +1 -0
- package/dist/SettingsMenu.js +367 -0
- package/dist/SettingsMenu.js.map +1 -0
- package/dist/SplitPane.d.ts +63 -0
- package/dist/SplitPane.d.ts.map +1 -0
- package/dist/SplitPane.js +104 -0
- package/dist/SplitPane.js.map +1 -0
- package/dist/StatsMenu.d.ts +15 -0
- package/dist/StatsMenu.d.ts.map +1 -0
- package/dist/StatsMenu.js +230 -0
- package/dist/StatsMenu.js.map +1 -0
- package/dist/StatusBar.d.ts +58 -0
- package/dist/StatusBar.d.ts.map +1 -0
- package/dist/StatusBar.js +106 -0
- package/dist/StatusBar.js.map +1 -0
- package/dist/__tests__/ralph-loop-parser.test.d.ts +2 -0
- package/dist/__tests__/ralph-loop-parser.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-loop-parser.test.js +143 -0
- package/dist/__tests__/ralph-loop-parser.test.js.map +1 -0
- package/dist/claude-code-process.d.ts +76 -0
- package/dist/claude-code-process.d.ts.map +1 -0
- package/dist/claude-code-process.js +221 -0
- package/dist/claude-code-process.js.map +1 -0
- package/dist/cli.d.ts +42 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +265 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +206 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +270 -0
- package/dist/config.js.map +1 -0
- package/dist/game-types.d.ts +177 -0
- package/dist/game-types.d.ts.map +1 -0
- package/dist/game-types.js +55 -0
- package/dist/game-types.js.map +1 -0
- package/dist/games/MinesweeperGame.d.ts +15 -0
- package/dist/games/MinesweeperGame.d.ts.map +1 -0
- package/dist/games/MinesweeperGame.js +555 -0
- package/dist/games/MinesweeperGame.js.map +1 -0
- package/dist/games/PongGame.d.ts +15 -0
- package/dist/games/PongGame.d.ts.map +1 -0
- package/dist/games/PongGame.js +379 -0
- package/dist/games/PongGame.js.map +1 -0
- package/dist/games/SnakeGame.d.ts +15 -0
- package/dist/games/SnakeGame.d.ts.map +1 -0
- package/dist/games/SnakeGame.js +333 -0
- package/dist/games/SnakeGame.js.map +1 -0
- package/dist/games/TetrisGame.d.ts +15 -0
- package/dist/games/TetrisGame.d.ts.map +1 -0
- package/dist/games/TetrisGame.js +654 -0
- package/dist/games/TetrisGame.js.map +1 -0
- package/dist/games/index.d.ts +23 -0
- package/dist/games/index.d.ts.map +1 -0
- package/dist/games/index.js +47 -0
- package/dist/games/index.js.map +1 -0
- package/dist/high-scores.d.ts +57 -0
- package/dist/high-scores.d.ts.map +1 -0
- package/dist/high-scores.js +230 -0
- package/dist/high-scores.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +264 -0
- package/dist/index.js.map +1 -0
- package/dist/ralph-loop-parser.d.ts +58 -0
- package/dist/ralph-loop-parser.d.ts.map +1 -0
- package/dist/ralph-loop-parser.js +315 -0
- package/dist/ralph-loop-parser.js.map +1 -0
- package/dist/stats.d.ts +142 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +521 -0
- package/dist/stats.js.map +1 -0
- package/dist/styled-components.d.ts +231 -0
- package/dist/styled-components.d.ts.map +1 -0
- package/dist/styled-components.js +192 -0
- package/dist/styled-components.js.map +1 -0
- package/dist/theme.d.ts +301 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +372 -0
- package/dist/theme.js.map +1 -0
- package/dist/themes.d.ts +117 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +296 -0
- package/dist/themes.js.map +1 -0
- package/dist/ui/index.d.ts +13 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +29 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/use-claude-code.d.ts +30 -0
- package/dist/use-claude-code.d.ts.map +1 -0
- package/dist/use-claude-code.js +84 -0
- package/dist/use-claude-code.js.map +1 -0
- package/dist/use-config.d.ts +58 -0
- package/dist/use-config.d.ts.map +1 -0
- package/dist/use-config.js +113 -0
- package/dist/use-config.js.map +1 -0
- package/dist/use-game-loop.d.ts +47 -0
- package/dist/use-game-loop.d.ts.map +1 -0
- package/dist/use-game-loop.js +136 -0
- package/dist/use-game-loop.js.map +1 -0
- package/dist/use-high-scores.d.ts +41 -0
- package/dist/use-high-scores.d.ts.map +1 -0
- package/dist/use-high-scores.js +94 -0
- package/dist/use-high-scores.js.map +1 -0
- package/dist/use-layout-state.d.ts +77 -0
- package/dist/use-layout-state.d.ts.map +1 -0
- package/dist/use-layout-state.js +160 -0
- package/dist/use-layout-state.js.map +1 -0
- package/dist/use-ralph-loop.d.ts +41 -0
- package/dist/use-ralph-loop.d.ts.map +1 -0
- package/dist/use-ralph-loop.js +106 -0
- package/dist/use-ralph-loop.js.map +1 -0
- package/dist/use-spinner.d.ts +46 -0
- package/dist/use-spinner.d.ts.map +1 -0
- package/dist/use-spinner.js +71 -0
- package/dist/use-spinner.js.map +1 -0
- package/dist/use-stats.d.ts +59 -0
- package/dist/use-stats.d.ts.map +1 -0
- package/dist/use-stats.js +150 -0
- package/dist/use-stats.js.map +1 -0
- package/dist/use-terminal-size.d.ts +29 -0
- package/dist/use-terminal-size.d.ts.map +1 -0
- package/dist/use-terminal-size.js +48 -0
- package/dist/use-terminal-size.js.map +1 -0
- package/dist/useTheme.d.ts +76 -0
- package/dist/useTheme.d.ts.map +1 -0
- package/dist/useTheme.js +136 -0
- package/dist/useTheme.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ralph-loop-parser.js","sourceRoot":"","sources":["../src/ralph-loop-parser.ts"],"names":[],"mappings":"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"}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats and Achievement System
|
|
3
|
+
*
|
|
4
|
+
* Tracks gameplay statistics and provides an achievement system with
|
|
5
|
+
* unlockable milestones. Data persists using XDG Base Directory conventions.
|
|
6
|
+
*/
|
|
7
|
+
/** Stats for a single game */
|
|
8
|
+
export interface GameStats {
|
|
9
|
+
/** Total games played */
|
|
10
|
+
gamesPlayed: number;
|
|
11
|
+
/** Total time played in seconds */
|
|
12
|
+
timePlayed: number;
|
|
13
|
+
/** Highest score achieved */
|
|
14
|
+
highestScore: number;
|
|
15
|
+
/** Total score accumulated across all games */
|
|
16
|
+
totalScore: number;
|
|
17
|
+
/** Number of wins (for games with win conditions) */
|
|
18
|
+
wins: number;
|
|
19
|
+
/** Number of losses */
|
|
20
|
+
losses: number;
|
|
21
|
+
/** Last played timestamp */
|
|
22
|
+
lastPlayed: string | null;
|
|
23
|
+
/** Game-specific stats */
|
|
24
|
+
custom?: Record<string, number>;
|
|
25
|
+
}
|
|
26
|
+
/** Global stats across all games */
|
|
27
|
+
export interface GlobalStats {
|
|
28
|
+
/** Total games played across all games */
|
|
29
|
+
totalGamesPlayed: number;
|
|
30
|
+
/** Total time played in seconds */
|
|
31
|
+
totalTimePlayed: number;
|
|
32
|
+
/** Total achievements unlocked */
|
|
33
|
+
achievementsUnlocked: number;
|
|
34
|
+
/** First played timestamp */
|
|
35
|
+
firstPlayed: string | null;
|
|
36
|
+
/** Last played timestamp */
|
|
37
|
+
lastPlayed: string | null;
|
|
38
|
+
/** Session count (app opens) */
|
|
39
|
+
sessionCount: number;
|
|
40
|
+
/** Current streak (consecutive days played) */
|
|
41
|
+
currentStreak: number;
|
|
42
|
+
/** Best streak */
|
|
43
|
+
bestStreak: number;
|
|
44
|
+
/** Last streak date */
|
|
45
|
+
lastStreakDate: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** Achievement definition */
|
|
48
|
+
export interface Achievement {
|
|
49
|
+
/** Unique identifier */
|
|
50
|
+
id: string;
|
|
51
|
+
/** Display name */
|
|
52
|
+
name: string;
|
|
53
|
+
/** Description of how to unlock */
|
|
54
|
+
description: string;
|
|
55
|
+
/** Category of achievement */
|
|
56
|
+
category: "general" | "snake" | "pong" | "tetris" | "minesweeper";
|
|
57
|
+
/** Icon to display */
|
|
58
|
+
icon: string;
|
|
59
|
+
/** Whether this achievement is hidden until unlocked */
|
|
60
|
+
hidden?: boolean;
|
|
61
|
+
/** Condition check function - receives stats and returns true if unlocked */
|
|
62
|
+
condition: (stats: StatsData) => boolean;
|
|
63
|
+
}
|
|
64
|
+
/** Unlocked achievement record */
|
|
65
|
+
export interface UnlockedAchievement {
|
|
66
|
+
/** Achievement ID */
|
|
67
|
+
id: string;
|
|
68
|
+
/** Timestamp when unlocked */
|
|
69
|
+
unlockedAt: string;
|
|
70
|
+
}
|
|
71
|
+
/** Complete stats data structure */
|
|
72
|
+
export interface StatsData {
|
|
73
|
+
/** Data format version */
|
|
74
|
+
version: number;
|
|
75
|
+
/** Global stats */
|
|
76
|
+
global: GlobalStats;
|
|
77
|
+
/** Per-game stats */
|
|
78
|
+
games: Record<string, GameStats>;
|
|
79
|
+
/** Unlocked achievements */
|
|
80
|
+
achievements: UnlockedAchievement[];
|
|
81
|
+
}
|
|
82
|
+
export declare const ACHIEVEMENTS: Achievement[];
|
|
83
|
+
/**
|
|
84
|
+
* Load stats from disk
|
|
85
|
+
*/
|
|
86
|
+
export declare function loadStats(): Promise<StatsData>;
|
|
87
|
+
/**
|
|
88
|
+
* Save stats to disk
|
|
89
|
+
*/
|
|
90
|
+
export declare function saveStats(data: StatsData): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Get stats for a specific game
|
|
93
|
+
*/
|
|
94
|
+
export declare function getGameStats(gameId: string): Promise<GameStats>;
|
|
95
|
+
/**
|
|
96
|
+
* Get global stats
|
|
97
|
+
*/
|
|
98
|
+
export declare function getGlobalStats(): Promise<GlobalStats>;
|
|
99
|
+
/**
|
|
100
|
+
* Record a game session
|
|
101
|
+
*/
|
|
102
|
+
export declare function recordGameSession(gameId: string, score: number, duration: number, won?: boolean, customStats?: Record<string, number>): Promise<string[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Record a session start (increment session count)
|
|
105
|
+
*/
|
|
106
|
+
export declare function recordSessionStart(): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Check for newly unlocked achievements
|
|
109
|
+
* Returns array of newly unlocked achievement IDs
|
|
110
|
+
*/
|
|
111
|
+
export declare function checkAchievements(data: StatsData): string[];
|
|
112
|
+
/**
|
|
113
|
+
* Get all achievements with unlock status
|
|
114
|
+
*/
|
|
115
|
+
export declare function getAchievements(): Promise<Array<Achievement & {
|
|
116
|
+
unlocked: boolean;
|
|
117
|
+
unlockedAt: string | null;
|
|
118
|
+
}>>;
|
|
119
|
+
/**
|
|
120
|
+
* Get achievement by ID
|
|
121
|
+
*/
|
|
122
|
+
export declare function getAchievementById(id: string): Achievement | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Get achievement count
|
|
125
|
+
*/
|
|
126
|
+
export declare function getAchievementCount(): Promise<{
|
|
127
|
+
unlocked: number;
|
|
128
|
+
total: number;
|
|
129
|
+
}>;
|
|
130
|
+
/**
|
|
131
|
+
* Get all stats data
|
|
132
|
+
*/
|
|
133
|
+
export declare function getAllStats(): Promise<StatsData>;
|
|
134
|
+
/**
|
|
135
|
+
* Format duration in seconds to human-readable string
|
|
136
|
+
*/
|
|
137
|
+
export declare function formatDuration(seconds: number): string;
|
|
138
|
+
/**
|
|
139
|
+
* Format a date string for display
|
|
140
|
+
*/
|
|
141
|
+
export declare function formatStatsDate(timestamp: string | null): string;
|
|
142
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"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"}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats and Achievement System
|
|
3
|
+
*
|
|
4
|
+
* Tracks gameplay statistics and provides an achievement system with
|
|
5
|
+
* unlockable milestones. Data persists using XDG Base Directory conventions.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { getDataFilePath, ensureDataDir } from "./config.js";
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// CONSTANTS
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const DATA_VERSION = 1;
|
|
14
|
+
const STATS_FILE = "stats.json";
|
|
15
|
+
/** Default empty game stats */
|
|
16
|
+
const DEFAULT_GAME_STATS = {
|
|
17
|
+
gamesPlayed: 0,
|
|
18
|
+
timePlayed: 0,
|
|
19
|
+
highestScore: 0,
|
|
20
|
+
totalScore: 0,
|
|
21
|
+
wins: 0,
|
|
22
|
+
losses: 0,
|
|
23
|
+
lastPlayed: null,
|
|
24
|
+
};
|
|
25
|
+
/** Default global stats */
|
|
26
|
+
const DEFAULT_GLOBAL_STATS = {
|
|
27
|
+
totalGamesPlayed: 0,
|
|
28
|
+
totalTimePlayed: 0,
|
|
29
|
+
achievementsUnlocked: 0,
|
|
30
|
+
firstPlayed: null,
|
|
31
|
+
lastPlayed: null,
|
|
32
|
+
sessionCount: 0,
|
|
33
|
+
currentStreak: 0,
|
|
34
|
+
bestStreak: 0,
|
|
35
|
+
lastStreakDate: null,
|
|
36
|
+
};
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// ACHIEVEMENT DEFINITIONS
|
|
39
|
+
// ============================================================================
|
|
40
|
+
export const ACHIEVEMENTS = [
|
|
41
|
+
// General achievements
|
|
42
|
+
{
|
|
43
|
+
id: "first_game",
|
|
44
|
+
name: "First Steps",
|
|
45
|
+
description: "Play your first game",
|
|
46
|
+
category: "general",
|
|
47
|
+
icon: "🎮",
|
|
48
|
+
condition: (stats) => stats.global.totalGamesPlayed >= 1,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "ten_games",
|
|
52
|
+
name: "Getting Started",
|
|
53
|
+
description: "Play 10 games",
|
|
54
|
+
category: "general",
|
|
55
|
+
icon: "🎯",
|
|
56
|
+
condition: (stats) => stats.global.totalGamesPlayed >= 10,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "fifty_games",
|
|
60
|
+
name: "Dedicated Player",
|
|
61
|
+
description: "Play 50 games",
|
|
62
|
+
category: "general",
|
|
63
|
+
icon: "⭐",
|
|
64
|
+
condition: (stats) => stats.global.totalGamesPlayed >= 50,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "hundred_games",
|
|
68
|
+
name: "Game Master",
|
|
69
|
+
description: "Play 100 games",
|
|
70
|
+
category: "general",
|
|
71
|
+
icon: "🏆",
|
|
72
|
+
condition: (stats) => stats.global.totalGamesPlayed >= 100,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "hour_played",
|
|
76
|
+
name: "Time Flies",
|
|
77
|
+
description: "Play for a total of 1 hour",
|
|
78
|
+
category: "general",
|
|
79
|
+
icon: "⏰",
|
|
80
|
+
condition: (stats) => stats.global.totalTimePlayed >= 3600,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "five_hours",
|
|
84
|
+
name: "Marathon Runner",
|
|
85
|
+
description: "Play for a total of 5 hours",
|
|
86
|
+
category: "general",
|
|
87
|
+
icon: "🏃",
|
|
88
|
+
condition: (stats) => stats.global.totalTimePlayed >= 18000,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "three_day_streak",
|
|
92
|
+
name: "Consistent",
|
|
93
|
+
description: "Play 3 days in a row",
|
|
94
|
+
category: "general",
|
|
95
|
+
icon: "🔥",
|
|
96
|
+
condition: (stats) => stats.global.bestStreak >= 3,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "week_streak",
|
|
100
|
+
name: "Dedicated",
|
|
101
|
+
description: "Play 7 days in a row",
|
|
102
|
+
category: "general",
|
|
103
|
+
icon: "📅",
|
|
104
|
+
condition: (stats) => stats.global.bestStreak >= 7,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "all_games",
|
|
108
|
+
name: "Variety Pack",
|
|
109
|
+
description: "Play all 4 games at least once",
|
|
110
|
+
category: "general",
|
|
111
|
+
icon: "🎲",
|
|
112
|
+
condition: (stats) => {
|
|
113
|
+
const games = ["snake", "pong", "tetris", "minesweeper"];
|
|
114
|
+
return games.every((g) => (stats.games[g]?.gamesPlayed ?? 0) >= 1);
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
// Snake achievements
|
|
118
|
+
{
|
|
119
|
+
id: "snake_first",
|
|
120
|
+
name: "Slithering Start",
|
|
121
|
+
description: "Play your first Snake game",
|
|
122
|
+
category: "snake",
|
|
123
|
+
icon: "🐍",
|
|
124
|
+
condition: (stats) => (stats.games.snake?.gamesPlayed ?? 0) >= 1,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "snake_50_score",
|
|
128
|
+
name: "Growing Snake",
|
|
129
|
+
description: "Score 50 points in Snake",
|
|
130
|
+
category: "snake",
|
|
131
|
+
icon: "📈",
|
|
132
|
+
condition: (stats) => (stats.games.snake?.highestScore ?? 0) >= 50,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "snake_100_score",
|
|
136
|
+
name: "Long Snake",
|
|
137
|
+
description: "Score 100 points in Snake",
|
|
138
|
+
category: "snake",
|
|
139
|
+
icon: "🏅",
|
|
140
|
+
condition: (stats) => (stats.games.snake?.highestScore ?? 0) >= 100,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "snake_200_score",
|
|
144
|
+
name: "Snake Charmer",
|
|
145
|
+
description: "Score 200 points in Snake",
|
|
146
|
+
category: "snake",
|
|
147
|
+
icon: "👑",
|
|
148
|
+
condition: (stats) => (stats.games.snake?.highestScore ?? 0) >= 200,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "snake_10_games",
|
|
152
|
+
name: "Snake Enthusiast",
|
|
153
|
+
description: "Play Snake 10 times",
|
|
154
|
+
category: "snake",
|
|
155
|
+
icon: "🎮",
|
|
156
|
+
condition: (stats) => (stats.games.snake?.gamesPlayed ?? 0) >= 10,
|
|
157
|
+
},
|
|
158
|
+
// Pong achievements
|
|
159
|
+
{
|
|
160
|
+
id: "pong_first",
|
|
161
|
+
name: "Paddle Ready",
|
|
162
|
+
description: "Play your first Pong game",
|
|
163
|
+
category: "pong",
|
|
164
|
+
icon: "🏓",
|
|
165
|
+
condition: (stats) => (stats.games.pong?.gamesPlayed ?? 0) >= 1,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "pong_first_win",
|
|
169
|
+
name: "First Victory",
|
|
170
|
+
description: "Win your first Pong game",
|
|
171
|
+
category: "pong",
|
|
172
|
+
icon: "✌️",
|
|
173
|
+
condition: (stats) => (stats.games.pong?.wins ?? 0) >= 1,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "pong_10_wins",
|
|
177
|
+
name: "Pong Champion",
|
|
178
|
+
description: "Win 10 Pong games",
|
|
179
|
+
category: "pong",
|
|
180
|
+
icon: "🥇",
|
|
181
|
+
condition: (stats) => (stats.games.pong?.wins ?? 0) >= 10,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "pong_50_wins",
|
|
185
|
+
name: "Pong Master",
|
|
186
|
+
description: "Win 50 Pong games",
|
|
187
|
+
category: "pong",
|
|
188
|
+
icon: "🏆",
|
|
189
|
+
condition: (stats) => (stats.games.pong?.wins ?? 0) >= 50,
|
|
190
|
+
},
|
|
191
|
+
// Tetris achievements
|
|
192
|
+
{
|
|
193
|
+
id: "tetris_first",
|
|
194
|
+
name: "Block Party",
|
|
195
|
+
description: "Play your first Tetris game",
|
|
196
|
+
category: "tetris",
|
|
197
|
+
icon: "🧱",
|
|
198
|
+
condition: (stats) => (stats.games.tetris?.gamesPlayed ?? 0) >= 1,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: "tetris_1000_score",
|
|
202
|
+
name: "Stacking Up",
|
|
203
|
+
description: "Score 1000 points in Tetris",
|
|
204
|
+
category: "tetris",
|
|
205
|
+
icon: "📊",
|
|
206
|
+
condition: (stats) => (stats.games.tetris?.highestScore ?? 0) >= 1000,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "tetris_5000_score",
|
|
210
|
+
name: "Tetris Pro",
|
|
211
|
+
description: "Score 5000 points in Tetris",
|
|
212
|
+
category: "tetris",
|
|
213
|
+
icon: "🌟",
|
|
214
|
+
condition: (stats) => (stats.games.tetris?.highestScore ?? 0) >= 5000,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "tetris_10000_score",
|
|
218
|
+
name: "Tetris Legend",
|
|
219
|
+
description: "Score 10000 points in Tetris",
|
|
220
|
+
category: "tetris",
|
|
221
|
+
icon: "💎",
|
|
222
|
+
condition: (stats) => (stats.games.tetris?.highestScore ?? 0) >= 10000,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: "tetris_lines_cleared",
|
|
226
|
+
name: "Line Clearer",
|
|
227
|
+
description: "Clear 100 lines in Tetris",
|
|
228
|
+
category: "tetris",
|
|
229
|
+
icon: "📏",
|
|
230
|
+
condition: (stats) => (stats.games.tetris?.custom?.linesCleared ?? 0) >= 100,
|
|
231
|
+
},
|
|
232
|
+
// Minesweeper achievements
|
|
233
|
+
{
|
|
234
|
+
id: "minesweeper_first",
|
|
235
|
+
name: "Mine Explorer",
|
|
236
|
+
description: "Play your first Minesweeper game",
|
|
237
|
+
category: "minesweeper",
|
|
238
|
+
icon: "💣",
|
|
239
|
+
condition: (stats) => (stats.games.minesweeper?.gamesPlayed ?? 0) >= 1,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "minesweeper_first_win",
|
|
243
|
+
name: "Bomb Defuser",
|
|
244
|
+
description: "Win your first Minesweeper game",
|
|
245
|
+
category: "minesweeper",
|
|
246
|
+
icon: "🛡️",
|
|
247
|
+
condition: (stats) => (stats.games.minesweeper?.wins ?? 0) >= 1,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "minesweeper_10_wins",
|
|
251
|
+
name: "Mine Sweeper",
|
|
252
|
+
description: "Win 10 Minesweeper games",
|
|
253
|
+
category: "minesweeper",
|
|
254
|
+
icon: "🎖️",
|
|
255
|
+
condition: (stats) => (stats.games.minesweeper?.wins ?? 0) >= 10,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "minesweeper_fast",
|
|
259
|
+
name: "Speed Demon",
|
|
260
|
+
description: "Complete Minesweeper in under 60 seconds",
|
|
261
|
+
category: "minesweeper",
|
|
262
|
+
icon: "⚡",
|
|
263
|
+
condition: (stats) => (stats.games.minesweeper?.custom?.fastestWin ?? 999) < 60,
|
|
264
|
+
},
|
|
265
|
+
];
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// FILE OPERATIONS
|
|
268
|
+
// ============================================================================
|
|
269
|
+
function getStatsFile() {
|
|
270
|
+
return getDataFilePath(STATS_FILE);
|
|
271
|
+
}
|
|
272
|
+
function createEmptyStats() {
|
|
273
|
+
return {
|
|
274
|
+
version: DATA_VERSION,
|
|
275
|
+
global: { ...DEFAULT_GLOBAL_STATS },
|
|
276
|
+
games: {},
|
|
277
|
+
achievements: [],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Load stats from disk
|
|
282
|
+
*/
|
|
283
|
+
export async function loadStats() {
|
|
284
|
+
try {
|
|
285
|
+
const statsFile = getStatsFile();
|
|
286
|
+
if (!existsSync(statsFile)) {
|
|
287
|
+
return createEmptyStats();
|
|
288
|
+
}
|
|
289
|
+
const content = await readFile(statsFile, "utf-8");
|
|
290
|
+
const data = JSON.parse(content);
|
|
291
|
+
if (data.version !== DATA_VERSION) {
|
|
292
|
+
return createEmptyStats();
|
|
293
|
+
}
|
|
294
|
+
return data;
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return createEmptyStats();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Save stats to disk
|
|
302
|
+
*/
|
|
303
|
+
export async function saveStats(data) {
|
|
304
|
+
try {
|
|
305
|
+
await ensureDataDir();
|
|
306
|
+
const statsFile = getStatsFile();
|
|
307
|
+
await writeFile(statsFile, JSON.stringify(data, null, 2), "utf-8");
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// Silently fail - stats are nice to have but not critical
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// STATS OPERATIONS
|
|
315
|
+
// ============================================================================
|
|
316
|
+
/**
|
|
317
|
+
* Get stats for a specific game
|
|
318
|
+
*/
|
|
319
|
+
export async function getGameStats(gameId) {
|
|
320
|
+
const data = await loadStats();
|
|
321
|
+
return data.games[gameId] ?? { ...DEFAULT_GAME_STATS };
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get global stats
|
|
325
|
+
*/
|
|
326
|
+
export async function getGlobalStats() {
|
|
327
|
+
const data = await loadStats();
|
|
328
|
+
return data.global;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Update streak based on today's date
|
|
332
|
+
*/
|
|
333
|
+
function updateStreak(stats) {
|
|
334
|
+
const today = new Date().toISOString().split("T")[0];
|
|
335
|
+
const lastDate = stats.global.lastStreakDate;
|
|
336
|
+
if (!lastDate) {
|
|
337
|
+
// First time playing
|
|
338
|
+
stats.global.currentStreak = 1;
|
|
339
|
+
stats.global.bestStreak = 1;
|
|
340
|
+
stats.global.lastStreakDate = today;
|
|
341
|
+
}
|
|
342
|
+
else if (lastDate === today) {
|
|
343
|
+
// Already played today, no change
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// Check if yesterday
|
|
347
|
+
const yesterday = new Date();
|
|
348
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
349
|
+
const yesterdayStr = yesterday.toISOString().split("T")[0];
|
|
350
|
+
if (lastDate === yesterdayStr) {
|
|
351
|
+
// Consecutive day
|
|
352
|
+
stats.global.currentStreak++;
|
|
353
|
+
if (stats.global.currentStreak > stats.global.bestStreak) {
|
|
354
|
+
stats.global.bestStreak = stats.global.currentStreak;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
// Streak broken
|
|
359
|
+
stats.global.currentStreak = 1;
|
|
360
|
+
}
|
|
361
|
+
stats.global.lastStreakDate = today;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Record a game session
|
|
366
|
+
*/
|
|
367
|
+
export async function recordGameSession(gameId, score, duration, won, customStats) {
|
|
368
|
+
const data = await loadStats();
|
|
369
|
+
const now = new Date().toISOString();
|
|
370
|
+
// Initialize game stats if needed
|
|
371
|
+
if (!data.games[gameId]) {
|
|
372
|
+
data.games[gameId] = { ...DEFAULT_GAME_STATS };
|
|
373
|
+
}
|
|
374
|
+
const gameStats = data.games[gameId];
|
|
375
|
+
// Update game stats
|
|
376
|
+
gameStats.gamesPlayed++;
|
|
377
|
+
gameStats.timePlayed += duration;
|
|
378
|
+
gameStats.totalScore += score;
|
|
379
|
+
if (score > gameStats.highestScore) {
|
|
380
|
+
gameStats.highestScore = score;
|
|
381
|
+
}
|
|
382
|
+
if (won !== undefined) {
|
|
383
|
+
if (won) {
|
|
384
|
+
gameStats.wins++;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
gameStats.losses++;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
gameStats.lastPlayed = now;
|
|
391
|
+
// Merge custom stats
|
|
392
|
+
if (customStats) {
|
|
393
|
+
if (!gameStats.custom) {
|
|
394
|
+
gameStats.custom = {};
|
|
395
|
+
}
|
|
396
|
+
for (const [key, value] of Object.entries(customStats)) {
|
|
397
|
+
gameStats.custom[key] = (gameStats.custom[key] ?? 0) + value;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Update global stats
|
|
401
|
+
data.global.totalGamesPlayed++;
|
|
402
|
+
data.global.totalTimePlayed += duration;
|
|
403
|
+
if (!data.global.firstPlayed) {
|
|
404
|
+
data.global.firstPlayed = now;
|
|
405
|
+
}
|
|
406
|
+
data.global.lastPlayed = now;
|
|
407
|
+
// Update streak
|
|
408
|
+
updateStreak(data);
|
|
409
|
+
// Check for new achievements
|
|
410
|
+
const newAchievements = checkAchievements(data);
|
|
411
|
+
// Save
|
|
412
|
+
await saveStats(data);
|
|
413
|
+
return newAchievements;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Record a session start (increment session count)
|
|
417
|
+
*/
|
|
418
|
+
export async function recordSessionStart() {
|
|
419
|
+
const data = await loadStats();
|
|
420
|
+
data.global.sessionCount++;
|
|
421
|
+
await saveStats(data);
|
|
422
|
+
}
|
|
423
|
+
// ============================================================================
|
|
424
|
+
// ACHIEVEMENT OPERATIONS
|
|
425
|
+
// ============================================================================
|
|
426
|
+
/**
|
|
427
|
+
* Check for newly unlocked achievements
|
|
428
|
+
* Returns array of newly unlocked achievement IDs
|
|
429
|
+
*/
|
|
430
|
+
export function checkAchievements(data) {
|
|
431
|
+
const newUnlocks = [];
|
|
432
|
+
const unlockedIds = new Set(data.achievements.map((a) => a.id));
|
|
433
|
+
for (const achievement of ACHIEVEMENTS) {
|
|
434
|
+
if (!unlockedIds.has(achievement.id) && achievement.condition(data)) {
|
|
435
|
+
newUnlocks.push(achievement.id);
|
|
436
|
+
data.achievements.push({
|
|
437
|
+
id: achievement.id,
|
|
438
|
+
unlockedAt: new Date().toISOString(),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Update achievement count
|
|
443
|
+
data.global.achievementsUnlocked = data.achievements.length;
|
|
444
|
+
return newUnlocks;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Get all achievements with unlock status
|
|
448
|
+
*/
|
|
449
|
+
export async function getAchievements() {
|
|
450
|
+
const data = await loadStats();
|
|
451
|
+
const unlockedMap = new Map(data.achievements.map((a) => [a.id, a.unlockedAt]));
|
|
452
|
+
return ACHIEVEMENTS.map((achievement) => ({
|
|
453
|
+
...achievement,
|
|
454
|
+
unlocked: unlockedMap.has(achievement.id),
|
|
455
|
+
unlockedAt: unlockedMap.get(achievement.id) ?? null,
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Get achievement by ID
|
|
460
|
+
*/
|
|
461
|
+
export function getAchievementById(id) {
|
|
462
|
+
return ACHIEVEMENTS.find((a) => a.id === id);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Get achievement count
|
|
466
|
+
*/
|
|
467
|
+
export async function getAchievementCount() {
|
|
468
|
+
const data = await loadStats();
|
|
469
|
+
return {
|
|
470
|
+
unlocked: data.achievements.length,
|
|
471
|
+
total: ACHIEVEMENTS.length,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Get all stats data
|
|
476
|
+
*/
|
|
477
|
+
export async function getAllStats() {
|
|
478
|
+
return loadStats();
|
|
479
|
+
}
|
|
480
|
+
// ============================================================================
|
|
481
|
+
// FORMATTING HELPERS
|
|
482
|
+
// ============================================================================
|
|
483
|
+
/**
|
|
484
|
+
* Format duration in seconds to human-readable string
|
|
485
|
+
*/
|
|
486
|
+
export function formatDuration(seconds) {
|
|
487
|
+
if (seconds < 60) {
|
|
488
|
+
return `${Math.floor(seconds)}s`;
|
|
489
|
+
}
|
|
490
|
+
if (seconds < 3600) {
|
|
491
|
+
const mins = Math.floor(seconds / 60);
|
|
492
|
+
const secs = Math.floor(seconds % 60);
|
|
493
|
+
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
494
|
+
}
|
|
495
|
+
const hours = Math.floor(seconds / 3600);
|
|
496
|
+
const mins = Math.floor((seconds % 3600) / 60);
|
|
497
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Format a date string for display
|
|
501
|
+
*/
|
|
502
|
+
export function formatStatsDate(timestamp) {
|
|
503
|
+
if (!timestamp)
|
|
504
|
+
return "Never";
|
|
505
|
+
const date = new Date(timestamp);
|
|
506
|
+
const now = new Date();
|
|
507
|
+
if (date.toDateString() === now.toDateString()) {
|
|
508
|
+
return "Today";
|
|
509
|
+
}
|
|
510
|
+
const yesterday = new Date();
|
|
511
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
512
|
+
if (date.toDateString() === yesterday.toDateString()) {
|
|
513
|
+
return "Yesterday";
|
|
514
|
+
}
|
|
515
|
+
return date.toLocaleDateString([], {
|
|
516
|
+
month: "short",
|
|
517
|
+
day: "numeric",
|
|
518
|
+
year: date.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
//# sourceMappingURL=stats.js.map
|