Global Rankings
The CodeJam leaderboard ranks all players by total XP, creating a competitive environment where skill and consistency determine your position.
Ranking System
Your global rank is calculated dynamically based on XP:
export const getRank = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) return 0;
const user = await ctx.db.get(userId);
if (!user || user.xp === undefined) return 0;
const betterUsers = await ctx.db
.query("users")
.withIndex("by_xp", q => q.gt("xp", user.xp!))
.filter(q => q.neq(q.field("isAnonymous"), true))
.collect();
return betterUsers.length + 1;
}
});
Your rank = (number of users with more XP than you) + 1. Anonymous users are excluded from rankings.
XP Index
Efficient ranking relies on database indexing:
users: defineTable({
// ... fields
xp: v.optional(v.number()),
level: v.optional(v.number()),
}).index("by_xp", ["xp"])
The by_xp index enables fast queries for:
- Global rankings
- Top player lists
- Percentile calculations
Top Players
View the highest-ranking players on the leaderboard.
Top Users Query
Retrieve the top players:
export const getTopUsers = query({
args: { limit: v.optional(v.number()) },
handler: async (ctx, args) => {
const limit = args.limit || 50;
const users = await ctx.db
.query("users")
.withIndex("by_xp")
.order("desc")
.filter(q => q.neq(q.field("isAnonymous"), true))
.take(limit);
return users.map(u => ({
_id: u._id,
name: u.name,
image: u.image,
customAvatar: u.customAvatar,
xp: u.xp || 0,
level: u.level || 1,
}));
}
});
Leaderboard Display
The leaderboard shows:
Player Name
Username or display name
Avatar
Profile picture or custom avatar
Total XP
Cumulative experience points
Level
Current progression level
Badges
Achievement highlights
The default leaderboard shows the top 50 players. Pass a custom limit to retrieve more or fewer.
Game-Specific Stats
Beyond global rankings, track per-challenge performance.
Game Stats Schema
Each game tracks individual player stats:
game_stats: defineTable({
userId: v.id("users"),
gameId: v.string(), // 'syntax-smasher', 'css-combat', etc.
bestScore: v.number(),
gamesPlayed: v.number(),
lastPlayed: v.number(),
}).index("by_user_game", ["userId", "gameId"])
.index("by_game", ["gameId"])
Challenge Leaderboards
Each challenge can have its own leaderboard:
Syntax Smasher
Function Fury
CSS Combat
gameId: "syntax-smasher"
Top scores:
1. Player1 - 5000 pts
2. Player2 - 4850 pts
3. Player3 - 4720 pts
gameId: "function-fury"
Top scores:
1. Player4 - 3200 pts
2. Player5 - 3150 pts
3. Player6 - 3100 pts
gameId: "css-combat"
Top scores:
1. Player7 - 4500 pts
2. Player8 - 4400 pts
3. Player9 - 4350 pts
Best Scores
Only your highest score per challenge is saved:
await ctx.db.patch(existing._id, {
bestScore: Math.max(existing.bestScore, args.score),
gamesPlayed: existing.gamesPlayed + 1,
lastPlayed: Date.now()
});
Play challenges repeatedly to improve your bestScore and climb the challenge-specific leaderboard.
Player Statistics
Detailed stats provide insight into player performance:
Personal Stats
- Total XP: Cumulative across all activities
- Global Rank: Position among all players
- Level: Calculated from total XP
- Quests Completed: Total activities finished
- Best Score: Highest score per challenge
- Games Played: Attempts per challenge
- Last Played: Timestamp of most recent attempt
- Challenge Rank: Position on challenge leaderboard
- Current Streak: Consecutive active days
- Weekly XP: Last 7 days of activity
- Daily XP Average: Mean XP per active day
- Peak Performance: Highest single-day XP
Anonymous Users
Anonymous users are excluded from competitive features:
filter(q => q.neq(q.field("isAnonymous"), true))
Anonymous users cannot:
- Appear on leaderboards
- Compete in rankings
- Challenge other players
- Unlock social achievements
Sign up to access competitive features!
Ranking Updates
Rankings update in real-time as players earn XP.
Update Flow
Player Earns XP
Complete a challenge or quest
Activity Logged
logActivity mutation updates user XP
Rank Recalculated
Next getRank query reflects new XP
Leaderboard Refreshes
getTopUsers query returns updated rankings
Rankings are dynamically calculated - no manual refresh needed. Queries always return current data.
Leaderboard Competition
Multiple competitive dimensions:
XP Race
Climb the global leaderboard by earning more XP than other players
Challenge Mastery
Achieve the highest score on individual challenges
Consistency Battle
Maintain the longest active streak
Speed Runs
Complete time-based objectives faster than competitors
Battle System Integration
Leaderboards power the battle system:
Ghost Battles
Challenge players by competing against their best scores:
export const getGhostScore = query({
args: { userId: v.id("users"), gameId: v.string() },
handler: async (ctx, args) => {
const stats = await ctx.db
.query("game_stats")
.withIndex("by_user_game", (q) =>
q.eq("userId", args.userId).eq("gameId", args.gameId)
)
.first();
return stats ? stats.bestScore : 0;
},
});
Ghost battles use the opponent’s bestScore as the target to beat.
Battle Outcomes
Battle results affect stats:
const isWin = args.score > (battle.opponentScore || 0);
await ctx.db.patch(args.battleId, {
challengerScore: args.score,
status: "completed",
winnerId: isWin ? userId : battle.opponentId
});
Winning ghost battles against top players proves your skill and boosts your reputation!
Player Search
Find specific players to challenge:
export const searchUsers = query({
args: { query: v.string() },
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) return [];
if (!args.query || args.query.length < 2) return [];
const users = await ctx.db
.query("users")
.filter(q => q.neq(q.field("isAnonymous"), true))
.collect();
return users.filter(u =>
(u.name?.toLowerCase().includes(args.query.toLowerCase()) ||
u.email?.toLowerCase().includes(args.query.toLowerCase())) &&
u._id !== userId // Exclude self
).slice(0, 5).map(u => ({
_id: u._id,
name: u.name,
email: u.email,
image: u.image,
customAvatar: u.customAvatar,
level: u.level
}));
}
});
Search supports:
- Name matching (case-insensitive)
- Email matching
- Returns top 5 results
- Excludes yourself and anonymous users
Leaderboard Strategy
Focus on High-XP Challenges
Expert challenges (300 XP) provide the fastest rank gains
Maintain Consistency
Daily play earns steady XP and protects your streak
Perfect Your Scores
Replay challenges to improve your best scores
Complete All Objectives
Full objective completion maximizes XP per challenge
Challenge Top Players
Ghost battles against leaders test your skills
Live Activity Feed
See real-time global activity:
export const getSidebarFeed = query({
args: {},
handler: async (ctx) => {
const recentActivity = await ctx.db
.query("activity_logs")
.order("desc")
.take(1);
let activityItem = null;
if (recentActivity.length > 0) {
const act = recentActivity[0];
const user = await ctx.db.get(act.userId);
if (user) {
activityItem = {
type: "event",
badge: "Live",
badgeColor: "bg-nav-orange",
title: user.name || "Anonymous",
subtitle: `${act.type} • +${act.xp} XP`
};
}
}
return [activityItem];
},
});
The activity feed shows the most recent XP-earning action platform-wide, creating a sense of live competition.
Next Steps
Progression
Learn how to earn XP and level up
Challenges
Explore challenges to climb rankings
Achievements
Unlock badges to showcase mastery
Battle System
Challenge players in ghost battles