Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x | import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { players } from './players'
/**
* Player stats table - game statistics per player
*
* Tracks aggregate performance and per-game breakdowns for each player.
* One-to-one with players table. Deleted when player is deleted (cascade).
*/
export const playerStats = sqliteTable('player_stats', {
/** Primary key and foreign key to players table */
playerId: text('player_id')
.primaryKey()
.references(() => players.id, { onDelete: 'cascade' }),
/** Total number of games played across all game types */
gamesPlayed: integer('games_played').notNull().default(0),
/** Total number of games won */
totalWins: integer('total_wins').notNull().default(0),
/** Total number of games lost */
totalLosses: integer('total_losses').notNull().default(0),
/** Best completion time in milliseconds (across all games) */
bestTime: integer('best_time'),
/** Highest accuracy percentage (0.0 - 1.0, across all games) */
highestAccuracy: real('highest_accuracy').notNull().default(0),
/** Player's most-played game type */
favoriteGameType: text('favorite_game_type'),
/**
* Per-game statistics breakdown (JSON)
*
* Structure:
* {
* "matching": {
* gamesPlayed: 10,
* wins: 5,
* losses: 5,
* bestTime: 45000,
* highestAccuracy: 0.95,
* averageScore: 12.5,
* lastPlayed: 1704326400000
* },
* "complement-race": { ... },
* ...
* }
*/
gameStats: text('game_stats', { mode: 'json' })
.notNull()
.default('{}')
.$type<Record<string, GameStatsBreakdown>>(),
/** When this player last played any game */
lastPlayedAt: integer('last_played_at', { mode: 'timestamp' }),
/** When this record was created */
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
/** When this record was last updated */
updatedAt: integer('updated_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
})
/**
* Per-game stats breakdown stored in JSON
*/
export interface GameStatsBreakdown {
gamesPlayed: number
wins: number
losses: number
bestTime: number | null
highestAccuracy: number
averageScore: number
lastPlayed: number // timestamp
}
export type PlayerStats = typeof playerStats.$inferSelect
export type NewPlayerStats = typeof playerStats.$inferInsert
|