Galaga
Source: src/galaga.c · 368 lines · 19 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
clampf() · source line 65
static float clampf(float value, float low, float high)
- Exit this function
return value < low ? low : value > high ? high : value;
Read original function source
static float clampf(float value, float low, float high)
{
return value < low ? low : value > high ? high : value;
}enemyColour() · source line 70
static uint16_t enemyColour(EnemyType type, EnemyMotion motion)
- Decision
motion == DIVINGTrue- Exit this function
return type == BOSS ? COLOR_RED : type == BUTTERFLY ? COLOR_LIME : COLOR_ORANGE;
FalseContinue after this decision.
- Exit this function
return type == BOSS ? COLOR_MAGENTA : type == BUTTERFLY ? COLOR_CYAN : COLOR_YELLOW;
Read original function source
static uint16_t enemyColour(EnemyType type, EnemyMotion motion)
{
if (motion == DIVING) {
return type == BOSS ? COLOR_RED : type == BUTTERFLY ? COLOR_LIME : COLOR_ORANGE;
}
return type == BOSS ? COLOR_MAGENTA : type == BUTTERFLY ? COLOR_CYAN : COLOR_YELLOW;
}enemyBitmap() · source line 78
static const uint8_t *enemyBitmap(EnemyType type)
- Exit this function
return type == BOSS ? galaga_boss_bitmap : type == BUTTERFLY ? galaga_butterfly_bitmap : galaga_bee_bitmap;
Read original function source
static const uint8_t *enemyBitmap(EnemyType type)
{
return type == BOSS ? galaga_boss_bitmap : type == BUTTERFLY ? galaga_butterfly_bitmap : galaga_bee_bitmap;
}enemyPoints() · source line 83
static unsigned enemyPoints(EnemyType type, bool diving)
- Execute
const unsigned base = type == BOSS ? 100U : type == BUTTERFLY ? 80U : 50U; - Exit this function
return diving ? base * 2U : base;
Read original function source
static unsigned enemyPoints(EnemyType type, bool diving)
{
const unsigned base = type == BOSS ? 100U : type == BUTTERFLY ? 80U : 50U;
return diving ? base * 2U : base;
}addScore() · source line 89
static void addScore(unsigned points)
- Execute
score = score > UINT32_MAX - points ? UINT32_MAX : score + points;
Read original function source
static void addScore(unsigned points)
{
score = score > UINT32_MAX - points ? UINT32_MAX : score + points;
}prepareWave() · source line 94
static void prepareWave(void)
- Execute
challenge_stage = wave > 0 && wave % 3U == 0; - Execute
memset(player_shots, 0, sizeof(player_shots)); - Execute
memset(enemy_shots, 0, sizeof(enemy_shots)); - Execute
unsigned index = 0; - FOR loop
unsigned row = 0; row < sizeof(formation_count); ++rowRepeat body while the loop condition permits; then continue below.
- Execute
const unsigned count = formation_count[row]; - Execute
const float spacing = 22.0f; - Execute
const float first_x = (LCD_WIDTH - (16.0f + (count - 1U) * spacing)) / 2.0f; - FOR loop
unsigned column = 0; column < count; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
EnemyType type = row == 0 ? BOSS : row < 3 ? BUTTERFLY : BEE; - Execute
const float home_x = first_x + column * spacing; - Execute
const float home_y = 29.0f + row * 18.0f; - Execute
const unsigned group = index / 4U; - Execute
enemies[index] = (Enemy){home_x, home_y, 0.0f, -16.0f, -(float)group * 0.18f, type, ENTERING, true}; - Execute
++index;
- Execute
dive_delay = 1000U; - Execute
enemy_fire_delay = 800U; - Execute
banner_until = last_update + 1200U;
Read original function source
static void prepareWave(void)
{
challenge_stage = wave > 0 && wave % 3U == 0;
memset(player_shots, 0, sizeof(player_shots));
memset(enemy_shots, 0, sizeof(enemy_shots));
unsigned index = 0;
for (unsigned row = 0; row < sizeof(formation_count); ++row) {
const unsigned count = formation_count[row];
const float spacing = 22.0f;
const float first_x = (LCD_WIDTH - (16.0f + (count - 1U) * spacing)) / 2.0f;
for (unsigned column = 0; column < count; ++column) {
EnemyType type = row == 0 ? BOSS : row < 3 ? BUTTERFLY : BEE;
const float home_x = first_x + column * spacing;
const float home_y = 29.0f + row * 18.0f;
// Galaga enemies arrive in staggered groups along long, curved paths
// before locking into their assigned formation position.
const unsigned group = index / 4U;
enemies[index] = (Enemy){home_x, home_y, 0.0f, -16.0f,
-(float)group * 0.18f, type, ENTERING, true};
++index;
}
}
dive_delay = 1000U;
enemy_fire_delay = 800U;
banner_until = last_update + 1200U;
}beginGame() · source line 121
static void beginGame(uint32_t now_ms)
Calls into this documentation:
- Execute
score = 0; - Execute
lives = 3; - Execute
wave = 1; - Execute
controls_ready = false; - Execute
player = (Object){152.0f, PLAYER_Y, 0.0f, 0.0f, true}; - Execute
fire_delay = 0; - Execute
accumulator = 0; - Execute
last_update = now_ms; - Execute
prepareWave(); - Execute
state = PLAYING;
Read original function source
static void beginGame(uint32_t now_ms)
{
score = 0; lives = 3; wave = 1; controls_ready = false;
player = (Object){152.0f, PLAYER_Y, 0.0f, 0.0f, true};
fire_delay = 0; accumulator = 0; last_update = now_ms;
prepareWave();
state = PLAYING;
}firePlayerShot() · source line 130
static void firePlayerShot(void)
Calls into this documentation:
- Decision
!player.active || fire_delay != 0True- Exit this function
return;
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_PLAYER_SHOTS; ++iRepeat body while the loop condition permits; then continue below.
- Decision
!player_shots[i].activeTrue- Execute
player_shots[i] = (Object){player.x + PLAYER_W / 2.0f - 1.0f, PLAYER_Y - 2.0f, 0.0f, -245.0f, true}; - Execute
fire_delay = FIRE_DELAY_MS; - Execute
audioPlay(AUDIO_SFX_PLAYER_SHOT); - Exit this function
return;
FalseContinue after this decision.
Read original function source
static void firePlayerShot(void)
{
if (!player.active || fire_delay != 0) return;
for (unsigned i = 0; i < MAX_PLAYER_SHOTS; ++i) if (!player_shots[i].active) {
player_shots[i] = (Object){player.x + PLAYER_W / 2.0f - 1.0f, PLAYER_Y - 2.0f, 0.0f, -245.0f, true};
fire_delay = FIRE_DELAY_MS;
audioPlay(AUDIO_SFX_PLAYER_SHOT);
return;
}
}fireEnemyShot() · source line 141
static void fireEnemyShot(void)
Calls into this documentation:
- Decision
enemy_fire_delay != 0 || challenge_stageTrue- Exit this function
return;
FalseContinue after this decision.
- FOR loop
unsigned attempts = 0; attempts < MAX_ENEMIES; ++attemptsRepeat body while the loop condition permits; then continue below.
- Execute
Enemy *enemy = &enemies[esp_random() % MAX_ENEMIES]; - Decision
!enemy->active || enemy->motion != FORMATIONTrue- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_ENEMY_SHOTS; ++iRepeat body while the loop condition permits; then continue below.
- Decision
!enemy_shots[i].activeTrue- Execute
const float dx = player.x + PLAYER_W / 2.0f - enemy->x; - Execute
enemy_shots[i] = (Object){enemy->x + 7.0f, enemy->y + 13.0f, clampf(dx * 0.45f, -80.0f, 80.0f), 115.0f + wave * 5.0f, true}; - Execute
enemy_fire_delay = ENEMY_FIRE_MS > wave * 45U ? ENEMY_FIRE_MS - wave * 45U : 260U; - Execute
audioPlay(AUDIO_SFX_ENEMY_SHOT); - Exit this function
return;
FalseContinue after this decision.
Read original function source
static void fireEnemyShot(void)
{
if (enemy_fire_delay != 0 || challenge_stage) return;
for (unsigned attempts = 0; attempts < MAX_ENEMIES; ++attempts) {
Enemy *enemy = &enemies[esp_random() % MAX_ENEMIES];
if (!enemy->active || enemy->motion != FORMATION) continue;
for (unsigned i = 0; i < MAX_ENEMY_SHOTS; ++i) if (!enemy_shots[i].active) {
const float dx = player.x + PLAYER_W / 2.0f - enemy->x;
enemy_shots[i] = (Object){enemy->x + 7.0f, enemy->y + 13.0f, clampf(dx * 0.45f, -80.0f, 80.0f), 115.0f + wave * 5.0f, true};
enemy_fire_delay = ENEMY_FIRE_MS > wave * 45U ? ENEMY_FIRE_MS - wave * 45U : 260U;
audioPlay(AUDIO_SFX_ENEMY_SHOT);
return;
}
}
}startDive() · source line 157
static void startDive(void)
- Decision
challenge_stage || dive_delay != 0True- Exit this function
return;
FalseContinue after this decision.
- FOR loop
unsigned attempts = 0; attempts < MAX_ENEMIES; ++attemptsRepeat body while the loop condition permits; then continue below.
- Execute
Enemy *enemy = &enemies[esp_random() % MAX_ENEMIES]; - Decision
!enemy->active || enemy->motion != FORMATIONTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
enemy->motion = DIVING; - Execute
enemy->dive_phase = 0.0f; - Execute
dive_delay = DIVE_DELAY_MS > wave * 55U ? DIVE_DELAY_MS - wave * 55U : 360U; - Exit this function
return;
Read original function source
static void startDive(void)
{
if (challenge_stage || dive_delay != 0) return;
for (unsigned attempts = 0; attempts < MAX_ENEMIES; ++attempts) {
Enemy *enemy = &enemies[esp_random() % MAX_ENEMIES];
if (!enemy->active || enemy->motion != FORMATION) continue;
enemy->motion = DIVING;
enemy->dive_phase = 0.0f;
dive_delay = DIVE_DELAY_MS > wave * 55U ? DIVE_DELAY_MS - wave * 55U : 360U;
return;
}
}hitPlayer() · source line 170
static void hitPlayer(void)
Calls into this documentation:
- Decision
!player.activeTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION); - Decision
--lives == 0True- Execute
player.active = false; - Execute
state = FINISHED; - Execute
state_started = last_update; - Exit this function
return;
FalseContinue after this decision.
- Execute
player.active = false; - Execute
state_started = last_update; - Execute
memset(player_shots, 0, sizeof(player_shots)); - Execute
memset(enemy_shots, 0, sizeof(enemy_shots));
Read original function source
static void hitPlayer(void)
{
if (!player.active) return;
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION);
if (--lives == 0) {
player.active = false;
state = FINISHED;
state_started = last_update;
return;
}
player.active = false;
state_started = last_update;
memset(player_shots, 0, sizeof(player_shots));
memset(enemy_shots, 0, sizeof(enemy_shots));
}enemiesRemaining() · source line 186
static unsigned enemiesRemaining(void)
- Execute
unsigned count = 0; - FOR loop
unsigned i = 0; i < MAX_ENEMIES; ++iRepeat body while the loop condition permits; then continue below.
- Decision
enemies[i].activeTrue- Execute
++count;
FalseContinue after this decision.
- Exit this function
return count;
Read original function source
static unsigned enemiesRemaining(void)
{
unsigned count = 0;
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].active) ++count;
return count;
}updateEnemies() · source line 193
static void updateEnemies(void)
Calls into this documentation:
- FOR loop
unsigned i = 0; i < MAX_ENEMIES; ++iRepeat body while the loop condition permits; then continue below.
- Execute
Enemy *enemy = &enemies[i]; - Decision
!enemy->activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
enemy->motion == ENTERINGTrue- Execute
enemy->dive_phase += STEP_MS / 1350.0f; - Decision
enemy->dive_phase < 0.0fTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float t = enemy->dive_phase; - Decision
t >= 1.0fTrue- Execute
enemy->x = enemy->home_x; - Execute
enemy->y = enemy->home_y; - Execute
enemy->motion = FORMATION; - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float curve = sinf(PI * t); - Execute
const float start_x = ((unsigned)(enemy - enemies) / 4U & 1U) == 0U ? -18.0f : (float)LCD_WIDTH + 2.0f; - Execute
const float direction = start_x < 0.0f ? 1.0f : -1.0f; - Execute
enemy->x = start_x + (enemy->home_x - start_x) * t + direction * curve * 42.0f; - Execute
enemy->y = -16.0f + (enemy->home_y + 16.0f) * t + curve * 36.0f; - Next loop iteration
continue;
FalseContinue after this decision.
- Decision
enemy->motion != DIVINGTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
enemy->dive_phase += STEP_MS / 1800.0f; - Decision
enemy->dive_phase >= 1.0fTrue- Execute
enemy->motion = FORMATION; - Execute
enemy->x = enemy->home_x; - Execute
enemy->y = enemy->home_y; - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float curve = sinf(PI * enemy->dive_phase); - Execute
const float direction = ((unsigned)(enemy - enemies) & 1U) == 0U ? -1.0f : 1.0f; - Execute
const float depth = (float)(PLAYER_Y - 16) - enemy->home_y; - Execute
enemy->x = enemy->home_x + direction * (curve * 88.0f + sinf(PI * enemy->dive_phase * 2.0f) * 28.0f); - Execute
enemy->y = enemy->home_y + curve * depth; - Decision
player.active && fabsf(enemy->x + 8.0f - (player.x + PLAYER_W / 2.0f)) < 12.0f && fabsf(enemy->y + 8.0f - (PLAYER_Y + PLAYER_H / 2.0f)) < 10.0fTrue- Execute
hitPlayer();
FalseContinue after this decision.
Read original function source
static void updateEnemies(void)
{
for (unsigned i = 0; i < MAX_ENEMIES; ++i) {
Enemy *enemy = &enemies[i];
if (!enemy->active) continue;
if (enemy->motion == ENTERING) {
enemy->dive_phase += STEP_MS / 1350.0f;
if (enemy->dive_phase < 0.0f) continue;
const float t = enemy->dive_phase;
if (t >= 1.0f) {
enemy->x = enemy->home_x;
enemy->y = enemy->home_y;
enemy->motion = FORMATION;
continue;
}
const float curve = sinf(PI * t);
const float start_x = ((unsigned)(enemy - enemies) / 4U & 1U) == 0U ? -18.0f : (float)LCD_WIDTH + 2.0f;
const float direction = start_x < 0.0f ? 1.0f : -1.0f;
enemy->x = start_x + (enemy->home_x - start_x) * t + direction * curve * 42.0f;
enemy->y = -16.0f + (enemy->home_y + 16.0f) * t + curve * 36.0f;
continue;
}
if (enemy->motion != DIVING) continue;
enemy->dive_phase += STEP_MS / 1800.0f;
if (enemy->dive_phase >= 1.0f) {
enemy->motion = FORMATION;
enemy->x = enemy->home_x;
enemy->y = enemy->home_y;
continue;
}
const float curve = sinf(PI * enemy->dive_phase);
const float direction = ((unsigned)(enemy - enemies) & 1U) == 0U ? -1.0f : 1.0f;
const float depth = (float)(PLAYER_Y - 16) - enemy->home_y;
enemy->x = enemy->home_x + direction * (curve * 88.0f + sinf(PI * enemy->dive_phase * 2.0f) * 28.0f);
enemy->y = enemy->home_y + curve * depth;
if (player.active && fabsf(enemy->x + 8.0f - (player.x + PLAYER_W / 2.0f)) < 12.0f &&
fabsf(enemy->y + 8.0f - (PLAYER_Y + PLAYER_H / 2.0f)) < 10.0f) hitPlayer();
}
}stepGame() · source line 233
static void stepGame(float input_x, bool fire)
Calls into this documentation:
- Execute
fire_delay = fire_delay > STEP_MS ? fire_delay - STEP_MS : 0; - Execute
enemy_fire_delay = enemy_fire_delay > STEP_MS ? enemy_fire_delay - STEP_MS : 0; - Execute
dive_delay = dive_delay > STEP_MS ? dive_delay - STEP_MS : 0; - Decision
!player.activeTrue- Decision
last_update - state_started >= RESPAWN_MSTrue- Execute
player = (Object){152.0f, PLAYER_Y, 0.0f, 0.0f, true};
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
player.vx += input_x * 1000.0f * STEP_MS / 1000.0f; - Execute
player.vx *= 0.92f; - Execute
player.vx = clampf(player.vx, -140.0f, 140.0f); - Execute
player.x = clampf(player.x + player.vx * STEP_MS / 1000.0f, 4.0f, LCD_WIDTH - PLAYER_W - 4.0f); - Decision
fireTrue- Execute
firePlayerShot();
FalseContinue after this decision.
- Execute
startDive(); - Execute
fireEnemyShot(); - Execute
updateEnemies(); - FOR loop
unsigned s = 0; s < MAX_PLAYER_SHOTS; ++sRepeat body while the loop condition permits; then continue below.
- Execute
Object *shot = &player_shots[s]; - Decision
!shot->activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
shot->y += shot->vy * STEP_MS / 1000.0f; - Decision
shot->y < PLAY_TOPTrue- Execute
shot->active = false; - Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
unsigned e = 0; e < MAX_ENEMIES; ++eRepeat body while the loop condition permits; then continue below.
- Execute
Enemy *enemy = &enemies[e]; - Decision
!enemy->active || fabsf(shot->x - (enemy->x + 7.0f)) >= 9.0f || fabsf(shot->y - (enemy->y + 7.0f)) >= 10.0fTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
addScore(enemyPoints(enemy->type, enemy->motion == DIVING)); - Execute
enemy->active = false; - Execute
shot->active = false; - Execute
audioPlay(AUDIO_SFX_HIT); - Exit enclosing loop or switch
break;
- FOR loop
unsigned s = 0; s < MAX_ENEMY_SHOTS; ++sRepeat body while the loop condition permits; then continue below.
- Execute
Object *shot = &enemy_shots[s]; - Decision
!shot->activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
shot->x += shot->vx * STEP_MS / 1000.0f; - Execute
shot->y += shot->vy * STEP_MS / 1000.0f; - Decision
shot->y > LCD_HEIGHTTrue- Execute
shot->active = false; - Next loop iteration
continue;
FalseContinue after this decision.
- Decision
fabsf(shot->x - (player.x + PLAYER_W / 2.0f)) < 10.0f && fabsf(shot->y - (PLAYER_Y + 5.0f)) < 9.0fTrue- Execute
shot->active = false; - Execute
hitPlayer();
FalseContinue after this decision.
- Decision
enemiesRemaining() == 0True- Execute
audioPlay(AUDIO_SFX_WAVE_CLEAR); - Execute
++wave; - Execute
prepareWave();
FalseContinue after this decision.
Read original function source
static void stepGame(float input_x, bool fire)
{
fire_delay = fire_delay > STEP_MS ? fire_delay - STEP_MS : 0;
enemy_fire_delay = enemy_fire_delay > STEP_MS ? enemy_fire_delay - STEP_MS : 0;
dive_delay = dive_delay > STEP_MS ? dive_delay - STEP_MS : 0;
if (!player.active) {
if (last_update - state_started >= RESPAWN_MS) player = (Object){152.0f, PLAYER_Y, 0.0f, 0.0f, true};
return;
}
player.vx += input_x * 1000.0f * STEP_MS / 1000.0f;
player.vx *= 0.92f;
player.vx = clampf(player.vx, -140.0f, 140.0f);
player.x = clampf(player.x + player.vx * STEP_MS / 1000.0f, 4.0f, LCD_WIDTH - PLAYER_W - 4.0f);
if (fire) firePlayerShot();
startDive();
fireEnemyShot();
updateEnemies();
for (unsigned s = 0; s < MAX_PLAYER_SHOTS; ++s) {
Object *shot = &player_shots[s];
if (!shot->active) continue;
shot->y += shot->vy * STEP_MS / 1000.0f;
if (shot->y < PLAY_TOP) { shot->active = false; continue; }
for (unsigned e = 0; e < MAX_ENEMIES; ++e) {
Enemy *enemy = &enemies[e];
if (!enemy->active || fabsf(shot->x - (enemy->x + 7.0f)) >= 9.0f || fabsf(shot->y - (enemy->y + 7.0f)) >= 10.0f) continue;
addScore(enemyPoints(enemy->type, enemy->motion == DIVING));
enemy->active = false;
shot->active = false;
audioPlay(AUDIO_SFX_HIT);
break;
}
}
for (unsigned s = 0; s < MAX_ENEMY_SHOTS; ++s) {
Object *shot = &enemy_shots[s];
if (!shot->active) continue;
shot->x += shot->vx * STEP_MS / 1000.0f;
shot->y += shot->vy * STEP_MS / 1000.0f;
if (shot->y > LCD_HEIGHT) { shot->active = false; continue; }
if (fabsf(shot->x - (player.x + PLAYER_W / 2.0f)) < 10.0f && fabsf(shot->y - (PLAYER_Y + 5.0f)) < 9.0f) {
shot->active = false;
hitPlayer();
}
}
if (enemiesRemaining() == 0) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
++wave;
prepareWave();
}
}startGalaga() · source line 284
void startGalaga(uint32_t now_ms)
Calls into this documentation:
- Execute
high_score_table_t scores; - Execute
high_score = high_scores_load(HIGH_SCORE_GALAGA, &scores) == HIGH_SCORES_OK && scores.count != 0 ? scores.entries[0].score : 0; - Execute
starfield_initialize(&stars, 80, 0, 0, LCD_WIDTH, LCD_HEIGHT, STARFIELD_COLOUR); - Execute
running = true; - Execute
beginGame(now_ms); - Execute
state = READY;
Read original function source
void startGalaga(uint32_t now_ms)
{
high_score_table_t scores;
high_score = high_scores_load(HIGH_SCORE_GALAGA, &scores) == HIGH_SCORES_OK && scores.count != 0 ? scores.entries[0].score : 0;
starfield_initialize(&stars, 80, 0, 0, LCD_WIDTH, LCD_HEIGHT, STARFIELD_COLOUR);
running = true;
beginGame(now_ms);
state = READY;
}stopGalaga() · source line 293
void stopGalaga(void)
Calls into this documentation:
- Execute
cancelHighScoreEntry(); - Execute
running = false;
Read original function source
void stopGalaga(void) { cancelHighScoreEntry(); running = false; }galagaAction() · source line 294
void galagaAction(bool start, uint32_t now_ms)
Calls into this documentation:
- Decision
running && start && state == READYTrue- Execute
beginGame(now_ms); - Execute
audioPlay(AUDIO_SFX_GAME_START);
FalseContinue after this decision.
Read original function source
void galagaAction(bool start, uint32_t now_ms)
{
if (running && start && state == READY) {
beginGame(now_ms);
audioPlay(AUDIO_SFX_GAME_START);
}
}updateGalaga() · source line 302
void updateGalaga(int x_offset, int y_offset, bool joystick_valid, bool fire, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed = now_ms - last_update; - Execute
last_update = now_ms; - Decision
state == FINISHED && now_ms - state_started >= 1000UTrue- Execute
startHighScoreEntry(HIGH_SCORE_GALAGA, score, now_ms); - Execute
state = SCORES;
FalseContinue after this decision.
- Decision
state == SCORESTrue- Decision
updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)True- Execute
state = READY;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
starfield_update(&stars, elapsed); - Decision
state != PLAYINGTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
!joystick_validTrue- Execute
controls_ready = false;
False- Decision
x_offset >= -350 && x_offset <= 350True- Execute
controls_ready = true;
FalseContinue after this decision.
- Execute
float input_x = joystick_valid && controls_ready ? clampf((float)x_offset / 1500.0f, -1.0f, 1.0f) : 0.0f; - Execute
accumulator += elapsed > 100U ? 100U : elapsed; - WHILE loop
accumulator >= STEP_MS && state == PLAYINGRepeat body while the loop condition permits; then continue below.
- Execute
accumulator -= STEP_MS; - Execute
stepGame(input_x, fire);
Read original function source
void updateGalaga(int x_offset, int y_offset, bool joystick_valid, bool fire, uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - last_update;
last_update = now_ms;
if (state == FINISHED && now_ms - state_started >= 1000U) {
startHighScoreEntry(HIGH_SCORE_GALAGA, score, now_ms);
state = SCORES;
}
if (state == SCORES) { if (updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)) state = READY; return; }
starfield_update(&stars, elapsed);
if (state != PLAYING) return;
if (!joystick_valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350) controls_ready = true;
float input_x = joystick_valid && controls_ready ? clampf((float)x_offset / 1500.0f, -1.0f, 1.0f) : 0.0f;
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == PLAYING) { accumulator -= STEP_MS; stepGame(input_x, fire); }
}drawGalaga() · source line 321
void drawGalaga(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == SCORESTrue- Execute
drawHighScoreEntry(frame); - Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
starfield_draw(frame, &stars); - FOR loop
unsigned i = 0; i < MAX_ENEMIES; ++iRepeat body while the loop condition permits; then continue below.
- Decision
enemies[i].activeTrue- Execute
const Enemy *enemy = &enemies[i]; - Execute
draw_bitmap_scaled(frame, (int)enemy->x, (int)enemy->y, enemyBitmap(enemy->type), 8, 8, 16, 14, enemyColour(enemy->type, enemy->motion));
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_PLAYER_SHOTS; ++iRepeat body while the loop condition permits; then continue below.
- Decision
player_shots[i].activeTrue- Execute
fill_rect(frame, (int)player_shots[i].x, (int)player_shots[i].y, 2, 8, COLOR_WHITE); - Execute
fill_rect(frame, (int)player_shots[i].x - 1, (int)player_shots[i].y + 2, 4, 3, COLOR_CYAN);
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_ENEMY_SHOTS; ++iRepeat body while the loop condition permits; then continue below.
- Decision
enemy_shots[i].activeTrue- Execute
fill_rect(frame, (int)enemy_shots[i].x, (int)enemy_shots[i].y, 2, 5, COLOR_RED);
FalseContinue after this decision.
- Decision
player.activeTrue- Execute
draw_bitmap_scaled(frame, (int)player.x, PLAYER_Y, galaga_player_bitmap, 8, 8, PLAYER_W, PLAYER_H, COLOR_CYAN);
FalseContinue after this decision.
- FOR loop
unsigned i = 1; i < lives; ++iRepeat body while the loop condition permits; then continue below.
- Execute
draw_bitmap_scaled(frame, 6 + (int)(i - 1U) * 20, LCD_HEIGHT - 13, galaga_player_bitmap, 8, 8, 16, 12, COLOR_CYAN);
- Execute
fill_rect(frame, 0, 0, LCD_WIDTH, PLAY_TOP, COLOR_BLACK); - Execute
char text[32]; - Execute
draw_text(frame, 52, 1, "1UP", 1, COLOR_RED); - Execute
snprintf(text, sizeof(text), "%u", (unsigned)score); - Execute
draw_text_centred(frame, 68, 9, text, 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 190, 1, "HIGH SCORE", 1, COLOR_RED); - Execute
snprintf(text, sizeof(text), "%u", (unsigned)high_score); - Execute
draw_text_centred(frame, 190, 9, text, 1, COLOR_WHITE); - Decision
state == READYTrue- Execute
clear(frame, COLOR_BLACK); - Execute
starfield_draw(frame, &stars); - Execute
draw_text_centred(frame, 160, 14, "-- GALAGA --", 3, COLOR_CYAN); - Execute
draw_text_centred(frame, 160, 49, "SCORE", 1, COLOR_WHITE); - Execute
draw_bitmap_scaled(frame, 82, 63, galaga_boss_bitmap, 8, 8, 16, 14, COLOR_MAGENTA); - Execute
draw_text(frame, 148, 66, "100", 1, COLOR_MAGENTA); - Execute
draw_text(frame, 214, 66, "200", 1, COLOR_RED); - Execute
draw_bitmap_scaled(frame, 82, 81, galaga_butterfly_bitmap, 8, 8, 16, 14, COLOR_CYAN); - Execute
draw_text(frame, 148, 84, "80", 1, COLOR_CYAN); - Execute
draw_text(frame, 214, 84, "160", 1, COLOR_LIME); - Execute
draw_bitmap_scaled(frame, 82, 99, galaga_bee_bitmap, 8, 8, 16, 14, COLOR_YELLOW); - Execute
draw_text(frame, 148, 102, "50", 1, COLOR_YELLOW); - Execute
draw_text(frame, 214, 102, "100", 1, COLOR_ORANGE); - Execute
draw_text_centred(frame, 160, 124, "JOYSTICK MOVE A FIRE", 1, COLOR_CYAN); - Execute
draw_text_centred(frame, 160, 145, "BOOT BACK", 1, COLOR_LIGHTGRAY);
False- Decision
state == FINISHEDTrue- Execute
draw_text_centred(frame, 160, 78, "GAME OVER", 2, COLOR_RED);
False- Decision
last_update < banner_untilTrue- Execute
draw_text_centred(frame, 160, 23, challenge_stage ? "CHALLENGING STAGE" : "READY", 1, challenge_stage ? COLOR_YELLOW : COLOR_WHITE);
FalseContinue after this decision.
Read original function source
void drawGalaga(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == SCORES) { drawHighScoreEntry(frame); return; }
clear(frame, COLOR_BLACK);
starfield_draw(frame, &stars);
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].active) {
const Enemy *enemy = &enemies[i];
draw_bitmap_scaled(frame, (int)enemy->x, (int)enemy->y, enemyBitmap(enemy->type), 8, 8, 16, 14,
enemyColour(enemy->type, enemy->motion));
}
for (unsigned i = 0; i < MAX_PLAYER_SHOTS; ++i) if (player_shots[i].active) {
fill_rect(frame, (int)player_shots[i].x, (int)player_shots[i].y, 2, 8, COLOR_WHITE);
fill_rect(frame, (int)player_shots[i].x - 1, (int)player_shots[i].y + 2, 4, 3, COLOR_CYAN);
}
for (unsigned i = 0; i < MAX_ENEMY_SHOTS; ++i) if (enemy_shots[i].active) {
fill_rect(frame, (int)enemy_shots[i].x, (int)enemy_shots[i].y, 2, 5, COLOR_RED);
}
if (player.active) draw_bitmap_scaled(frame, (int)player.x, PLAYER_Y, galaga_player_bitmap, 8, 8, PLAYER_W, PLAYER_H, COLOR_CYAN);
for (unsigned i = 1; i < lives; ++i) draw_bitmap_scaled(frame, 6 + (int)(i - 1U) * 20, LCD_HEIGHT - 13,
galaga_player_bitmap, 8, 8, 16, 12, COLOR_CYAN);
fill_rect(frame, 0, 0, LCD_WIDTH, PLAY_TOP, COLOR_BLACK);
char text[32];
draw_text(frame, 52, 1, "1UP", 1, COLOR_RED);
snprintf(text, sizeof(text), "%u", (unsigned)score); draw_text_centred(frame, 68, 9, text, 1, COLOR_WHITE);
draw_text_centred(frame, 190, 1, "HIGH SCORE", 1, COLOR_RED);
snprintf(text, sizeof(text), "%u", (unsigned)high_score); draw_text_centred(frame, 190, 9, text, 1, COLOR_WHITE);
if (state == READY) {
clear(frame, COLOR_BLACK);
starfield_draw(frame, &stars);
draw_text_centred(frame, 160, 14, "-- GALAGA --", 3, COLOR_CYAN);
draw_text_centred(frame, 160, 49, "SCORE", 1, COLOR_WHITE);
draw_bitmap_scaled(frame, 82, 63, galaga_boss_bitmap, 8, 8, 16, 14, COLOR_MAGENTA);
draw_text(frame, 148, 66, "100", 1, COLOR_MAGENTA);
draw_text(frame, 214, 66, "200", 1, COLOR_RED);
draw_bitmap_scaled(frame, 82, 81, galaga_butterfly_bitmap, 8, 8, 16, 14, COLOR_CYAN);
draw_text(frame, 148, 84, "80", 1, COLOR_CYAN);
draw_text(frame, 214, 84, "160", 1, COLOR_LIME);
draw_bitmap_scaled(frame, 82, 99, galaga_bee_bitmap, 8, 8, 16, 14, COLOR_YELLOW);
draw_text(frame, 148, 102, "50", 1, COLOR_YELLOW);
draw_text(frame, 214, 102, "100", 1, COLOR_ORANGE);
draw_text_centred(frame, 160, 124, "JOYSTICK MOVE A FIRE", 1, COLOR_CYAN);
draw_text_centred(frame, 160, 145, "BOOT BACK", 1, COLOR_LIGHTGRAY);
} else if (state == FINISHED) draw_text_centred(frame, 160, 78, "GAME OVER", 2, COLOR_RED);
else if (last_update < banner_until) draw_text_centred(frame, 160, 23,
challenge_stage ? "CHALLENGING STAGE" : "READY", 1, challenge_stage ? COLOR_YELLOW : COLOR_WHITE);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "galaga.h"
#include "audio.h"
#include "esp_random.h"
#include "game_graphics.h"
#include "high_scores.h"
#include "PET2001_font.h"
#include "starfield.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#define STEP_MS 10U
#define PI 3.14159265f
#define PLAY_TOP 17
#define PLAYER_Y 148
#define PLAYER_W 16
#define PLAYER_H 12
#define MAX_ENEMIES 49
#define MAX_PLAYER_SHOTS 2
#define MAX_ENEMY_SHOTS 6
#define FIRE_DELAY_MS 120U
#define ENEMY_FIRE_MS 850U
#define DIVE_DELAY_MS 1050U
#define RESPAWN_MS 900U
// GALAGA ARTWORK: each byte is one 8-pixel row, bit 7 is the left edge.
// Change these arrays to replace the player, Boss Galaga, butterflies, and bees.
static const uint8_t galaga_player_bitmap[8] = {
0x18, 0x3C, 0x7E, 0xDB, 0xFF, 0x7E, 0x24, 0x42,
};
static const uint8_t galaga_boss_bitmap[8] = {
0x18, 0x7E, 0xDB, 0xFF, 0xBD, 0x5A, 0x24, 0x42,
};
static const uint8_t galaga_butterfly_bitmap[8] = {
0x81, 0x5A, 0x3C, 0xFF, 0x7E, 0x3C, 0x5A, 0x81,
};
static const uint8_t galaga_bee_bitmap[8] = {
0x24, 0x5A, 0xFF, 0x3C, 0x7E, 0xDB, 0x24, 0x42,
};
typedef enum { READY, PLAYING, FINISHED, SCORES } State;
typedef enum { BOSS, BUTTERFLY, BEE } EnemyType;
typedef enum { ENTERING, FORMATION, DIVING } EnemyMotion;
typedef struct { float x, y, vx, vy; bool active; } Object;
typedef struct {
float home_x, home_y, x, y, dive_phase;
EnemyType type;
EnemyMotion motion;
bool active;
} Enemy;
static State state;
static Object player, player_shots[MAX_PLAYER_SHOTS], enemy_shots[MAX_ENEMY_SHOTS];
static Enemy enemies[MAX_ENEMIES];
static uint32_t score, last_update, accumulator, fire_delay, enemy_fire_delay;
static uint32_t dive_delay, state_started, banner_until;
static uint32_t high_score;
static uint8_t lives, wave;
static bool running, controls_ready, challenge_stage;
// The original Galaga formation is deliberately uneven: 5 Boss Galagas,
// 10 butterflies in each middle row, then 12 bees in each lower row.
static const uint8_t formation_count[] = {5, 10, 10, 12, 12};
static starfield_t stars;
static float clampf(float value, float low, float high)
{
return value < low ? low : value > high ? high : value;
}
static uint16_t enemyColour(EnemyType type, EnemyMotion motion)
{
if (motion == DIVING) {
return type == BOSS ? COLOR_RED : type == BUTTERFLY ? COLOR_LIME : COLOR_ORANGE;
}
return type == BOSS ? COLOR_MAGENTA : type == BUTTERFLY ? COLOR_CYAN : COLOR_YELLOW;
}
static const uint8_t *enemyBitmap(EnemyType type)
{
return type == BOSS ? galaga_boss_bitmap : type == BUTTERFLY ? galaga_butterfly_bitmap : galaga_bee_bitmap;
}
static unsigned enemyPoints(EnemyType type, bool diving)
{
const unsigned base = type == BOSS ? 100U : type == BUTTERFLY ? 80U : 50U;
return diving ? base * 2U : base;
}
static void addScore(unsigned points)
{
score = score > UINT32_MAX - points ? UINT32_MAX : score + points;
}
static void prepareWave(void)
{
challenge_stage = wave > 0 && wave % 3U == 0;
memset(player_shots, 0, sizeof(player_shots));
memset(enemy_shots, 0, sizeof(enemy_shots));
unsigned index = 0;
for (unsigned row = 0; row < sizeof(formation_count); ++row) {
const unsigned count = formation_count[row];
const float spacing = 22.0f;
const float first_x = (LCD_WIDTH - (16.0f + (count - 1U) * spacing)) / 2.0f;
for (unsigned column = 0; column < count; ++column) {
EnemyType type = row == 0 ? BOSS : row < 3 ? BUTTERFLY : BEE;
const float home_x = first_x + column * spacing;
const float home_y = 29.0f + row * 18.0f;
// Galaga enemies arrive in staggered groups along long, curved paths
// before locking into their assigned formation position.
const unsigned group = index / 4U;
enemies[index] = (Enemy){home_x, home_y, 0.0f, -16.0f,
-(float)group * 0.18f, type, ENTERING, true};
++index;
}
}
dive_delay = 1000U;
enemy_fire_delay = 800U;
banner_until = last_update + 1200U;
}
static void beginGame(uint32_t now_ms)
{
score = 0; lives = 3; wave = 1; controls_ready = false;
player = (Object){152.0f, PLAYER_Y, 0.0f, 0.0f, true};
fire_delay = 0; accumulator = 0; last_update = now_ms;
prepareWave();
state = PLAYING;
}
static void firePlayerShot(void)
{
if (!player.active || fire_delay != 0) return;
for (unsigned i = 0; i < MAX_PLAYER_SHOTS; ++i) if (!player_shots[i].active) {
player_shots[i] = (Object){player.x + PLAYER_W / 2.0f - 1.0f, PLAYER_Y - 2.0f, 0.0f, -245.0f, true};
fire_delay = FIRE_DELAY_MS;
audioPlay(AUDIO_SFX_PLAYER_SHOT);
return;
}
}
static void fireEnemyShot(void)
{
if (enemy_fire_delay != 0 || challenge_stage) return;
for (unsigned attempts = 0; attempts < MAX_ENEMIES; ++attempts) {
Enemy *enemy = &enemies[esp_random() % MAX_ENEMIES];
if (!enemy->active || enemy->motion != FORMATION) continue;
for (unsigned i = 0; i < MAX_ENEMY_SHOTS; ++i) if (!enemy_shots[i].active) {
const float dx = player.x + PLAYER_W / 2.0f - enemy->x;
enemy_shots[i] = (Object){enemy->x + 7.0f, enemy->y + 13.0f, clampf(dx * 0.45f, -80.0f, 80.0f), 115.0f + wave * 5.0f, true};
enemy_fire_delay = ENEMY_FIRE_MS > wave * 45U ? ENEMY_FIRE_MS - wave * 45U : 260U;
audioPlay(AUDIO_SFX_ENEMY_SHOT);
return;
}
}
}
static void startDive(void)
{
if (challenge_stage || dive_delay != 0) return;
for (unsigned attempts = 0; attempts < MAX_ENEMIES; ++attempts) {
Enemy *enemy = &enemies[esp_random() % MAX_ENEMIES];
if (!enemy->active || enemy->motion != FORMATION) continue;
enemy->motion = DIVING;
enemy->dive_phase = 0.0f;
dive_delay = DIVE_DELAY_MS > wave * 55U ? DIVE_DELAY_MS - wave * 55U : 360U;
return;
}
}
static void hitPlayer(void)
{
if (!player.active) return;
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION);
if (--lives == 0) {
player.active = false;
state = FINISHED;
state_started = last_update;
return;
}
player.active = false;
state_started = last_update;
memset(player_shots, 0, sizeof(player_shots));
memset(enemy_shots, 0, sizeof(enemy_shots));
}
static unsigned enemiesRemaining(void)
{
unsigned count = 0;
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].active) ++count;
return count;
}
static void updateEnemies(void)
{
for (unsigned i = 0; i < MAX_ENEMIES; ++i) {
Enemy *enemy = &enemies[i];
if (!enemy->active) continue;
if (enemy->motion == ENTERING) {
enemy->dive_phase += STEP_MS / 1350.0f;
if (enemy->dive_phase < 0.0f) continue;
const float t = enemy->dive_phase;
if (t >= 1.0f) {
enemy->x = enemy->home_x;
enemy->y = enemy->home_y;
enemy->motion = FORMATION;
continue;
}
const float curve = sinf(PI * t);
const float start_x = ((unsigned)(enemy - enemies) / 4U & 1U) == 0U ? -18.0f : (float)LCD_WIDTH + 2.0f;
const float direction = start_x < 0.0f ? 1.0f : -1.0f;
enemy->x = start_x + (enemy->home_x - start_x) * t + direction * curve * 42.0f;
enemy->y = -16.0f + (enemy->home_y + 16.0f) * t + curve * 36.0f;
continue;
}
if (enemy->motion != DIVING) continue;
enemy->dive_phase += STEP_MS / 1800.0f;
if (enemy->dive_phase >= 1.0f) {
enemy->motion = FORMATION;
enemy->x = enemy->home_x;
enemy->y = enemy->home_y;
continue;
}
const float curve = sinf(PI * enemy->dive_phase);
const float direction = ((unsigned)(enemy - enemies) & 1U) == 0U ? -1.0f : 1.0f;
const float depth = (float)(PLAYER_Y - 16) - enemy->home_y;
enemy->x = enemy->home_x + direction * (curve * 88.0f + sinf(PI * enemy->dive_phase * 2.0f) * 28.0f);
enemy->y = enemy->home_y + curve * depth;
if (player.active && fabsf(enemy->x + 8.0f - (player.x + PLAYER_W / 2.0f)) < 12.0f &&
fabsf(enemy->y + 8.0f - (PLAYER_Y + PLAYER_H / 2.0f)) < 10.0f) hitPlayer();
}
}
static void stepGame(float input_x, bool fire)
{
fire_delay = fire_delay > STEP_MS ? fire_delay - STEP_MS : 0;
enemy_fire_delay = enemy_fire_delay > STEP_MS ? enemy_fire_delay - STEP_MS : 0;
dive_delay = dive_delay > STEP_MS ? dive_delay - STEP_MS : 0;
if (!player.active) {
if (last_update - state_started >= RESPAWN_MS) player = (Object){152.0f, PLAYER_Y, 0.0f, 0.0f, true};
return;
}
player.vx += input_x * 1000.0f * STEP_MS / 1000.0f;
player.vx *= 0.92f;
player.vx = clampf(player.vx, -140.0f, 140.0f);
player.x = clampf(player.x + player.vx * STEP_MS / 1000.0f, 4.0f, LCD_WIDTH - PLAYER_W - 4.0f);
if (fire) firePlayerShot();
startDive();
fireEnemyShot();
updateEnemies();
for (unsigned s = 0; s < MAX_PLAYER_SHOTS; ++s) {
Object *shot = &player_shots[s];
if (!shot->active) continue;
shot->y += shot->vy * STEP_MS / 1000.0f;
if (shot->y < PLAY_TOP) { shot->active = false; continue; }
for (unsigned e = 0; e < MAX_ENEMIES; ++e) {
Enemy *enemy = &enemies[e];
if (!enemy->active || fabsf(shot->x - (enemy->x + 7.0f)) >= 9.0f || fabsf(shot->y - (enemy->y + 7.0f)) >= 10.0f) continue;
addScore(enemyPoints(enemy->type, enemy->motion == DIVING));
enemy->active = false;
shot->active = false;
audioPlay(AUDIO_SFX_HIT);
break;
}
}
for (unsigned s = 0; s < MAX_ENEMY_SHOTS; ++s) {
Object *shot = &enemy_shots[s];
if (!shot->active) continue;
shot->x += shot->vx * STEP_MS / 1000.0f;
shot->y += shot->vy * STEP_MS / 1000.0f;
if (shot->y > LCD_HEIGHT) { shot->active = false; continue; }
if (fabsf(shot->x - (player.x + PLAYER_W / 2.0f)) < 10.0f && fabsf(shot->y - (PLAYER_Y + 5.0f)) < 9.0f) {
shot->active = false;
hitPlayer();
}
}
if (enemiesRemaining() == 0) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
++wave;
prepareWave();
}
}
void startGalaga(uint32_t now_ms)
{
high_score_table_t scores;
high_score = high_scores_load(HIGH_SCORE_GALAGA, &scores) == HIGH_SCORES_OK && scores.count != 0 ? scores.entries[0].score : 0;
starfield_initialize(&stars, 80, 0, 0, LCD_WIDTH, LCD_HEIGHT, STARFIELD_COLOUR);
running = true;
beginGame(now_ms);
state = READY;
}
void stopGalaga(void) { cancelHighScoreEntry(); running = false; }
void galagaAction(bool start, uint32_t now_ms)
{
if (running && start && state == READY) {
beginGame(now_ms);
audioPlay(AUDIO_SFX_GAME_START);
}
}
void updateGalaga(int x_offset, int y_offset, bool joystick_valid, bool fire, uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - last_update;
last_update = now_ms;
if (state == FINISHED && now_ms - state_started >= 1000U) {
startHighScoreEntry(HIGH_SCORE_GALAGA, score, now_ms);
state = SCORES;
}
if (state == SCORES) { if (updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)) state = READY; return; }
starfield_update(&stars, elapsed);
if (state != PLAYING) return;
if (!joystick_valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350) controls_ready = true;
float input_x = joystick_valid && controls_ready ? clampf((float)x_offset / 1500.0f, -1.0f, 1.0f) : 0.0f;
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == PLAYING) { accumulator -= STEP_MS; stepGame(input_x, fire); }
}
void drawGalaga(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == SCORES) { drawHighScoreEntry(frame); return; }
clear(frame, COLOR_BLACK);
starfield_draw(frame, &stars);
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].active) {
const Enemy *enemy = &enemies[i];
draw_bitmap_scaled(frame, (int)enemy->x, (int)enemy->y, enemyBitmap(enemy->type), 8, 8, 16, 14,
enemyColour(enemy->type, enemy->motion));
}
for (unsigned i = 0; i < MAX_PLAYER_SHOTS; ++i) if (player_shots[i].active) {
fill_rect(frame, (int)player_shots[i].x, (int)player_shots[i].y, 2, 8, COLOR_WHITE);
fill_rect(frame, (int)player_shots[i].x - 1, (int)player_shots[i].y + 2, 4, 3, COLOR_CYAN);
}
for (unsigned i = 0; i < MAX_ENEMY_SHOTS; ++i) if (enemy_shots[i].active) {
fill_rect(frame, (int)enemy_shots[i].x, (int)enemy_shots[i].y, 2, 5, COLOR_RED);
}
if (player.active) draw_bitmap_scaled(frame, (int)player.x, PLAYER_Y, galaga_player_bitmap, 8, 8, PLAYER_W, PLAYER_H, COLOR_CYAN);
for (unsigned i = 1; i < lives; ++i) draw_bitmap_scaled(frame, 6 + (int)(i - 1U) * 20, LCD_HEIGHT - 13,
galaga_player_bitmap, 8, 8, 16, 12, COLOR_CYAN);
fill_rect(frame, 0, 0, LCD_WIDTH, PLAY_TOP, COLOR_BLACK);
char text[32];
drawPet2001TextTransparent(frame, 52, 1, "1UP", COLOR_RED, 1);
snprintf(text, sizeof(text), "%u", (unsigned)score); drawPet2001TextCentredTransparent(frame, 68, 9, text, COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 190, 1, "HIGH SCORE", COLOR_RED, 1);
snprintf(text, sizeof(text), "%u", (unsigned)high_score); drawPet2001TextCentredTransparent(frame, 190, 9, text, COLOR_WHITE, 1);
if (state == READY) {
clear(frame, COLOR_BLACK);
starfield_draw(frame, &stars);
drawPet2001TextCentredTransparent(frame, 160, 14, "-- GALAGA --", COLOR_CYAN, 3);
drawPet2001TextCentredTransparent(frame, 160, 49, "SCORE", COLOR_WHITE, 1);
draw_bitmap_scaled(frame, 82, 63, galaga_boss_bitmap, 8, 8, 16, 14, COLOR_MAGENTA);
drawPet2001TextTransparent(frame, 148, 66, "100", COLOR_MAGENTA, 1);
drawPet2001TextTransparent(frame, 214, 66, "200", COLOR_RED, 1);
draw_bitmap_scaled(frame, 82, 81, galaga_butterfly_bitmap, 8, 8, 16, 14, COLOR_CYAN);
drawPet2001TextTransparent(frame, 148, 84, "80", COLOR_CYAN, 1);
drawPet2001TextTransparent(frame, 214, 84, "160", COLOR_LIME, 1);
draw_bitmap_scaled(frame, 82, 99, galaga_bee_bitmap, 8, 8, 16, 14, COLOR_YELLOW);
drawPet2001TextTransparent(frame, 148, 102, "50", COLOR_YELLOW, 1);
drawPet2001TextTransparent(frame, 214, 102, "100", COLOR_ORANGE, 1);
drawPet2001TextCentredTransparent(frame, 160, 124, "JOYSTICK MOVE A FIRE", COLOR_CYAN, 1);
drawPet2001TextCentredTransparent(frame, 160, 145, "BOOT BACK", COLOR_LIGHTGRAY, 1);
} else if (state == FINISHED) drawPet2001TextCentredTransparent(frame, 160, 78, "GAME OVER", COLOR_RED, 2);
else if (last_update < banner_until) drawPet2001TextCentredTransparent(frame, 160, 23,
challenge_stage ? "CHALLENGING STAGE" : "READY", challenge_stage ? COLOR_YELLOW : COLOR_WHITE, 1);
}