Hall Of Fame
Source: src/hall_of_fame.c · 285 lines · 7 functions.
Screen captures
Views of this part running on the Mini Arcade.

Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
playMoonlightMelody() · source line 148
static void playMoonlightMelody(unsigned step)
Calls into this documentation:
- FOR loop
unsigned note = 0U; note < MOONLIGHT_MELODY_COUNT; ++noteRepeat body while the loop condition permits; then continue below.
- Decision
moonlight_melody[note].step == stepTrue- Execute
audioPlayMusicNote(moonlight_melody[note].frequency_hz, moonlight_melody[note].duration_ms);
FalseContinue after this decision.
Read original function source
static void playMoonlightMelody(unsigned step)
{
for (unsigned note = 0U; note < MOONLIGHT_MELODY_COUNT; ++note) {
if (moonlight_melody[note].step == step) {
audioPlayMusicNote(moonlight_melody[note].frequency_hz,
moonlight_melody[note].duration_ms);
}
}
}playMoonlightStep() · source line 158
static void playMoonlightStep(void)
Calls into this documentation:
- Execute
const moonlight_step_t *step = &moonlight_opening[moonlight_step]; - Decision
step->bass_hz != 0UTrue- Execute
audioPlayMusicNote(step->bass_hz, MOONLIGHT_BAR_MS);
FalseContinue after this decision.
- Execute
audioPlayMusicNote(step->arpeggio_hz, MOONLIGHT_STEP_MS - 30U); - Execute
playMoonlightMelody(moonlight_step); - Execute
moonlight_step = (moonlight_step + 1U) % MOONLIGHT_STEP_COUNT;
Read original function source
static void playMoonlightStep(void)
{
const moonlight_step_t *step = &moonlight_opening[moonlight_step];
if (step->bass_hz != 0U) audioPlayMusicNote(step->bass_hz, MOONLIGHT_BAR_MS);
audioPlayMusicNote(step->arpeggio_hz, MOONLIGHT_STEP_MS - 30U);
playMoonlightMelody(moonlight_step);
moonlight_step = (moonlight_step + 1U) % MOONLIGHT_STEP_COUNT;
}startHallOfFame() · source line 168
void startHallOfFame(uint32_t now_ms)
Calls into this documentation:
- FOR loop
unsigned game = 0; game < HIGH_SCORE_GAME_COUNT; ++gameRepeat body while the loop condition permits; then continue below.
- Execute
table_status[game] = high_scores_load((high_score_game_t)game, &tables[game]);
- Execute
selected_game = 0; - Execute
starfield_initialize(&hall_stars, HALL_STAR_COUNT, HALL_STAR_LEFT, HALL_STAR_TOP, HALL_STAR_WIDTH, HALL_STAR_HEIGHT, STARFIELD_GREYSCALE); - Execute
flameEffectInitialize(&top_flame, top_flame_heat, FLAME_COLUMNS, FLAME_ROWS, FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x13579BDFU); - Execute
flameEffectInitialize(&bottom_flame, bottom_flame_heat, FLAME_COLUMNS, FLAME_ROWS, FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x2468ACE1U); - Execute
flameEffectInitialize(&top_left_side_flame, top_left_side_flame_heat, SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS, FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x3456789AU); - Execute
flameEffectInitialize(&top_right_side_flame, top_right_side_flame_heat, SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS, FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x456789ABU); - Execute
flameEffectInitialize(&bottom_left_side_flame, bottom_left_side_flame_heat, SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS, FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x56789ABCU); - Execute
flameEffectInitialize(&bottom_right_side_flame, bottom_right_side_flame_heat, SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS, FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x6789ABCDU); - Execute
last_page_change = last_flame_update = now_ms; - Execute
startMoonlight(now_ms); - Execute
controls_ready = false; - Execute
running = true;
Read original function source
void startHallOfFame(uint32_t now_ms)
{
for (unsigned game = 0; game < HIGH_SCORE_GAME_COUNT; ++game) {
table_status[game] = high_scores_load((high_score_game_t)game, &tables[game]);
}
selected_game = 0;
// Stars stay inside the black score area, clear of the flame borders.
starfield_initialize(&hall_stars, HALL_STAR_COUNT, HALL_STAR_LEFT, HALL_STAR_TOP,
HALL_STAR_WIDTH, HALL_STAR_HEIGHT, STARFIELD_GREYSCALE);
flameEffectInitialize(&top_flame, top_flame_heat, FLAME_COLUMNS, FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x13579BDFU);
flameEffectInitialize(&bottom_flame, bottom_flame_heat, FLAME_COLUMNS, FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x2468ACE1U);
flameEffectInitialize(&top_left_side_flame, top_left_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x3456789AU);
flameEffectInitialize(&top_right_side_flame, top_right_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x456789ABU);
flameEffectInitialize(&bottom_left_side_flame, bottom_left_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x56789ABCU);
flameEffectInitialize(&bottom_right_side_flame, bottom_right_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x6789ABCDU);
last_page_change = last_flame_update = now_ms;
startMoonlight(now_ms);
controls_ready = false;
running = true;
}stopHallOfFame() · source line 199
void stopHallOfFame(void)
Calls into this documentation:
- Execute
stopMoonlight(); - Execute
running = false;
Read original function source
void stopHallOfFame(void)
{
stopMoonlight();
running = false;
}updateHallOfFame() · source line 205
void updateHallOfFame(int x_offset, int y_offset, bool valid, bool left, bool right,
uint32_t now_ms)Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
updateMoonlight(now_ms); - Decision
now_ms - last_flame_update >= FLAME_FRAME_MSTrue- Execute
const uint32_t elapsed = now_ms - last_flame_update; - Execute
last_flame_update = now_ms; - Execute
starfield_update(&hall_stars, elapsed); - Execute
flameEffectUpdate(&top_flame); - Execute
flameEffectUpdate(&bottom_flame); - Execute
const uint8_t *top_flame_edge = top_flame_heat + (FLAME_ROWS - 1U) * FLAME_COLUMNS; - Execute
const uint8_t *bottom_flame_source = bottom_flame_heat + (FLAME_ROWS - 1U) * FLAME_COLUMNS; - Execute
flameEffectUpdateFromSource(&top_left_side_flame, top_flame_edge); - Execute
flameEffectUpdateFromSource(&top_right_side_flame, top_flame_edge + FLAME_COLUMNS - SIDE_FLAME_COLUMNS); - Execute
flameEffectUpdateFromSource(&bottom_left_side_flame, bottom_flame_source); - Execute
flameEffectUpdateFromSource(&bottom_right_side_flame, bottom_flame_source + FLAME_COLUMNS - SIDE_FLAME_COLUMNS);
FalseContinue after this decision.
- Decision
!validTrue- Execute
controls_ready = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350True- Execute
controls_ready = true;
FalseContinue after this decision.
- Execute
int direction = 0; - Decision
left != rightTrue- Execute
direction = right ? 1 : -1;
False- Decision
controls_ready && (x_offset > 600 || x_offset < -600)True- Execute
direction = x_offset > 0 ? 1 : -1;
FalseContinue after this decision.
- Decision
direction == 0 || now_ms - last_page_change < PAGE_CHANGE_MSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
selected_game = (selected_game + HIGH_SCORE_GAME_COUNT + direction) % HIGH_SCORE_GAME_COUNT; - Execute
last_page_change = now_ms;
Read original function source
void updateHallOfFame(int x_offset, int y_offset, bool valid, bool left, bool right,
uint32_t now_ms)
{
if (!running) return;
updateMoonlight(now_ms);
if (now_ms - last_flame_update >= FLAME_FRAME_MS) {
const uint32_t elapsed = now_ms - last_flame_update;
last_flame_update = now_ms;
starfield_update(&hall_stars, elapsed);
flameEffectUpdate(&top_flame);
flameEffectUpdate(&bottom_flame);
const uint8_t *top_flame_edge = top_flame_heat +
(FLAME_ROWS - 1U) * FLAME_COLUMNS;
const uint8_t *bottom_flame_source = bottom_flame_heat +
(FLAME_ROWS - 1U) * FLAME_COLUMNS;
flameEffectUpdateFromSource(&top_left_side_flame, top_flame_edge);
flameEffectUpdateFromSource(&top_right_side_flame,
top_flame_edge + FLAME_COLUMNS - SIDE_FLAME_COLUMNS);
flameEffectUpdateFromSource(&bottom_left_side_flame, bottom_flame_source);
flameEffectUpdateFromSource(&bottom_right_side_flame,
bottom_flame_source + FLAME_COLUMNS - SIDE_FLAME_COLUMNS);
}
if (!valid) {
controls_ready = false;
return;
}
if (x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350) {
controls_ready = true;
}
int direction = 0;
if (left != right) direction = right ? 1 : -1;
else if (controls_ready && (x_offset > 600 || x_offset < -600))
direction = x_offset > 0 ? 1 : -1;
if (direction == 0 || now_ms - last_page_change < PAGE_CHANGE_MS) return;
selected_game = (selected_game + HIGH_SCORE_GAME_COUNT + direction) % HIGH_SCORE_GAME_COUNT;
last_page_change = now_ms;
}drawHallOfFame() · source line 243
void drawHallOfFame(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const flame_palette_t *palette = &game_flame_palettes[selected_game]; - Execute
clear(frame, COLOR_BLACK); - Execute
starfield_draw(frame, &hall_stars); - Execute
flameEffectDraw(frame, &top_flame, 0, TOP_FLAME_Y, palette); - Execute
flameEffectDraw(frame, &bottom_flame, 0, BOTTOM_FLAME_Y, palette); - Execute
flameEffectDraw(frame, &top_left_side_flame, 0, TOP_SIDE_FLAME_Y, palette); - Execute
flameEffectDraw(frame, &top_right_side_flame, RIGHT_SIDE_FLAME_X, TOP_SIDE_FLAME_Y, palette); - Execute
flameEffectDraw(frame, &bottom_left_side_flame, 0, BOTTOM_SIDE_FLAME_Y, palette); - Execute
flameEffectDraw(frame, &bottom_right_side_flame, RIGHT_SIDE_FLAME_X, BOTTOM_SIDE_FLAME_Y, palette); - Execute
draw_text_centred(frame, 160, 4, "HALL OF FAME", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 37, game_names[selected_game], 2, COLOR_CYAN); - Decision
table_status[selected_game] != HIGH_SCORES_OKTrue- Execute
draw_text_centred(frame, 160, 83, "SCORES UNAVAILABLE", 1, COLOR_RED);
False- Execute
const high_score_table_t *table = &tables[selected_game]; - FOR loop
unsigned rank = 0; rank < HIGH_SCORE_COUNT; ++rankRepeat body while the loop condition permits; then continue below.
- Execute
char line[36]; - Decision
rank < table->countTrue- Execute
snprintf(line, sizeof(line), "%u. %-16s %u", rank + 1U, table->entries[rank].name, (unsigned)table->entries[rank].score);
False- Execute
snprintf(line, sizeof(line), "%u. ---", rank + 1U);
- Execute
const uint16_t colour = rank == 0 ? COLOR_YELLOW : rank == 1 ? COLOR_LIGHTGRAY : COLOR_ORANGE; - Execute
draw_text_centred(frame, 160, 62 + (int)rank * 16, line, 1, colour);
- Execute
draw_text_centred(frame, 160, 160, "D/B OR STICK GAME BOOT BACK", 1, COLOR_LIGHTGRAY);
Read original function source
void drawHallOfFame(uint16_t *frame)
{
if (!running || frame == NULL) return;
const flame_palette_t *palette = &game_flame_palettes[selected_game];
clear(frame, COLOR_BLACK);
starfield_draw(frame, &hall_stars);
flameEffectDraw(frame, &top_flame, 0, TOP_FLAME_Y, palette);
flameEffectDraw(frame, &bottom_flame, 0, BOTTOM_FLAME_Y, palette);
flameEffectDraw(frame, &top_left_side_flame, 0, TOP_SIDE_FLAME_Y, palette);
flameEffectDraw(frame, &top_right_side_flame, RIGHT_SIDE_FLAME_X, TOP_SIDE_FLAME_Y,
palette);
flameEffectDraw(frame, &bottom_left_side_flame, 0, BOTTOM_SIDE_FLAME_Y, palette);
flameEffectDraw(frame, &bottom_right_side_flame, RIGHT_SIDE_FLAME_X,
BOTTOM_SIDE_FLAME_Y, palette);
draw_text_centred(frame, 160, 4, "HALL OF FAME", 1, COLOR_WHITE);
draw_text_centred(frame, 160, 37, game_names[selected_game], 2, COLOR_CYAN);
if (table_status[selected_game] != HIGH_SCORES_OK) {
draw_text_centred(frame, 160, 83, "SCORES UNAVAILABLE", 1, COLOR_RED);
} else {
const high_score_table_t *table = &tables[selected_game];
for (unsigned rank = 0; rank < HIGH_SCORE_COUNT; ++rank) {
char line[36];
if (rank < table->count) {
snprintf(line, sizeof(line), "%u. %-16s %u", rank + 1U,
table->entries[rank].name, (unsigned)table->entries[rank].score);
} else {
snprintf(line, sizeof(line), "%u. ---", rank + 1U);
}
const uint16_t colour = rank == 0 ? COLOR_YELLOW :
rank == 1 ? COLOR_LIGHTGRAY : COLOR_ORANGE;
draw_text_centred(frame, 160, 62 + (int)rank * 16, line, 1, colour);
}
}
draw_text_centred(frame, 160, 160, "D/B OR STICK GAME BOOT BACK", 1, COLOR_LIGHTGRAY);
}hallOfFameScreenshotLabel() · source line 279
const char *hallOfFameScreenshotLabel(void)
- Exit this function
return selected_game < HIGH_SCORE_GAME_COUNT ? game_screenshot_labels[selected_game] : "hall-of-fame";
Read original function source
const char *hallOfFameScreenshotLabel(void)
{
return selected_game < HIGH_SCORE_GAME_COUNT ? game_screenshot_labels[selected_game] :
"hall-of-fame";
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "hall_of_fame.h"
#include "moonlight.h"
#include "flame_effect.h"
#include "c64_colors.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "high_scores.h"
#include "starfield.h"
#include <stdio.h>
#define FLAME_PIXEL_WIDTH 2U
#define FLAME_PIXEL_HEIGHT 2U
#define FLAME_COLUMNS (LCD_WIDTH / FLAME_PIXEL_WIDTH)
#define FLAME_ROWS 10U
#define FLAME_CELL_COUNT (FLAME_COLUMNS * FLAME_ROWS)
#define TOP_FLAME_Y 14
#define BOTTOM_FLAME_Y 136
#define SIDE_FLAME_WIDTH 48U
#define SIDE_FLAME_COLUMNS (SIDE_FLAME_WIDTH / FLAME_PIXEL_WIDTH)
#define SIDE_FLAME_ROWS 28U
#define SIDE_FLAME_CELL_COUNT (SIDE_FLAME_COLUMNS * SIDE_FLAME_ROWS)
#define TOP_SIDE_FLAME_Y (TOP_FLAME_Y + FLAME_ROWS * FLAME_PIXEL_HEIGHT)
#define BOTTOM_FLAME_SOURCE_Y (BOTTOM_FLAME_Y + (FLAME_ROWS - 1U) * FLAME_PIXEL_HEIGHT)
#define BOTTOM_SIDE_FLAME_Y (BOTTOM_FLAME_SOURCE_Y - (SIDE_FLAME_ROWS - 1U) * FLAME_PIXEL_HEIGHT)
#define RIGHT_SIDE_FLAME_X (LCD_WIDTH - SIDE_FLAME_WIDTH)
#define FLAME_FRAME_MS 20U
#define PAGE_CHANGE_MS 260U
#define HALL_STAR_COUNT 40U
#define HALL_STAR_LEFT SIDE_FLAME_WIDTH
#define HALL_STAR_TOP TOP_SIDE_FLAME_Y
#define HALL_STAR_WIDTH (LCD_WIDTH - 2U * SIDE_FLAME_WIDTH)
#define HALL_STAR_HEIGHT (BOTTOM_FLAME_Y - HALL_STAR_TOP)
#if 0
#define MOONLIGHT_STEP_MS 400U
#define MOONLIGHT_BAR_MS (MOONLIGHT_STEP_MS * 12U)
typedef struct {
uint16_t bass_hz;
uint16_t arpeggio_hz;
} moonlight_step_t;
typedef struct {
uint8_t step;
uint16_t frequency_hz;
uint16_t duration_ms;
} moonlight_melody_note_t;
// Beethoven, Piano Sonata No. 14, Op. 27 No. 2, I, bars 1-16. The source
// marks Adagio sostenuto at quarter note = 50: 400 ms per triplet note.
// This is the familiar C-sharp-minor opening, with its original bass changes.
static const moonlight_step_t moonlight_opening[] = {
{69U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U},
{62U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U},
{55U, 220U}, {0U, 277U}, {0U, 330U}, {0U, 220U}, {0U, 294U}, {0U, 370U}, {46U, 220U}, {0U, 294U}, {0U, 370U}, {0U, 220U}, {0U, 294U}, {0U, 370U},
{52U, 208U}, {0U, 262U}, {0U, 370U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {52U, 208U}, {0U, 277U}, {0U, 311U}, {185U, 262U}, {0U, 311U}, {0U, 311U},
{69U, 165U}, {0U, 208U}, {0U, 277U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U},
{65U, 208U}, {0U, 311U}, {0U, 370U}, {0U, 208U}, {0U, 311U}, {0U, 370U}, {0U, 208U}, {0U, 311U}, {0U, 370U}, {0U, 208U}, {0U, 311U}, {0U, 370U},
{69U, 208U}, {0U, 277U}, {0U, 330U}, {0U, 208U}, {0U, 277U}, {0U, 330U}, {46U, 220U}, {0U, 277U}, {0U, 370U}, {0U, 220U}, {0U, 277U}, {0U, 370U},
{62U, 208U}, {0U, 247U}, {0U, 330U}, {0U, 208U}, {0U, 247U}, {0U, 330U}, {0U, 220U}, {0U, 247U}, {0U, 311U}, {0U, 220U}, {0U, 247U}, {0U, 311U},
{82U, 208U}, {0U, 247U}, {0U, 330U}, {0U, 208U}, {0U, 247U}, {0U, 330U}, {0U, 208U}, {0U, 247U}, {0U, 330U}, {0U, 208U}, {0U, 247U}, {0U, 330U},
{82U, 196U}, {0U, 247U}, {0U, 330U}, {0U, 196U}, {0U, 247U}, {0U, 330U}, {0U, 196U}, {0U, 247U}, {0U, 330U}, {0U, 196U}, {0U, 247U}, {0U, 330U},
{73U, 196U}, {0U, 247U}, {0U, 349U}, {0U, 196U}, {0U, 247U}, {0U, 349U}, {0U, 196U}, {0U, 247U}, {0U, 349U}, {0U, 196U}, {0U, 247U}, {0U, 349U},
{65U, 196U}, {0U, 262U}, {0U, 330U}, {62U, 196U}, {0U, 247U}, {0U, 330U}, {58U, 196U}, {0U, 277U}, {0U, 330U}, {46U, 277U}, {0U, 330U}, {0U, 330U},
{62U, 185U}, {0U, 247U}, {0U, 294U}, {0U, 185U}, {0U, 247U}, {0U, 294U}, {0U, 196U}, {0U, 247U}, {0U, 277U}, {82U, 165U}, {0U, 247U}, {0U, 277U},
{46U, 185U}, {0U, 247U}, {0U, 294U}, {0U, 185U}, {0U, 233U}, {0U, 277U}, {0U, 185U}, {0U, 233U}, {0U, 277U}, {0U, 185U}, {0U, 233U}, {0U, 277U},
{62U, 123U}, {0U, 294U}, {0U, 370U}, {0U, 123U}, {0U, 294U}, {0U, 370U}, {0U, 123U}, {0U, 311U}, {0U, 370U}, {0U, 123U}, {0U, 311U}, {0U, 370U},
{62U, 123U}, {0U, 330U}, {0U, 392U}, {0U, 123U}, {0U, 330U}, {0U, 392U}, {0U, 123U}, {0U, 330U}, {0U, 392U}, {0U, 123U}, {0U, 330U}, {0U, 392U},
};
#define MOONLIGHT_STEP_COUNT (sizeof(moonlight_opening) / sizeof(moonlight_opening[0]))
// The upper melody enters in bars 5-16 after the familiar four-bar arpeggio.
static const moonlight_melody_note_t moonlight_melody[] = {
{57U, 415U, 800U}, {59U, 415U, 400U},
{60U, 415U, 3000U}, {69U, 415U, 800U},
{72U, 415U, 2200U}, {78U, 440U, 1600U},
{84U, 415U, 2000U}, {90U, 370U, 1000U}, {93U, 494U, 1200U},
{105U, 392U, 800U}, {107U, 392U, 400U},
{108U, 392U, 3000U}, {117U, 392U, 800U},
{120U, 392U, 2200U}, {126U, 370U, 1600U},
{132U, 370U, 2000U}, {138U, 392U, 1000U}, {141U, 330U, 1200U},
{144U, 370U, 2200U}, {150U, 349U, 1600U},
{156U, 349U, 2000U}, {162U, 330U, 1000U}, {165U, 349U, 1200U},
{168U, 349U, 2200U}, {174U, 330U, 1600U},
{180U, 330U, 3000U}, {189U, 330U, 800U},
};
#define MOONLIGHT_MELODY_COUNT (sizeof(moonlight_melody) / sizeof(moonlight_melody[0]))
#endif
// These labels match the NVS high-score table order in high_scores.h.
static const char *const game_names[HIGH_SCORE_GAME_COUNT] = {
"INVADERS", "TEMPEST", "ASTEROIDS", "YAHTZEE", "MISSILE COMMAND", "SCRAMBLE", "DEFENDER", "GALAGA",
"BREAKOUT", "CRAWLER", "MAZE CHASE", "SIMON",
};
static const char *const game_screenshot_labels[HIGH_SCORE_GAME_COUNT] = {
"hall-of-fame-invaders", "hall-of-fame-tempest", "hall-of-fame-asteroids",
"hall-of-fame-yahtzee", "hall-of-fame-missile-command", "hall-of-fame-scramble",
"hall-of-fame-defender", "hall-of-fame-galaga", "hall-of-fame-breakout",
"hall-of-fame-crawler", "hall-of-fame-maze-chase", "hall-of-fame-simon",
};
// Each score page recolours the same continuous flame simulation with a
// C64-palette ramp that matches that game's on-screen identity.
static const flame_palette_t game_flame_palettes[HIGH_SCORE_GAME_COUNT] = {
[HIGH_SCORE_INVADERS] = {C64_COLOR_BLUE, C64_COLOR_CYAN, C64_COLOR_GREEN,
C64_COLOR_YELLOW, C64_COLOR_WHITE},
[HIGH_SCORE_TEMPEST] = {C64_COLOR_BLUE, C64_COLOR_PURPLE, C64_COLOR_LIGHT_PURPLE,
C64_COLOR_CYAN, C64_COLOR_WHITE},
[HIGH_SCORE_ASTEROIDS] = {C64_COLOR_DARK_GREY, C64_COLOR_GREY, C64_COLOR_LIGHT_GREY,
C64_COLOR_CYAN, C64_COLOR_WHITE},
[HIGH_SCORE_YAHTZEE] = {C64_COLOR_BLUE, C64_COLOR_CYAN, C64_COLOR_LIGHT_GREEN,
C64_COLOR_LIGHT_GREY, C64_COLOR_WHITE},
[HIGH_SCORE_MISSILE_COMMAND] = {C64_COLOR_RED, C64_COLOR_RED, C64_COLOR_ORANGE,
C64_COLOR_YELLOW, C64_COLOR_WHITE},
[HIGH_SCORE_SCRAMBLE] = {C64_COLOR_ORANGE_BROWN, C64_COLOR_ORANGE, C64_COLOR_YELLOW,
C64_COLOR_LIGHT_GREEN, C64_COLOR_WHITE},
[HIGH_SCORE_DEFENDER] = {C64_COLOR_BLUE, C64_COLOR_PURPLE, C64_COLOR_LIGHT_PURPLE,
C64_COLOR_CYAN, C64_COLOR_WHITE},
[HIGH_SCORE_GALAGA] = {C64_COLOR_RED, C64_COLOR_SALMON, C64_COLOR_ORANGE,
C64_COLOR_YELLOW, C64_COLOR_WHITE},
[HIGH_SCORE_BREAKOUT] = {C64_COLOR_BLUE, C64_COLOR_CYAN, C64_COLOR_LIGHT_PURPLE,
C64_COLOR_LIGHT_GREEN, C64_COLOR_WHITE},
[HIGH_SCORE_CRAWLER] = {C64_COLOR_GREEN, C64_COLOR_LIGHT_GREEN, C64_COLOR_YELLOW,
C64_COLOR_CYAN, C64_COLOR_WHITE},
[HIGH_SCORE_MAZE_CHASE] = {C64_COLOR_BLUE, C64_COLOR_CYAN, C64_COLOR_PURPLE,
C64_COLOR_YELLOW, C64_COLOR_WHITE},
[HIGH_SCORE_SIMON] = {C64_COLOR_DARK_GREY, C64_COLOR_GREEN, C64_COLOR_CYAN,
C64_COLOR_YELLOW, C64_COLOR_WHITE},
};
static high_score_table_t tables[HIGH_SCORE_GAME_COUNT];
static high_score_result_t table_status[HIGH_SCORE_GAME_COUNT];
static unsigned selected_game;
static uint8_t top_flame_heat[FLAME_CELL_COUNT], bottom_flame_heat[FLAME_CELL_COUNT];
static uint8_t top_left_side_flame_heat[SIDE_FLAME_CELL_COUNT];
static uint8_t top_right_side_flame_heat[SIDE_FLAME_CELL_COUNT];
static uint8_t bottom_left_side_flame_heat[SIDE_FLAME_CELL_COUNT];
static uint8_t bottom_right_side_flame_heat[SIDE_FLAME_CELL_COUNT];
static flame_effect_t top_flame, bottom_flame;
static flame_effect_t top_left_side_flame, top_right_side_flame;
static flame_effect_t bottom_left_side_flame, bottom_right_side_flame;
static starfield_t hall_stars;
static uint32_t last_page_change, last_flame_update;
static bool running, controls_ready;
#if 0
static void playMoonlightMelody(unsigned step)
{
for (unsigned note = 0U; note < MOONLIGHT_MELODY_COUNT; ++note) {
if (moonlight_melody[note].step == step) {
audioPlayMusicNote(moonlight_melody[note].frequency_hz,
moonlight_melody[note].duration_ms);
}
}
}
static void playMoonlightStep(void)
{
const moonlight_step_t *step = &moonlight_opening[moonlight_step];
if (step->bass_hz != 0U) audioPlayMusicNote(step->bass_hz, MOONLIGHT_BAR_MS);
audioPlayMusicNote(step->arpeggio_hz, MOONLIGHT_STEP_MS - 30U);
playMoonlightMelody(moonlight_step);
moonlight_step = (moonlight_step + 1U) % MOONLIGHT_STEP_COUNT;
}
#endif
void startHallOfFame(uint32_t now_ms)
{
for (unsigned game = 0; game < HIGH_SCORE_GAME_COUNT; ++game) {
table_status[game] = high_scores_load((high_score_game_t)game, &tables[game]);
}
selected_game = 0;
// Stars stay inside the black score area, clear of the flame borders.
starfield_initialize(&hall_stars, HALL_STAR_COUNT, HALL_STAR_LEFT, HALL_STAR_TOP,
HALL_STAR_WIDTH, HALL_STAR_HEIGHT, STARFIELD_GREYSCALE);
flameEffectInitialize(&top_flame, top_flame_heat, FLAME_COLUMNS, FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x13579BDFU);
flameEffectInitialize(&bottom_flame, bottom_flame_heat, FLAME_COLUMNS, FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x2468ACE1U);
flameEffectInitialize(&top_left_side_flame, top_left_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x3456789AU);
flameEffectInitialize(&top_right_side_flame, top_right_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, false, now_ms ^ 0x456789ABU);
flameEffectInitialize(&bottom_left_side_flame, bottom_left_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x56789ABCU);
flameEffectInitialize(&bottom_right_side_flame, bottom_right_side_flame_heat,
SIDE_FLAME_COLUMNS, SIDE_FLAME_ROWS,
FLAME_PIXEL_WIDTH, FLAME_PIXEL_HEIGHT, true, now_ms ^ 0x6789ABCDU);
last_page_change = last_flame_update = now_ms;
startMoonlight(now_ms);
controls_ready = false;
running = true;
}
void stopHallOfFame(void)
{
stopMoonlight();
running = false;
}
void updateHallOfFame(int x_offset, int y_offset, bool valid, bool left, bool right,
uint32_t now_ms)
{
if (!running) return;
updateMoonlight(now_ms);
if (now_ms - last_flame_update >= FLAME_FRAME_MS) {
const uint32_t elapsed = now_ms - last_flame_update;
last_flame_update = now_ms;
starfield_update(&hall_stars, elapsed);
flameEffectUpdate(&top_flame);
flameEffectUpdate(&bottom_flame);
const uint8_t *top_flame_edge = top_flame_heat +
(FLAME_ROWS - 1U) * FLAME_COLUMNS;
const uint8_t *bottom_flame_source = bottom_flame_heat +
(FLAME_ROWS - 1U) * FLAME_COLUMNS;
flameEffectUpdateFromSource(&top_left_side_flame, top_flame_edge);
flameEffectUpdateFromSource(&top_right_side_flame,
top_flame_edge + FLAME_COLUMNS - SIDE_FLAME_COLUMNS);
flameEffectUpdateFromSource(&bottom_left_side_flame, bottom_flame_source);
flameEffectUpdateFromSource(&bottom_right_side_flame,
bottom_flame_source + FLAME_COLUMNS - SIDE_FLAME_COLUMNS);
}
if (!valid) {
controls_ready = false;
return;
}
if (x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350) {
controls_ready = true;
}
int direction = 0;
if (left != right) direction = right ? 1 : -1;
else if (controls_ready && (x_offset > 600 || x_offset < -600))
direction = x_offset > 0 ? 1 : -1;
if (direction == 0 || now_ms - last_page_change < PAGE_CHANGE_MS) return;
selected_game = (selected_game + HIGH_SCORE_GAME_COUNT + direction) % HIGH_SCORE_GAME_COUNT;
last_page_change = now_ms;
}
void drawHallOfFame(uint16_t *frame)
{
if (!running || frame == NULL) return;
const flame_palette_t *palette = &game_flame_palettes[selected_game];
clear(frame, COLOR_BLACK);
starfield_draw(frame, &hall_stars);
flameEffectDraw(frame, &top_flame, 0, TOP_FLAME_Y, palette);
flameEffectDraw(frame, &bottom_flame, 0, BOTTOM_FLAME_Y, palette);
flameEffectDraw(frame, &top_left_side_flame, 0, TOP_SIDE_FLAME_Y, palette);
flameEffectDraw(frame, &top_right_side_flame, RIGHT_SIDE_FLAME_X, TOP_SIDE_FLAME_Y,
palette);
flameEffectDraw(frame, &bottom_left_side_flame, 0, BOTTOM_SIDE_FLAME_Y, palette);
flameEffectDraw(frame, &bottom_right_side_flame, RIGHT_SIDE_FLAME_X,
BOTTOM_SIDE_FLAME_Y, palette);
drawPet2001TextCentredTransparent(frame, 160, 4, "HALL OF FAME", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 37, game_names[selected_game], COLOR_CYAN, 2);
if (table_status[selected_game] != HIGH_SCORES_OK) {
drawPet2001TextCentredTransparent(frame, 160, 83, "SCORES UNAVAILABLE", COLOR_RED, 1);
} else {
const high_score_table_t *table = &tables[selected_game];
for (unsigned rank = 0; rank < HIGH_SCORE_COUNT; ++rank) {
char line[36];
if (rank < table->count) {
snprintf(line, sizeof(line), "%u. %-16s %u", rank + 1U,
table->entries[rank].name, (unsigned)table->entries[rank].score);
} else {
snprintf(line, sizeof(line), "%u. ---", rank + 1U);
}
const uint16_t colour = rank == 0 ? COLOR_YELLOW :
rank == 1 ? COLOR_LIGHTGRAY : COLOR_ORANGE;
drawPet2001TextCentredTransparent(frame, 160, 62 + (int)rank * 16, line, colour, 1);
}
}
drawPet2001TextCentredTransparent(frame, 160, 160, "D/B OR STICK GAME BOOT BACK",
COLOR_LIGHTGRAY, 1);
}
const char *hallOfFameScreenshotLabel(void)
{
return selected_game < HIGH_SCORE_GAME_COUNT ? game_screenshot_labels[selected_game] :
"hall-of-fame";
}