Skip to content

Score Engine

Alekmaul edited this page Jul 20, 2026 · 1 revision

PVSneslib includes a score management engine designed for arcade-style games where a player's score can reach large values.

A complete working example is available in snes-examples/systems/scoring.

How the score is stored

A score is represented by the scoMemory struct, which holds two 16-bit unsigned integers:

typedef struct
{
    u16 scolo;   // low counter  — counts from 0 to 9999
    u16 scohi;   // high counter — counts from 0 to 9999
} scoMemory;

The two fields behave like a two-digit decimal group in a larger counter:

  • scolo accumulates points in the range 0 to 9 999.
  • When scolo reaches or exceeds 10 000, the excess wraps back to zero and scohi is incremented by 1.
  • scohi also ranges from 0 to 9 999 and is saturated at 9 999 — there is no further carry.

This gives a maximum representable score of 9 999 × 10 000 + 9 999 = 99 989 999.

The struct occupies 4 bytes of WRAM. Declare one or more of them as global variables in your.c file:

#include <snes.h>

scoMemory playerScore;
scoMemory hiScore;

Including the header

#include <snes/scores.h>

scores.h is already pulled in by the master <snes.h> header, so in most projects you do not need to include it separately.

API reference

scoreClear — reset a score to zero

void scoreClear(scoMemory *source);

Sets both scolo and scohi of *source to 0.

Example:

scoreClear(&playerScore);

scoreAdd — add points to a score

void scoreAdd(scoMemory *source, u16 value);

Adds value (0–9 999) to source->scolo. If the result reaches or exceeds 10 000, the engine subtracts 10 000 from scolo and increments scohi by one. scohi is capped at 9 999.

Example:

// Award 250 points
scoreAdd(&playerScore, 250);

// Award 9750 points — if scolo was already 500, this pushes it to 10250,
// which wraps: scolo → 250, scohi → scohi + 1
scoreAdd(&playerScore, 9750);

scoreSub — subtract points from a score

void scoreSub(scoMemory *source, u16 value);

Subtracts value from source->scolo. If scolo goes below zero, the engine borrows from scohi (adds 10 000 to scolo and decrements scohi).
If scohi is already 0 and a borrow would be needed, the score saturates at {0, 0} — it never goes negative.

Example:

// Deduct 100 points penalty
scoreSub(&playerScore, 100);

scoreCpy — copy a score

void scoreCpy(scoMemory *source, scoMemory *dest);

Copies both scolo and scohi from *source into *dest. Use this to update the high-score record or to snapshot a score.

Example:

// Save player score as the new high score
scoreCpy(&playerScore, &hiScore);

scoreCmp — compare two scores

u8 scoreCmp(scoMemory *source, scoMemory *dest);

Compares *source against *dest and returns:

Return value Meaning
0x00 *source equals *dest
0xFF *source is greater than *dest
0x01 *source is less than *dest

The comparison checks scohi first; scolo is only compared when the high words are equal.

Example:

if (scoreCmp(&playerScore, &hiScore) == 0xFF)
{
    // Player beat the high score — copy it
    scoreCpy(&playerScore, &hiScore);
}

scoreToStr — convert a score to a display string

void scoreToStr(scoMemory *source, char *buf);

Converts *source to a fixed-width, null-terminated ASCII string and writes it into buf.

The output is always exactly 8 digits followed by a null terminator (9 bytes total):

HHHH LLLL \0
│    │
│    └─ scolo, zero-padded to 4 digits
└────── scohi, zero-padded to 4 digits

buf must point to a buffer of at least 9 bytes.

Score String written to buf
{ scohi=0, scolo=0 } "00000000"
{ scohi=0, scolo=42 } "00000042"
{ scohi=1, scolo=2345 } "00012345"
{ scohi=9999, scolo=9999 } "99999999"

Example:

char scoreBuf[9];

scoreToStr(&playerScore, scoreBuf);
consoleDrawText(10, 5, scoreBuf);   // draw the 8-digit string on screen

Tips

Score cap. The maximum score is { scohi=9999, scolo=9999 }, displayed as "99999999". scohi is saturated and will not overflow further; no undefined behaviour occurs if you keep adding points beyond the maximum.

Updating the high score. Call scoreCmp first, then scoreCpy only if the player's score is greater. This avoids needlessly overwriting the high score with an equal value.

Game Over reset. Call scoreClear at the start of each new game to zero scolo and scohi. The high score variable should only be cleared when deliberately resetting records (e.g. a factory-reset option in your settings menu).

Saving to SRAM. scoMemory is a plain 4-byte struct. You can copy it into your SRAM save block with a simple memcpy or by copying the two fields individually — no special serialisation is required.

Clone this wiki locally