Persistent Storage¶
Save data across games for leaderboards, ELO, and achievements.
Overview¶
persistentStorage provides cross-game data storage that survives beyond individual game sessions.
Basic Operations¶
Increment Values¶
// Increment wins by 1
await this.persistentStorage.increment('players', oderId, 'wins', 1);
// Increment ELO by 25
await this.persistentStorage.increment('players', oderId, 'elo', 25);
// Decrement ELO by 15
await this.persistentStorage.increment('players', oderId, 'elo', -15);
Get Ordered Data¶
// Top 10 players by ELO (descending)
const topByElo = await this.persistentStorage.getOrderedDesc(
'players', // collection
'elo', // field
10 // limit
);
// Result: [{ oderId: '...', elo: 2100 }, { oderId: '...', elo: 2050 }, ...]
Common Use Cases¶
Leaderboard System¶
async onGameEnd(game: Game, winnerId: PlayerID): Promise<void> {
// Update winner stats
await this.persistentStorage.increment('players', winnerId, 'wins', 1);
await this.persistentStorage.increment('players', winnerId, 'elo', 25);
// Update all players' games played
for (const player of game.players()) {
await this.persistentStorage.increment('players', player.oderId(), 'gamesPlayed', 1);
}
}
// Scheduler job to compute leaderboard
async computeLeaderboard(): Promise<void> {
const topPlayers = await this.persistentStorage.getOrderedDesc(
'players',
'elo',
100
);
// Cache or process leaderboard
console.log('Top player:', topPlayers[0]);
}
ELO Rating System¶
async updateElo(winnerId: PlayerID, loserId: PlayerID): Promise<void> {
// Simple ELO calculation
const K = 32;
// Get current ratings (simplified - you'd fetch these)
const winnerRating = 1500;
const loserRating = 1500;
// Calculate expected scores
const expectedWinner = 1 / (1 + Math.pow(10, (loserRating - winnerRating) / 400));
const expectedLoser = 1 / (1 + Math.pow(10, (winnerRating - loserRating) / 400));
// Calculate rating changes
const winnerChange = Math.round(K * (1 - expectedWinner));
const loserChange = Math.round(K * (0 - expectedLoser));
// Update ratings
await this.persistentStorage.increment('players', winnerId, 'elo', winnerChange);
await this.persistentStorage.increment('players', loserId, 'elo', loserChange);
}
Team ELO¶
async distributeTeamElo(
winningTeam: string[],
losingTeam: string[]
): Promise<void> {
const winBonus = 25;
const lossPenalty = 15;
// Award winning team
for (const oderId of winningTeam) {
await this.persistentStorage.increment('players', oderId, 'elo', winBonus);
await this.persistentStorage.increment('players', oderId, 'teamWins', 1);
}
// Penalize losing team
for (const oderId of losingTeam) {
await this.persistentStorage.increment('players', oderId, 'elo', -lossPenalty);
await this.persistentStorage.increment('players', oderId, 'teamLosses', 1);
}
}
Clan Statistics¶
async updateClanStats(clanId: string, territory: number): Promise<void> {
// Track clan performance
await this.persistentStorage.increment('clans', clanId, 'totalTerritory', territory);
await this.persistentStorage.increment('clans', clanId, 'gamesPlayed', 1);
}
async getClanLeaderboard(): Promise<unknown[]> {
return this.persistentStorage.getOrderedDesc('clans', 'totalTerritory', 10);
}
Achievement Tracking¶
async checkAchievements(oderId: string, stats: PlayerStats): Promise<void> {
// First Win
if (stats.wins === 1) {
await this.unlockAchievement(playerId, 'first_win');
}
// Dominator - 100 wins
if (stats.wins >= 100) {
await this.unlockAchievement(playerId, 'dominator');
}
// Elite - 2000+ ELO
if (stats.elo >= 2000) {
await this.unlockAchievement(playerId, 'elite');
}
}
async unlockAchievement(oderId: string, achievementId: string): Promise<void> {
await this.persistentStorage.increment(
'achievements',
`${playerId}:${achievementId}`,
'unlocked',
1
);
}
Best Practices¶
Async Handling
All storage operations are async. Use in lifecycle hooks like onGameEnd, not in execute().
Rate Limiting
Batch updates when possible to reduce database load.
Data Structure
Plan your collections and fields carefully. Storage schema changes require migrations.
Consistency
Storage updates are eventually consistent. Don't depend on immediate reads after writes.