Skip to content

leaderboard

You declare boards per game in the console: name (^[a-z][a-z0-9_]{0,31}$), sort (desc or asc), min and max, keep (best or latest) and a rate per minute (10 by default). The API rejects a score outside the bounds. Periods are day, week and all.

leaderboard.submit(board, score, meta?): Promise<SubmitResult>

Section titled “leaderboard.submit(board, score, meta?): Promise<SubmitResult>”

Once per finished run, after gameplay.stop(), in a run that called gameplay.start(). score is a safe integer. meta is at most 512 bytes of JSON with string or number values, shown as the entry’s detail (tries, time). A board or a meta key named time is in seconds; the hub formats it as m:ss.

Submit a score

async function endRun(metres, tries) {
sdk.gameplay.stop();
const result = await sdk.leaderboard.submit('height', metres, { tries });
if (result.status === 'accepted') showRank(result.rank.week, result.best);
else if (result.status === 'rejected') console.warn('score rejected:', result.reason);
}

leaderboard.top(board, period): Promise<TopResult>

Section titled “leaderboard.top(board, period): Promise<TopResult>”

Up to 10 entries, ranked. The hub draws the top 5 and the player’s row itself; the SDK gives the raw list. total is the number of entries on the board for that period.

leaderboard.me(board, period): Promise<MeResult>

Section titled “leaderboard.me(board, period): Promise<MeResult>”

The player’s own entry for that period, or null when they have none.

Draw a board

const top = await sdk.leaderboard.top('height', 'week');
const me = await sdk.leaderboard.me('height', 'week');
if (top.status === 'unavailable') return hideBoard();
drawRows(top.entries.slice(0, 5)); // { rank, nickname, score, meta, isMe }
if (me.status === 'ok' && me.entry) drawMyRow(me.entry);

Leaderboard types

// Period: 'day' | 'week' | 'all'
// LeaderboardEntry: { rank, nickname, score, meta, isMe }
// SubmitResult: { status: 'accepted', rank: { day, week, all }, best }
// | { status: 'rejected', reason } 'bounds' | 'rate' | 'signature' | 'board' | 'session' | 'nonce' | 'clock' | 'meta'
// | { status: 'unavailable' }
// | { status: 'error', message } meta not serializable, network failure
// TopResult: { status: 'ok', entries, period, total } | { status: 'unavailable' }
// MeResult: { status: 'ok', entry, period } | { status: 'unavailable' }

gameplay.start() opens a play session and receives a session key. The SDK signs pid|gameId|board|score|nonce|ts with HMAC-SHA256 through crypto.subtle and sends the signature with the score. Replaying a nonce or submitting outside a session is rejected. The game does nothing for this; it only brackets play. Without crypto.subtle (an old browser on plain HTTP) submit answers rejected with reason signature and everything else works.

ReasonWhat it means
boundsThe score is outside the board’s min and max, or not a safe integer. Also the mapping of an unknown reason
rateMore scores than the board’s rate per minute (a 429)
sessionNo play session: gameplay.start() was not called in this run
signature, nonce, clockThe signature failed, the nonce was replayed, or the clock is off by too much
boardNo board of that name on this game
metameta is over 512 bytes or has a value that is not a string or a number
Hostsubmittop and me
filudi.comSigned, to the board of the gameWork, by period
CrazyGamesEncrypted for user.submitScore when init({ crazygames: { leaderboardKey } }) is given, else unavailable; rank values are 0unavailable: the portal draws its own
Pokiunavailableunavailable
PlaygamaThrough bridge.leaderboards unless its type is not_available; rank values are 0Work when the type is in_game
mockAccepted against the seed entries of mock.leaderboard; standalone answers unavailableSame

features.leaderboard from init() says ahead of time whether a board is worth drawing. Which boards to declare and how to pick their bounds is in leaderboards.