Leader board needs 5 finish games to be qualified
This commit is contained in:
@@ -103,10 +103,20 @@ function findUser(username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const EMPTY_STATS = { games_played: 0, games_won: 0, shelemCount: 0, bids_won: 0, total_score: 0 };
|
||||
|
||||
// Read-only lookup. Never creates or persists a row — the leaderboard walks
|
||||
// every known user, and creating-on-read there meant one full stats.json write
|
||||
// per user on every page load, plus a file steadily filling with empty rows.
|
||||
function readStats(userId) {
|
||||
return stats.find(s => s.userId === userId) || { userId, ...EMPTY_STATS };
|
||||
}
|
||||
|
||||
// Write path: materialises the row so addStats has something to accumulate into.
|
||||
function getStats(userId) {
|
||||
let s = stats.find(s => s.userId === userId);
|
||||
if (!s) {
|
||||
s = { userId, games_played: 0, games_won: 0, shelemCount: 0, bids_won: 0, total_score: 0 };
|
||||
s = { userId, ...EMPTY_STATS };
|
||||
stats.push(s);
|
||||
saveStats();
|
||||
}
|
||||
@@ -146,7 +156,11 @@ app.use(express.static(path.join(__dirname, 'public'), {
|
||||
const APP_VERSION = require('./package.json').version;
|
||||
|
||||
app.get('/api/config', (_req, res) => {
|
||||
res.json({ signupsOpen: config.signupsOpen, version: APP_VERSION });
|
||||
res.json({
|
||||
signupsOpen: config.signupsOpen,
|
||||
version: APP_VERSION,
|
||||
minRankedGames: MIN_RANKED_GAMES,
|
||||
});
|
||||
});
|
||||
|
||||
// Validate the stored auth token; the client uses this at startup to detect a
|
||||
@@ -207,7 +221,7 @@ app.get('/api/profile/:username', (req, res) => {
|
||||
const user = findUser(req.params.username);
|
||||
if (!user) return res.status(404).json({ error: 'User not found' });
|
||||
const effectiveId = user._fromShared ? `hokm_${user.id}` : user.id;
|
||||
const s = getStats(effectiveId);
|
||||
const s = readStats(effectiveId);
|
||||
res.json({
|
||||
username: user.username,
|
||||
games_played: s.games_played || 0,
|
||||
@@ -246,6 +260,9 @@ app.get('/api/history/:username', (req, res) => {
|
||||
res.json(games);
|
||||
});
|
||||
|
||||
// Finished games a player needs before they appear in the ranking proper.
|
||||
const MIN_RANKED_GAMES = 5;
|
||||
|
||||
app.get('/api/leaderboard', (_req, res) => {
|
||||
const allUsers = [...users];
|
||||
if (SHARED_USERS_FILE) {
|
||||
@@ -259,7 +276,7 @@ app.get('/api/leaderboard', (_req, res) => {
|
||||
}
|
||||
const rows = allUsers.map(u => {
|
||||
const eid = u._fromShared ? `hokm_${u.id}` : u.id;
|
||||
const s = getStats(eid);
|
||||
const s = readStats(eid);
|
||||
const played = s.games_played || 0;
|
||||
const won = s.games_won || 0;
|
||||
return {
|
||||
@@ -274,14 +291,28 @@ app.get('/api/leaderboard', (_req, res) => {
|
||||
avg_score: played > 0 ? +((s.total_score || 0) / played).toFixed(1) : null,
|
||||
};
|
||||
})
|
||||
.filter(r => r.games_played > 0)
|
||||
.sort((a, b) =>
|
||||
.filter(r => r.games_played > 0);
|
||||
|
||||
const byWinRate = (a, b) =>
|
||||
(b.win_rate - a.win_rate) || // primary: win rate
|
||||
(b.avg_score - a.avg_score) || // tie-break: average score
|
||||
(b.games_played - a.games_played)
|
||||
)
|
||||
.slice(0, 30);
|
||||
res.json(rows);
|
||||
(b.avg_score - a.avg_score) || // tie-break: average score (higher is better)
|
||||
(b.games_played - a.games_played);
|
||||
|
||||
// A single lucky game shouldn't top the board, so a player needs
|
||||
// MIN_RANKED_GAMES finished games before they get a rank. Everyone below that
|
||||
// is still listed, unranked, so a new player can see their progress.
|
||||
const ranked = rows.filter(r => r.games_played >= MIN_RANKED_GAMES)
|
||||
.sort(byWinRate)
|
||||
.slice(0, 30)
|
||||
.map(r => ({ ...r, ranked: true, gamesToRank: 0 }));
|
||||
|
||||
const provisional = rows.filter(r => r.games_played < MIN_RANKED_GAMES)
|
||||
.sort((a, b) => b.games_played - a.games_played || byWinRate(a, b))
|
||||
.slice(0, 10)
|
||||
.map(r => ({ ...r, ranked: false,
|
||||
gamesToRank: MIN_RANKED_GAMES - r.games_played }));
|
||||
|
||||
res.json([...ranked, ...provisional]);
|
||||
});
|
||||
|
||||
app.get('/api/rooms', (_req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user