Defender
Source: src/defender.c · 435 lines · 24 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 69
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;
}wrapWorld() · source line 74
static float wrapWorld(float x)
- WHILE loop
x < 0.0fRepeat body while the loop condition permits; then continue below.
- Execute
x += WORLD_WIDTH;
- WHILE loop
x >= WORLD_WIDTHRepeat body while the loop condition permits; then continue below.
- Execute
x -= WORLD_WIDTH;
- Exit this function
return x;
Read original function source
static float wrapWorld(float x)
{
while (x < 0.0f) x += WORLD_WIDTH;
while (x >= WORLD_WIDTH) x -= WORLD_WIDTH;
return x;
}worldDelta() · source line 81
static float worldDelta(float from, float to)
- Execute
float delta = to - from; - Decision
delta > WORLD_WIDTH / 2.0fTrue- Execute
delta -= WORLD_WIDTH;
FalseContinue after this decision.
- Decision
delta < -WORLD_WIDTH / 2.0fTrue- Execute
delta += WORLD_WIDTH;
FalseContinue after this decision.
- Exit this function
return delta;
Read original function source
static float worldDelta(float from, float to)
{
float delta = to - from;
if (delta > WORLD_WIDTH / 2.0f) delta -= WORLD_WIDTH;
if (delta < -WORLD_WIDTH / 2.0f) delta += WORLD_WIDTH;
return delta;
}addScore() · source line 89
static void addScore(uint32_t points)
- Execute
score = score > UINT32_MAX - points ? UINT32_MAX : score + points; - Execute
const uint8_t next_wave = (uint8_t)(score / 1500U + 1U); - Decision
next_wave > waveTrue- Execute
wave = next_wave;
FalseContinue after this decision.
Read original function source
static void addScore(uint32_t points)
{
score = score > UINT32_MAX - points ? UINT32_MAX : score + points;
const uint8_t next_wave = (uint8_t)(score / 1500U + 1U);
if (next_wave > wave) wave = next_wave;
}groundHeight() · source line 96
static float groundHeight(float world_x)
- Execute
const float phase = world_x * 0.030f; - Exit this function
return GROUND_Y - 9.0f * sinf(phase) - 5.0f * sinf(phase * 2.7f);
Read original function source
static float groundHeight(float world_x)
{
const float phase = world_x * 0.030f;
return GROUND_Y - 9.0f * sinf(phase) - 5.0f * sinf(phase * 2.7f);
}nearestHuman() · source line 102
static int nearestHuman(float x)
Calls into this documentation:
- Execute
int found = -1; - Execute
float distance = WORLD_WIDTH; - FOR loop
unsigned i = 0; i < HUMANOID_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
humans[i].state != HUMAN_GROUNDTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float candidate = fabsf(worldDelta(x, humans[i].x)); - Decision
candidate < distanceTrue- Execute
distance = candidate; - Execute
found = (int)i;
FalseContinue after this decision.
- Exit this function
return found;
Read original function source
static int nearestHuman(float x)
{
int found = -1;
float distance = WORLD_WIDTH;
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if (humans[i].state != HUMAN_GROUND) continue;
const float candidate = fabsf(worldDelta(x, humans[i].x));
if (candidate < distance) { distance = candidate; found = (int)i; }
}
return found;
}spawnEnemy() · source line 114
static void spawnEnemy(EnemyType type)
Calls into this documentation:
- FOR loop
unsigned i = 0; i < MAX_ENEMIES; ++iRepeat body while the loop condition permits; then continue below.
- Decision
enemies[i].object.activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float x = wrapWorld(player.x + (esp_random() % 2U ? 190.0f : -190.0f)); - Execute
const int human = type == LANDER ? nearestHuman(x) : -1; - Execute
enemies[i] = (Enemy){.object = {x, 28.0f + esp_random() % 55U, 0.0f, 0.0f, true}, .type = type, .human = human}; - Exit this function
return;
Read original function source
static void spawnEnemy(EnemyType type)
{
for (unsigned i = 0; i < MAX_ENEMIES; ++i) {
if (enemies[i].object.active) continue;
const float x = wrapWorld(player.x + (esp_random() % 2U ? 190.0f : -190.0f));
const int human = type == LANDER ? nearestHuman(x) : -1;
enemies[i] = (Enemy){.object = {x, 28.0f + esp_random() % 55U, 0.0f, 0.0f, true},
.type = type, .human = human};
return;
}
}resetWorld() · source line 126
static void resetWorld(void)
Calls into this documentation:
- Execute
memset(shots, 0, sizeof(shots)); - Execute
memset(shot_distance, 0, sizeof(shot_distance)); - Execute
memset(enemies, 0, sizeof(enemies)); - FOR loop
unsigned i = 0; i < HUMANOID_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Execute
humans[i] = (Humanoid){(i + 1U) * WORLD_WIDTH / (HUMANOID_COUNT + 1U), 0.0f, HUMAN_GROUND}; - Execute
humans[i].y = groundHeight(humans[i].x) - 8.0f;
- FOR loop
unsigned i = 0; i < 4; ++iRepeat body while the loop condition permits; then continue below.
- Execute
spawnEnemy(LANDER);
Read original function source
static void resetWorld(void)
{
memset(shots, 0, sizeof(shots));
memset(shot_distance, 0, sizeof(shot_distance));
memset(enemies, 0, sizeof(enemies));
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
humans[i] = (Humanoid){(i + 1U) * WORLD_WIDTH / (HUMANOID_COUNT + 1U), 0.0f, HUMAN_GROUND};
humans[i].y = groundHeight(humans[i].x) - 8.0f;
}
for (unsigned i = 0; i < 4; ++i) spawnEnemy(LANDER);
}beginGame() · source line 138
static void beginGame(uint32_t now_ms)
Calls into this documentation:
- Execute
score = 0; - Execute
wave = 1; - Execute
lives = 3; - Execute
smart_bombs = 3; - Execute
facing = 1; - Execute
reverse_lag = 0; - Execute
player = (Object){WORLD_WIDTH / 2.0f, 75.0f, 0.0f, 0.0f, true}; - Execute
fire_delay = bomb_delay = 0; - Execute
spawn_delay = 1000U; - Execute
accumulator = 0; - Execute
controls_ready = false; - Execute
smart_bomb_held = false; - Execute
thrust_x_ms = thrust_y_ms = 0; - Execute
thrust_x_direction = thrust_y_direction = 0; - Execute
resetWorld(); - Execute
state = PLAYING; - Execute
last_update = now_ms;
Read original function source
static void beginGame(uint32_t now_ms)
{
score = 0; wave = 1; lives = 3; smart_bombs = 3; facing = 1; reverse_lag = 0;
player = (Object){WORLD_WIDTH / 2.0f, 75.0f, 0.0f, 0.0f, true};
fire_delay = bomb_delay = 0; spawn_delay = 1000U; accumulator = 0;
controls_ready = false; smart_bomb_held = false;
thrust_x_ms = thrust_y_ms = 0; thrust_x_direction = thrust_y_direction = 0;
resetWorld();
state = PLAYING;
last_update = now_ms;
}hitPlayer() · source line 150
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(shots, 0, sizeof(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(shots, 0, sizeof(shots));
}fireShot() · source line 160
static void fireShot(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_SHOTS; ++iRepeat body while the loop condition permits; then continue below.
- Decision
!shots[i].activeTrue- Execute
shots[i] = (Object){player.x + facing * 10.0f, player.y + 4.0f, facing * 250.0f, 0.0f, true}; - Execute
shot_distance[i] = 0.0f; - 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 fireShot(void)
{
if (!player.active || fire_delay != 0) return;
for (unsigned i = 0; i < MAX_SHOTS; ++i) if (!shots[i].active) {
shots[i] = (Object){player.x + facing * 10.0f, player.y + 4.0f, facing * 250.0f, 0.0f, true};
shot_distance[i] = 0.0f;
fire_delay = FIRE_DELAY_MS;
audioPlay(AUDIO_SFX_PLAYER_SHOT);
return;
}
}releaseLanderHuman() · source line 172
static void releaseLanderHuman(Enemy *enemy)
Calls into this documentation:
- Decision
enemy->human < 0 || humans[enemy->human].state != HUMAN_ABDUCTEDTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
Humanoid *human = &humans[enemy->human]; - Execute
human->x = enemy->object.x; - Execute
human->y = enemy->object.y + 10.0f; - Decision
groundHeight(human->x) - human->y > 16.0fTrue- Execute
human->state = HUMAN_FALLING;
False- Execute
human->y = groundHeight(human->x) - 8.0f; - Execute
human->state = HUMAN_GROUND;
- Execute
enemy->human = -1;
Read original function source
static void releaseLanderHuman(Enemy *enemy)
{
if (enemy->human < 0 || humans[enemy->human].state != HUMAN_ABDUCTED) return;
Humanoid *human = &humans[enemy->human];
human->x = enemy->object.x;
human->y = enemy->object.y + 10.0f;
if (groundHeight(human->x) - human->y > 16.0f) human->state = HUMAN_FALLING;
else {
human->y = groundHeight(human->x) - 8.0f;
human->state = HUMAN_GROUND;
}
enemy->human = -1;
}smartBomb() · source line 186
static void smartBomb(void)
Calls into this documentation:
- Decision
bomb_delay != 0 || smart_bombs == 0 || !player.activeTrue- Exit this function
return;
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_ENEMIES; ++iRepeat body while the loop condition permits; then continue below.
- Decision
enemies[i].object.activeTrue- Execute
const float x = screenX(enemies[i].object.x); - Decision
x < -16.0f || x >= LCD_WIDTH + 16.0fTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
releaseLanderHuman(&enemies[i]); - Execute
enemies[i].object.active = false; - Execute
addScore(enemies[i].type == MUTANT ? 250U : 150U);
FalseContinue after this decision.
- Execute
--smart_bombs; - Execute
bomb_delay = SMART_BOMB_DELAY_MS; - Execute
audioPlay(AUDIO_SFX_EXPLOSION);
Read original function source
static void smartBomb(void)
{
if (bomb_delay != 0 || smart_bombs == 0 || !player.active) return;
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].object.active) {
const float x = screenX(enemies[i].object.x);
if (x < -16.0f || x >= LCD_WIDTH + 16.0f) continue;
releaseLanderHuman(&enemies[i]);
enemies[i].object.active = false;
addScore(enemies[i].type == MUTANT ? 250U : 150U);
}
--smart_bombs;
bomb_delay = SMART_BOMB_DELAY_MS;
audioPlay(AUDIO_SFX_EXPLOSION);
}updateEnemy() · source line 201
static void updateEnemy(Enemy *enemy)
Calls into this documentation:
- Execute
Object *object = &enemy->object; - Decision
!object->activeTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const float dx = worldDelta(object->x, player.x); - Decision
enemy->type == LANDERTrue- Decision
enemy->human >= 0 && humans[enemy->human].state == HUMAN_GROUNDTrue- Execute
const float target_x = humans[enemy->human].x; - Execute
object->x = wrapWorld(object->x + clampf(worldDelta(object->x, target_x), -35.0f, 35.0f) * STEP_MS / 1000.0f); - Execute
const float target_y = humans[enemy->human].y - 8.0f; - Execute
object->y += clampf(target_y - object->y, -24.0f, 24.0f) * STEP_MS / 1000.0f; - Decision
fabsf(worldDelta(object->x, target_x)) < 5.0f && fabsf(object->y - target_y) < 5.0fTrue- Execute
humans[enemy->human].state = HUMAN_ABDUCTED;
FalseContinue after this decision.
False- Decision
enemy->human >= 0 && humans[enemy->human].state == HUMAN_ABDUCTEDTrue- Execute
humans[enemy->human].x = object->x; - Execute
humans[enemy->human].y = object->y + 10.0f; - Execute
object->y -= (30.0f + wave * 2.0f) * STEP_MS / 1000.0f; - Decision
object->y < PLAY_TOPTrue- Execute
humans[enemy->human].state = HUMAN_LOST; - Execute
enemy->type = MUTANT; - Execute
enemy->human = -1; - Execute
object->y = PLAY_TOP + 4;
FalseContinue after this decision.
False- Execute
object->x = wrapWorld(object->x + (dx > 0 ? 20.0f : -20.0f) * STEP_MS / 1000.0f);
False- Execute
const float speed = enemy->type == MUTANT ? 58.0f + wave * 4.0f : 35.0f + wave * 2.0f; - Execute
object->x = wrapWorld(object->x + clampf(dx, -speed, speed) * STEP_MS / 1000.0f); - Execute
object->y += clampf(player.y - object->y, -speed * 0.45f, speed * 0.45f) * STEP_MS / 1000.0f;
- Decision
fabsf(dx) < 12.0f && fabsf(object->y - player.y) < 12.0fTrue- Execute
hitPlayer();
FalseContinue after this decision.
Read original function source
static void updateEnemy(Enemy *enemy)
{
Object *object = &enemy->object;
if (!object->active) return;
const float dx = worldDelta(object->x, player.x);
if (enemy->type == LANDER) {
if (enemy->human >= 0 && humans[enemy->human].state == HUMAN_GROUND) {
const float target_x = humans[enemy->human].x;
object->x = wrapWorld(object->x + clampf(worldDelta(object->x, target_x), -35.0f, 35.0f) * STEP_MS / 1000.0f);
const float target_y = humans[enemy->human].y - 8.0f;
object->y += clampf(target_y - object->y, -24.0f, 24.0f) * STEP_MS / 1000.0f;
if (fabsf(worldDelta(object->x, target_x)) < 5.0f && fabsf(object->y - target_y) < 5.0f) {
humans[enemy->human].state = HUMAN_ABDUCTED;
}
} else if (enemy->human >= 0 && humans[enemy->human].state == HUMAN_ABDUCTED) {
humans[enemy->human].x = object->x;
humans[enemy->human].y = object->y + 10.0f;
object->y -= (30.0f + wave * 2.0f) * STEP_MS / 1000.0f;
if (object->y < PLAY_TOP) {
humans[enemy->human].state = HUMAN_LOST;
enemy->type = MUTANT;
enemy->human = -1;
object->y = PLAY_TOP + 4;
}
} else {
object->x = wrapWorld(object->x + (dx > 0 ? 20.0f : -20.0f) * STEP_MS / 1000.0f);
}
} else {
const float speed = enemy->type == MUTANT ? 58.0f + wave * 4.0f : 35.0f + wave * 2.0f;
object->x = wrapWorld(object->x + clampf(dx, -speed, speed) * STEP_MS / 1000.0f);
object->y += clampf(player.y - object->y, -speed * 0.45f, speed * 0.45f) * STEP_MS / 1000.0f;
}
if (fabsf(dx) < 12.0f && fabsf(object->y - player.y) < 12.0f) hitPlayer();
}updateHumanRescue() · source line 237
static void updateHumanRescue(void)
Calls into this documentation:
- Execute
int carried = -1; - FOR loop
unsigned i = 0; i < HUMANOID_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
humans[i].state == HUMAN_CARRIEDTrue- Execute
carried = (int)i; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
carried < 0True- FOR loop
unsigned i = 0; i < HUMANOID_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
(humans[i].state != HUMAN_ABDUCTED && humans[i].state != HUMAN_FALLING) || fabsf(worldDelta(player.x, humans[i].x)) >= 10.0f || fabsf((player.y + PLAYER_H) - humans[i].y) >= 12.0fTrue- 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.
- Decision
enemies[e].human == (int)iTrue- Execute
enemies[e].human = -1;
FalseContinue after this decision.
- Execute
humans[i].state = HUMAN_CARRIED; - Execute
carried = (int)i; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
carried < 0True- Exit this function
return;
FalseContinue after this decision.
- Execute
Humanoid *human = &humans[carried]; - Execute
human->x = player.x; - Execute
human->y = player.y + PLAYER_H; - Decision
human->y >= groundHeight(player.x) - 1.0fTrue- Execute
human->y = groundHeight(player.x) - 8.0f; - Execute
human->state = HUMAN_GROUND; - Execute
addScore(500U);
FalseContinue after this decision.
Read original function source
static void updateHumanRescue(void)
{
int carried = -1;
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if (humans[i].state == HUMAN_CARRIED) { carried = (int)i; break; }
}
/* Catch an abducted human by flying beneath it. Only one can be carried. */
if (carried < 0) {
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if ((humans[i].state != HUMAN_ABDUCTED && humans[i].state != HUMAN_FALLING) ||
fabsf(worldDelta(player.x, humans[i].x)) >= 10.0f ||
fabsf((player.y + PLAYER_H) - humans[i].y) >= 12.0f) continue;
for (unsigned e = 0; e < MAX_ENEMIES; ++e) {
if (enemies[e].human == (int)i) enemies[e].human = -1;
}
humans[i].state = HUMAN_CARRIED;
carried = (int)i;
break;
}
}
if (carried < 0) return;
Humanoid *human = &humans[carried];
human->x = player.x;
human->y = player.y + PLAYER_H;
/* Landing near the terrain safely returns the human and awards rescue points. */
if (human->y >= groundHeight(player.x) - 1.0f) {
human->y = groundHeight(player.x) - 8.0f;
human->state = HUMAN_GROUND;
addScore(500U);
}
}updateFallingHumans() · source line 273
static void updateFallingHumans(void)
Calls into this documentation:
- FOR loop
unsigned i = 0; i < HUMANOID_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Execute
Humanoid *human = &humans[i]; - Decision
human->state != HUMAN_FALLINGTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
human->y += 75.0f * STEP_MS / 1000.0f; - Decision
human->y >= groundHeight(human->x) - 8.0fTrue- Execute
human->y = groundHeight(human->x) - 8.0f; - Execute
human->state = HUMAN_GROUND;
FalseContinue after this decision.
Read original function source
static void updateFallingHumans(void)
{
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
Humanoid *human = &humans[i];
if (human->state != HUMAN_FALLING) continue;
human->y += 75.0f * STEP_MS / 1000.0f;
if (human->y >= groundHeight(human->x) - 8.0f) {
human->y = groundHeight(human->x) - 8.0f;
human->state = HUMAN_GROUND;
}
}
}updateThrustRamp() · source line 286
static uint16_t updateThrustRamp(float input, int *direction, uint16_t held_ms)
- Execute
const int new_direction = input > 0.15f ? 1 : input < -0.15f ? -1 : 0; - Decision
new_direction == 0True- Execute
*direction = 0; - Exit this function
return 0;
FalseContinue after this decision.
- Decision
new_direction != *directionTrue- Execute
*direction = new_direction; - Exit this function
return STEP_MS;
FalseContinue after this decision.
- Exit this function
return held_ms + STEP_MS > THRUST_RAMP_MS ? THRUST_RAMP_MS : held_ms + STEP_MS;
Read original function source
static uint16_t updateThrustRamp(float input, int *direction, uint16_t held_ms)
{
const int new_direction = input > 0.15f ? 1 : input < -0.15f ? -1 : 0;
if (new_direction == 0) { *direction = 0; return 0; }
if (new_direction != *direction) { *direction = new_direction; return STEP_MS; }
return held_ms + STEP_MS > THRUST_RAMP_MS ? THRUST_RAMP_MS : held_ms + STEP_MS;
}stepGame() · source line 294
static void stepGame(float input_x, float input_y, bool fire, bool smart_bomb)
Calls into this documentation:
- Execute
fire_delay = fire_delay > STEP_MS ? fire_delay - STEP_MS : 0; - Execute
bomb_delay = bomb_delay > STEP_MS ? bomb_delay - STEP_MS : 0; - Execute
reverse_lag = reverse_lag > STEP_MS ? reverse_lag - STEP_MS : 0; - Decision
!player.activeTrue- Decision
last_update - state_started >= RESPAWN_MSTrue- Execute
player = (Object){WORLD_WIDTH / 2.0f, 75.0f, 0, 0, true};
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
thrust_x_ms = updateThrustRamp(input_x, &thrust_x_direction, thrust_x_ms); - Execute
thrust_y_ms = updateThrustRamp(input_y, &thrust_y_direction, thrust_y_ms); - Execute
const float x_acceleration = reverse_lag ? 0.0f : 100.0f + 180.0f * (float)thrust_x_ms / THRUST_RAMP_MS; - Execute
const float y_acceleration = reverse_lag ? 0.0f : 100.0f + 180.0f * (float)thrust_y_ms / THRUST_RAMP_MS; - Execute
player.vx += input_x * x_acceleration * STEP_MS / 1000.0f; - Execute
player.vy += input_y * y_acceleration * STEP_MS / 1000.0f; - Execute
player.vx *= 0.975f; - Execute
player.vy *= 0.975f; - Execute
player.vx = clampf(player.vx, -125.0f, 125.0f); - Execute
player.vy = clampf(player.vy, -95.0f, 95.0f); - Execute
player.x = wrapWorld(player.x + player.vx * STEP_MS / 1000.0f); - Execute
player.y = clampf(player.y + player.vy * STEP_MS / 1000.0f, PLAY_TOP + 4.0f, groundHeight(player.x) - PLAYER_H); - Execute
camera_x = player.x - LCD_WIDTH / 2.0f; - Execute
updateFallingHumans(); - Execute
updateHumanRescue(); - Decision
fireTrue- Execute
fireShot();
FalseContinue after this decision.
- Decision
smart_bombTrue- Execute
smartBomb();
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_SHOTS; ++iRepeat body while the loop condition permits; then continue below.
- Execute
Object *shot = &shots[i]; - Decision
!shot->activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
shot->x = wrapWorld(shot->x + shot->vx * STEP_MS / 1000.0f); - Execute
shot_distance[i] += fabsf(shot->vx) * STEP_MS / 1000.0f; - Decision
shot_distance[i] > 320.0fTrue- 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->object.active && fabsf(worldDelta(shot->x, enemy->object.x)) < 10.0f && fabsf(shot->y - enemy->object.y) < 9.0fTrue- Execute
releaseLanderHuman(enemy); - Execute
enemy->object.active = false; - Execute
shot->active = false; - Execute
addScore(enemy->type == MUTANT ? 250U : enemy->type == BOMBER ? 150U : 150U); - Execute
audioPlay(AUDIO_SFX_HIT); - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_ENEMIES; ++iRepeat body while the loop condition permits; then continue below.
- Execute
updateEnemy(&enemies[i]);
- Execute
spawn_delay = spawn_delay > STEP_MS ? spawn_delay - STEP_MS : 0; - Decision
spawn_delay == 0True- Execute
spawnEnemy((esp_random() % 8U) == 0 ? BOMBER : LANDER); - Execute
spawn_delay = 900U - (wave > 5U ? 250U : wave * 50U);
FalseContinue after this decision.
Read original function source
static void stepGame(float input_x, float input_y, bool fire, bool smart_bomb)
{
fire_delay = fire_delay > STEP_MS ? fire_delay - STEP_MS : 0;
bomb_delay = bomb_delay > STEP_MS ? bomb_delay - STEP_MS : 0;
reverse_lag = reverse_lag > STEP_MS ? reverse_lag - STEP_MS : 0;
if (!player.active) { if (last_update - state_started >= RESPAWN_MS) player = (Object){WORLD_WIDTH / 2.0f, 75.0f, 0, 0, true}; return; }
thrust_x_ms = updateThrustRamp(input_x, &thrust_x_direction, thrust_x_ms);
thrust_y_ms = updateThrustRamp(input_y, &thrust_y_direction, thrust_y_ms);
const float x_acceleration = reverse_lag ? 0.0f : 100.0f + 180.0f * (float)thrust_x_ms / THRUST_RAMP_MS;
const float y_acceleration = reverse_lag ? 0.0f : 100.0f + 180.0f * (float)thrust_y_ms / THRUST_RAMP_MS;
player.vx += input_x * x_acceleration * STEP_MS / 1000.0f;
player.vy += input_y * y_acceleration * STEP_MS / 1000.0f;
player.vx *= 0.975f; player.vy *= 0.975f;
player.vx = clampf(player.vx, -125.0f, 125.0f);
player.vy = clampf(player.vy, -95.0f, 95.0f);
player.x = wrapWorld(player.x + player.vx * STEP_MS / 1000.0f);
player.y = clampf(player.y + player.vy * STEP_MS / 1000.0f, PLAY_TOP + 4.0f, groundHeight(player.x) - PLAYER_H);
camera_x = player.x - LCD_WIDTH / 2.0f;
updateFallingHumans();
updateHumanRescue();
if (fire) fireShot();
if (smart_bomb) smartBomb();
for (unsigned i = 0; i < MAX_SHOTS; ++i) {
Object *shot = &shots[i]; if (!shot->active) continue;
shot->x = wrapWorld(shot->x + shot->vx * STEP_MS / 1000.0f);
shot_distance[i] += fabsf(shot->vx) * STEP_MS / 1000.0f;
if (shot_distance[i] > 320.0f) { shot->active = false; continue; }
for (unsigned e = 0; e < MAX_ENEMIES; ++e) {
Enemy *enemy = &enemies[e];
if (enemy->object.active && fabsf(worldDelta(shot->x, enemy->object.x)) < 10.0f && fabsf(shot->y - enemy->object.y) < 9.0f) {
releaseLanderHuman(enemy);
enemy->object.active = false; shot->active = false;
addScore(enemy->type == MUTANT ? 250U : enemy->type == BOMBER ? 150U : 150U);
audioPlay(AUDIO_SFX_HIT);
break;
}
}
}
for (unsigned i = 0; i < MAX_ENEMIES; ++i) updateEnemy(&enemies[i]);
spawn_delay = spawn_delay > STEP_MS ? spawn_delay - STEP_MS : 0;
if (spawn_delay == 0) { spawnEnemy((esp_random() % 8U) == 0 ? BOMBER : LANDER); spawn_delay = 900U - (wave > 5U ? 250U : wave * 50U); }
}startDefender() · source line 337
void startDefender(uint32_t now_ms)
Calls into this documentation:
- Execute
running = true; - Execute
beginGame(now_ms); - Execute
state = READY;
Read original function source
void startDefender(uint32_t now_ms) { running = true; beginGame(now_ms); state = READY; }stopDefender() · source line 338
void stopDefender(void)
Calls into this documentation:
- Execute
cancelHighScoreEntry(); - Execute
running = false;
Read original function source
void stopDefender(void) { cancelHighScoreEntry(); running = false; }defenderAction() · source line 339
void defenderAction(bool start, bool reverse, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
start && state == READYTrue- Execute
beginGame(now_ms); - Execute
audioPlay(AUDIO_SFX_GAME_START);
FalseContinue after this decision.
- Decision
reverse && state == PLAYING && player.activeTrue- Execute
facing = -facing; - Execute
player.vx *= 0.30f; - Execute
reverse_lag = REVERSE_LAG_MS; - Execute
thrust_x_ms = thrust_y_ms = 0; - Execute
thrust_x_direction = thrust_y_direction = 0;
FalseContinue after this decision.
Read original function source
void defenderAction(bool start, bool reverse, uint32_t now_ms)
{
if (!running) return;
if (start && state == READY) {
beginGame(now_ms);
audioPlay(AUDIO_SFX_GAME_START);
}
if (reverse && state == PLAYING && player.active) {
facing = -facing; player.vx *= 0.30f; reverse_lag = REVERSE_LAG_MS;
thrust_x_ms = thrust_y_ms = 0; thrust_x_direction = thrust_y_direction = 0;
}
}updateDefender() · source line 352
void updateDefender(int x_offset, int y_offset, bool valid, bool fire, bool smart_bomb, 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_DEFENDER, score, now_ms); - Execute
state = SCORES;
FalseContinue after this decision.
- Decision
state == SCORESTrue- Decision
updateHighScoreEntry(x_offset, y_offset, valid, now_ms)True- Execute
state = READY;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state != PLAYINGTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
!validTrue- Execute
controls_ready = false;
False- Decision
x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350True- Execute
controls_ready = true;
FalseContinue after this decision.
- Execute
const bool smart_bomb_pressed = smart_bomb && !smart_bomb_held; - Execute
smart_bomb_held = smart_bomb; - Execute
const float input_x = valid && controls_ready ? clampf((float)x_offset / 1600.0f, -1.0f, 1.0f) : 0.0f; - Execute
const float input_y = valid && controls_ready ? clampf((float)-y_offset / 1600.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, input_y, fire, smart_bomb_pressed);
Read original function source
void updateDefender(int x_offset, int y_offset, bool valid, bool fire, bool smart_bomb, 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_DEFENDER, score, now_ms); state = SCORES; }
if (state == SCORES) { if (updateHighScoreEntry(x_offset, y_offset, valid, now_ms)) state = READY; return; }
if (state != PLAYING) return;
if (!valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350) controls_ready = true;
const bool smart_bomb_pressed = smart_bomb && !smart_bomb_held;
smart_bomb_held = smart_bomb;
const float input_x = valid && controls_ready ? clampf((float)x_offset / 1600.0f, -1.0f, 1.0f) : 0.0f;
const float input_y = valid && controls_ready ? clampf((float)-y_offset / 1600.0f, -1.0f, 1.0f) : 0.0f;
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == PLAYING) { accumulator -= STEP_MS; stepGame(input_x, input_y, fire, smart_bomb_pressed); }
}screenX() · source line 369
static float screenX(float world_x)
Calls into this documentation:
- Execute
float x = worldDelta(camera_x, world_x); - Decision
x < -20.0fTrue- Execute
x += WORLD_WIDTH;
FalseContinue after this decision.
- Exit this function
return x;
Read original function source
static float screenX(float world_x)
{
float x = worldDelta(camera_x, world_x);
if (x < -20.0f) x += WORLD_WIDTH;
return x;
}drawDefender() · source line 376
void drawDefender(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); - FOR loop
int x = 0; x < LCD_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const int ground = (int)groundHeight(camera_x + x); - Execute
fill_rect(frame, x, ground, 1, LCD_HEIGHT - ground, COLOR_DARKGREEN); - Execute
fill_rect(frame, x, ground, 1, 2, COLOR_LIME);
- FOR loop
unsigned i = 0; i < HUMANOID_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
humans[i].state == HUMAN_LOSTTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int x = (int)screenX(humans[i].x); - Execute
const uint16_t colour = humans[i].state == HUMAN_CARRIED ? COLOR_CYAN : humans[i].state == HUMAN_FALLING ? COLOR_WHITE : humans[i].state == HUMAN_ABDUCTED ? COLOR_ORANGE : COLOR_YELLOW; - Decision
x >= -8 && x < LCD_WIDTHTrue- Execute
draw_bitmap_scaled(frame, x - 4, (int)humans[i].y, defender_humanoid_bitmap, 8, 8, 8, 10, colour);
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_ENEMIES; ++iRepeat body while the loop condition permits; then continue below.
- Decision
enemies[i].object.activeTrue- Execute
const Enemy *enemy = &enemies[i]; - Execute
const int x = (int)screenX(enemy->object.x); - Execute
const uint8_t *bitmap = enemy->type == LANDER ? defender_lander_bitmap : enemy->type == MUTANT ? defender_mutant_bitmap : defender_bomber_bitmap; - Execute
const uint16_t colour = enemy->type == LANDER ? COLOR_MAGENTA : enemy->type == MUTANT ? COLOR_RED : COLOR_ORANGE; - Execute
draw_bitmap_scaled(frame, x - 8, (int)enemy->object.y, bitmap, 8, 8, 16, 12, colour);
FalseContinue after this decision.
- FOR loop
unsigned i = 0; i < MAX_SHOTS; ++iRepeat body while the loop condition permits; then continue below.
- Decision
shots[i].activeTrue- Execute
const int x = (int)screenX(shots[i].x); - Execute
const int y = (int)shots[i].y + 4; - Execute
const int direction = shots[i].vx < 0.0f ? -1 : 1; - Decision
x < -18 || x >= LCD_WIDTH + 18True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
draw_line(frame, x - direction * 31, y, x - direction * 4, y, COLOR_DARKCYAN); - Execute
fill_rect(frame, x - (direction < 0 ? 4 : 0), y - 1, 5, 3, COLOR_WHITE); - Execute
fill_rect(frame, x - direction * 36, y, 2, 1, COLOR_CYAN); - Execute
fill_rect(frame, x - direction * 44, y, 2, 1, COLOR_BLUE);
FalseContinue after this decision.
- Decision
player.activeTrue- Execute
draw_bitmap_scaled(frame, 152, (int)player.y, facing > 0 ? defender_ship_right : defender_ship_left, 8, 8, PLAYER_W, PLAYER_H, COLOR_CYAN);
FalseContinue after this decision.
- Execute
fill_rect(frame, 0, 0, LCD_WIDTH, PLAY_TOP, COLOR_BLACK); - FOR loop
unsigned i = 0; i < HUMANOID_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Execute
const int x = (int)(humans[i].x * LCD_WIDTH / WORLD_WIDTH); - Execute
const uint16_t colour = humans[i].state == HUMAN_LOST ? COLOR_RED : humans[i].state == HUMAN_CARRIED ? COLOR_CYAN : humans[i].state == HUMAN_FALLING ? COLOR_WHITE : humans[i].state == HUMAN_ABDUCTED ? COLOR_ORANGE : COLOR_YELLOW; - Execute
fill_rect(frame, x, 2, 2, 4, colour);
- Execute
char text[32]; - Execute
snprintf(text, sizeof(text), "SCORE %u", (unsigned)score); - Execute
draw_text(frame, 3, 7, text, 1, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives); - Execute
draw_text_centred(frame, 164, 7, text, 1, COLOR_YELLOW); - Execute
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave); - Execute
draw_text(frame, 205, 7, text, 1, COLOR_CYAN); - Execute
snprintf(text, sizeof(text), "BOMB %u", (unsigned)smart_bombs); - Execute
draw_text(frame, 264, 7, text, 1, COLOR_ORANGE); - Decision
state == READYTrue- Execute
clear(frame, COLOR_BLACK); - Execute
draw_text_centred(frame, 160, 21, "DEFENDER", 3, COLOR_CYAN); - Execute
draw_text_centred(frame, 160, 59, "RESCUE THE HUMANOIDS", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 76, "STICK FLY A FIRE", 1, COLOR_YELLOW); - Execute
draw_text_centred(frame, 160, 92, "C REVERSE B SMART BOMB", 1, COLOR_ORANGE); - Execute
draw_text_centred(frame, 160, 140, "BOOT BACK", 1, COLOR_LIGHTGRAY);
False- Decision
state == FINISHEDTrue- Execute
draw_text_centred(frame, 160, 78, "GAME OVER", 2, COLOR_RED);
FalseContinue after this decision.
Read original function source
void drawDefender(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == SCORES) { drawHighScoreEntry(frame); return; }
clear(frame, COLOR_BLACK);
for (int x = 0; x < LCD_WIDTH; ++x) {
const int ground = (int)groundHeight(camera_x + x);
fill_rect(frame, x, ground, 1, LCD_HEIGHT - ground, COLOR_DARKGREEN);
fill_rect(frame, x, ground, 1, 2, COLOR_LIME);
}
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if (humans[i].state == HUMAN_LOST) continue;
const int x = (int)screenX(humans[i].x);
const uint16_t colour = humans[i].state == HUMAN_CARRIED ? COLOR_CYAN :
humans[i].state == HUMAN_FALLING ? COLOR_WHITE :
humans[i].state == HUMAN_ABDUCTED ? COLOR_ORANGE : COLOR_YELLOW;
if (x >= -8 && x < LCD_WIDTH) draw_bitmap_scaled(frame, x - 4, (int)humans[i].y,
defender_humanoid_bitmap, 8, 8, 8, 10, colour);
}
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].object.active) {
const Enemy *enemy = &enemies[i]; const int x = (int)screenX(enemy->object.x);
const uint8_t *bitmap = enemy->type == LANDER ? defender_lander_bitmap : enemy->type == MUTANT ? defender_mutant_bitmap : defender_bomber_bitmap;
const uint16_t colour = enemy->type == LANDER ? COLOR_MAGENTA : enemy->type == MUTANT ? COLOR_RED : COLOR_ORANGE;
draw_bitmap_scaled(frame, x - 8, (int)enemy->object.y, bitmap, 8, 8, 16, 12, colour);
}
// Laser bolts travel horizontally: a bright core with a short dotted cyan trail.
for (unsigned i = 0; i < MAX_SHOTS; ++i) if (shots[i].active) {
const int x = (int)screenX(shots[i].x);
const int y = (int)shots[i].y + 4;
const int direction = shots[i].vx < 0.0f ? -1 : 1;
if (x < -18 || x >= LCD_WIDTH + 18) continue;
draw_line(frame, x - direction * 31, y, x - direction * 4, y, COLOR_DARKCYAN);
fill_rect(frame, x - (direction < 0 ? 4 : 0), y - 1, 5, 3, COLOR_WHITE);
fill_rect(frame, x - direction * 36, y, 2, 1, COLOR_CYAN);
fill_rect(frame, x - direction * 44, y, 2, 1, COLOR_BLUE);
}
if (player.active) draw_bitmap_scaled(frame, 152, (int)player.y, facing > 0 ? defender_ship_right : defender_ship_left, 8, 8, PLAYER_W, PLAYER_H, COLOR_CYAN);
fill_rect(frame, 0, 0, LCD_WIDTH, PLAY_TOP, COLOR_BLACK);
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
const int x = (int)(humans[i].x * LCD_WIDTH / WORLD_WIDTH);
const uint16_t colour = humans[i].state == HUMAN_LOST ? COLOR_RED :
humans[i].state == HUMAN_CARRIED ? COLOR_CYAN :
humans[i].state == HUMAN_FALLING ? COLOR_WHITE :
humans[i].state == HUMAN_ABDUCTED ? COLOR_ORANGE : COLOR_YELLOW;
fill_rect(frame, x, 2, 2, 4, colour);
}
char text[32]; snprintf(text, sizeof(text), "SCORE %u", (unsigned)score); draw_text(frame, 3, 7, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives); draw_text_centred(frame, 164, 7, text, 1, COLOR_YELLOW);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave); draw_text(frame, 205, 7, text, 1, COLOR_CYAN);
snprintf(text, sizeof(text), "BOMB %u", (unsigned)smart_bombs); draw_text(frame, 264, 7, text, 1, COLOR_ORANGE);
if (state == READY) {
clear(frame, COLOR_BLACK);
draw_text_centred(frame, 160, 21, "DEFENDER", 3, COLOR_CYAN);
draw_text_centred(frame, 160, 59, "RESCUE THE HUMANOIDS", 1, COLOR_WHITE);
draw_text_centred(frame, 160, 76, "STICK FLY A FIRE", 1, COLOR_YELLOW);
draw_text_centred(frame, 160, 92, "C REVERSE B SMART BOMB", 1, COLOR_ORANGE);
draw_text_centred(frame, 160, 140, "BOOT BACK", 1, COLOR_LIGHTGRAY);
} else if (state == FINISHED) draw_text_centred(frame, 160, 78, "GAME OVER", 2, COLOR_RED);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "defender.h"
#include "audio.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "high_scores.h"
#include "esp_random.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#define STEP_MS 10U
#define WORLD_WIDTH 960.0f
#define PLAY_TOP 15
#define GROUND_Y 145
#define PLAYER_W 16
#define PLAYER_H 10
#define HUMANOID_COUNT 6
#define MAX_ENEMIES 10
#define MAX_SHOTS 5
#define FIRE_DELAY_MS 115U
#define SMART_BOMB_DELAY_MS 900U
#define REVERSE_LAG_MS 180U
#define RESPAWN_MS 1000U
#define THRUST_RAMP_MS 900U
// DEFENDER ARTWORK: each byte is one 8-pixel row, bit 7 is the left edge.
// Replace these arrays to change the ship, civilians, and enemy pictures.
static const uint8_t defender_ship_right[8] = {
0x00, 0x00, 0xC0, 0xFC, 0x77, 0xFF, 0x00, 0x00,
};
static const uint8_t defender_ship_left[8] = {
0x00, 0x00, 0x03, 0x7F, 0xEE, 0xFF, 0x08, 0x00,
};
static const uint8_t defender_humanoid_bitmap[8] = {
0x18, 0x18, 0x7E, 0x7E, 0x5A, 0x18, 0x18, 0x18,
};
static const uint8_t defender_lander_bitmap[8] = {
0x18, 0x3C, 0x7E, 0xDB, 0xFF, 0x24, 0x42, 0x00,
};
static const uint8_t defender_mutant_bitmap[8] = {
0x42, 0xA5, 0x5A, 0xFF, 0x5A, 0xA5, 0x42, 0x00,
};
static const uint8_t defender_bomber_bitmap[8] = {
0x00, 0x24, 0x7E, 0xFF, 0xFF, 0x7E, 0x24, 0x00,
};
typedef enum { READY, PLAYING, FINISHED, SCORES } State;
typedef enum { LANDER, MUTANT, BOMBER } EnemyType;
typedef enum { HUMAN_GROUND, HUMAN_ABDUCTED, HUMAN_FALLING, HUMAN_CARRIED, HUMAN_LOST } HumanState;
typedef struct { float x, y, vx, vy; bool active; } Object;
typedef struct { float x, y; HumanState state; } Humanoid;
typedef struct { Object object; EnemyType type; int human; } Enemy;
static State state;
static Object player, shots[MAX_SHOTS];
static float shot_distance[MAX_SHOTS];
static Humanoid humans[HUMANOID_COUNT];
static Enemy enemies[MAX_ENEMIES];
static float camera_x;
static uint32_t score, last_update, accumulator, fire_delay, bomb_delay, state_started, spawn_delay;
static uint8_t lives, wave, smart_bombs;
static int facing;
static bool running, controls_ready, smart_bomb_held;
static uint32_t reverse_lag;
static uint16_t thrust_x_ms, thrust_y_ms;
static int thrust_x_direction, thrust_y_direction;
static float screenX(float world_x);
static float clampf(float value, float low, float high)
{
return value < low ? low : value > high ? high : value;
}
static float wrapWorld(float x)
{
while (x < 0.0f) x += WORLD_WIDTH;
while (x >= WORLD_WIDTH) x -= WORLD_WIDTH;
return x;
}
static float worldDelta(float from, float to)
{
float delta = to - from;
if (delta > WORLD_WIDTH / 2.0f) delta -= WORLD_WIDTH;
if (delta < -WORLD_WIDTH / 2.0f) delta += WORLD_WIDTH;
return delta;
}
static void addScore(uint32_t points)
{
score = score > UINT32_MAX - points ? UINT32_MAX : score + points;
const uint8_t next_wave = (uint8_t)(score / 1500U + 1U);
if (next_wave > wave) wave = next_wave;
}
static float groundHeight(float world_x)
{
const float phase = world_x * 0.030f;
return GROUND_Y - 9.0f * sinf(phase) - 5.0f * sinf(phase * 2.7f);
}
static int nearestHuman(float x)
{
int found = -1;
float distance = WORLD_WIDTH;
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if (humans[i].state != HUMAN_GROUND) continue;
const float candidate = fabsf(worldDelta(x, humans[i].x));
if (candidate < distance) { distance = candidate; found = (int)i; }
}
return found;
}
static void spawnEnemy(EnemyType type)
{
for (unsigned i = 0; i < MAX_ENEMIES; ++i) {
if (enemies[i].object.active) continue;
const float x = wrapWorld(player.x + (esp_random() % 2U ? 190.0f : -190.0f));
const int human = type == LANDER ? nearestHuman(x) : -1;
enemies[i] = (Enemy){.object = {x, 28.0f + esp_random() % 55U, 0.0f, 0.0f, true},
.type = type, .human = human};
return;
}
}
static void resetWorld(void)
{
memset(shots, 0, sizeof(shots));
memset(shot_distance, 0, sizeof(shot_distance));
memset(enemies, 0, sizeof(enemies));
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
humans[i] = (Humanoid){(i + 1U) * WORLD_WIDTH / (HUMANOID_COUNT + 1U), 0.0f, HUMAN_GROUND};
humans[i].y = groundHeight(humans[i].x) - 8.0f;
}
for (unsigned i = 0; i < 4; ++i) spawnEnemy(LANDER);
}
static void beginGame(uint32_t now_ms)
{
score = 0; wave = 1; lives = 3; smart_bombs = 3; facing = 1; reverse_lag = 0;
player = (Object){WORLD_WIDTH / 2.0f, 75.0f, 0.0f, 0.0f, true};
fire_delay = bomb_delay = 0; spawn_delay = 1000U; accumulator = 0;
controls_ready = false; smart_bomb_held = false;
thrust_x_ms = thrust_y_ms = 0; thrust_x_direction = thrust_y_direction = 0;
resetWorld();
state = PLAYING;
last_update = now_ms;
}
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(shots, 0, sizeof(shots));
}
static void fireShot(void)
{
if (!player.active || fire_delay != 0) return;
for (unsigned i = 0; i < MAX_SHOTS; ++i) if (!shots[i].active) {
shots[i] = (Object){player.x + facing * 10.0f, player.y + 4.0f, facing * 250.0f, 0.0f, true};
shot_distance[i] = 0.0f;
fire_delay = FIRE_DELAY_MS;
audioPlay(AUDIO_SFX_PLAYER_SHOT);
return;
}
}
static void releaseLanderHuman(Enemy *enemy)
{
if (enemy->human < 0 || humans[enemy->human].state != HUMAN_ABDUCTED) return;
Humanoid *human = &humans[enemy->human];
human->x = enemy->object.x;
human->y = enemy->object.y + 10.0f;
if (groundHeight(human->x) - human->y > 16.0f) human->state = HUMAN_FALLING;
else {
human->y = groundHeight(human->x) - 8.0f;
human->state = HUMAN_GROUND;
}
enemy->human = -1;
}
static void smartBomb(void)
{
if (bomb_delay != 0 || smart_bombs == 0 || !player.active) return;
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].object.active) {
const float x = screenX(enemies[i].object.x);
if (x < -16.0f || x >= LCD_WIDTH + 16.0f) continue;
releaseLanderHuman(&enemies[i]);
enemies[i].object.active = false;
addScore(enemies[i].type == MUTANT ? 250U : 150U);
}
--smart_bombs;
bomb_delay = SMART_BOMB_DELAY_MS;
audioPlay(AUDIO_SFX_EXPLOSION);
}
static void updateEnemy(Enemy *enemy)
{
Object *object = &enemy->object;
if (!object->active) return;
const float dx = worldDelta(object->x, player.x);
if (enemy->type == LANDER) {
if (enemy->human >= 0 && humans[enemy->human].state == HUMAN_GROUND) {
const float target_x = humans[enemy->human].x;
object->x = wrapWorld(object->x + clampf(worldDelta(object->x, target_x), -35.0f, 35.0f) * STEP_MS / 1000.0f);
const float target_y = humans[enemy->human].y - 8.0f;
object->y += clampf(target_y - object->y, -24.0f, 24.0f) * STEP_MS / 1000.0f;
if (fabsf(worldDelta(object->x, target_x)) < 5.0f && fabsf(object->y - target_y) < 5.0f) {
humans[enemy->human].state = HUMAN_ABDUCTED;
}
} else if (enemy->human >= 0 && humans[enemy->human].state == HUMAN_ABDUCTED) {
humans[enemy->human].x = object->x;
humans[enemy->human].y = object->y + 10.0f;
object->y -= (30.0f + wave * 2.0f) * STEP_MS / 1000.0f;
if (object->y < PLAY_TOP) {
humans[enemy->human].state = HUMAN_LOST;
enemy->type = MUTANT;
enemy->human = -1;
object->y = PLAY_TOP + 4;
}
} else {
object->x = wrapWorld(object->x + (dx > 0 ? 20.0f : -20.0f) * STEP_MS / 1000.0f);
}
} else {
const float speed = enemy->type == MUTANT ? 58.0f + wave * 4.0f : 35.0f + wave * 2.0f;
object->x = wrapWorld(object->x + clampf(dx, -speed, speed) * STEP_MS / 1000.0f);
object->y += clampf(player.y - object->y, -speed * 0.45f, speed * 0.45f) * STEP_MS / 1000.0f;
}
if (fabsf(dx) < 12.0f && fabsf(object->y - player.y) < 12.0f) hitPlayer();
}
/* A rescued human follows the ship until it is returned to solid ground. */
static void updateHumanRescue(void)
{
int carried = -1;
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if (humans[i].state == HUMAN_CARRIED) { carried = (int)i; break; }
}
/* Catch an abducted human by flying beneath it. Only one can be carried. */
if (carried < 0) {
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if ((humans[i].state != HUMAN_ABDUCTED && humans[i].state != HUMAN_FALLING) ||
fabsf(worldDelta(player.x, humans[i].x)) >= 10.0f ||
fabsf((player.y + PLAYER_H) - humans[i].y) >= 12.0f) continue;
for (unsigned e = 0; e < MAX_ENEMIES; ++e) {
if (enemies[e].human == (int)i) enemies[e].human = -1;
}
humans[i].state = HUMAN_CARRIED;
carried = (int)i;
break;
}
}
if (carried < 0) return;
Humanoid *human = &humans[carried];
human->x = player.x;
human->y = player.y + PLAYER_H;
/* Landing near the terrain safely returns the human and awards rescue points. */
if (human->y >= groundHeight(player.x) - 1.0f) {
human->y = groundHeight(player.x) - 8.0f;
human->state = HUMAN_GROUND;
addScore(500U);
}
}
static void updateFallingHumans(void)
{
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
Humanoid *human = &humans[i];
if (human->state != HUMAN_FALLING) continue;
human->y += 75.0f * STEP_MS / 1000.0f;
if (human->y >= groundHeight(human->x) - 8.0f) {
human->y = groundHeight(human->x) - 8.0f;
human->state = HUMAN_GROUND;
}
}
}
static uint16_t updateThrustRamp(float input, int *direction, uint16_t held_ms)
{
const int new_direction = input > 0.15f ? 1 : input < -0.15f ? -1 : 0;
if (new_direction == 0) { *direction = 0; return 0; }
if (new_direction != *direction) { *direction = new_direction; return STEP_MS; }
return held_ms + STEP_MS > THRUST_RAMP_MS ? THRUST_RAMP_MS : held_ms + STEP_MS;
}
static void stepGame(float input_x, float input_y, bool fire, bool smart_bomb)
{
fire_delay = fire_delay > STEP_MS ? fire_delay - STEP_MS : 0;
bomb_delay = bomb_delay > STEP_MS ? bomb_delay - STEP_MS : 0;
reverse_lag = reverse_lag > STEP_MS ? reverse_lag - STEP_MS : 0;
if (!player.active) { if (last_update - state_started >= RESPAWN_MS) player = (Object){WORLD_WIDTH / 2.0f, 75.0f, 0, 0, true}; return; }
thrust_x_ms = updateThrustRamp(input_x, &thrust_x_direction, thrust_x_ms);
thrust_y_ms = updateThrustRamp(input_y, &thrust_y_direction, thrust_y_ms);
const float x_acceleration = reverse_lag ? 0.0f : 100.0f + 180.0f * (float)thrust_x_ms / THRUST_RAMP_MS;
const float y_acceleration = reverse_lag ? 0.0f : 100.0f + 180.0f * (float)thrust_y_ms / THRUST_RAMP_MS;
player.vx += input_x * x_acceleration * STEP_MS / 1000.0f;
player.vy += input_y * y_acceleration * STEP_MS / 1000.0f;
player.vx *= 0.975f; player.vy *= 0.975f;
player.vx = clampf(player.vx, -125.0f, 125.0f);
player.vy = clampf(player.vy, -95.0f, 95.0f);
player.x = wrapWorld(player.x + player.vx * STEP_MS / 1000.0f);
player.y = clampf(player.y + player.vy * STEP_MS / 1000.0f, PLAY_TOP + 4.0f, groundHeight(player.x) - PLAYER_H);
camera_x = player.x - LCD_WIDTH / 2.0f;
updateFallingHumans();
updateHumanRescue();
if (fire) fireShot();
if (smart_bomb) smartBomb();
for (unsigned i = 0; i < MAX_SHOTS; ++i) {
Object *shot = &shots[i]; if (!shot->active) continue;
shot->x = wrapWorld(shot->x + shot->vx * STEP_MS / 1000.0f);
shot_distance[i] += fabsf(shot->vx) * STEP_MS / 1000.0f;
if (shot_distance[i] > 320.0f) { shot->active = false; continue; }
for (unsigned e = 0; e < MAX_ENEMIES; ++e) {
Enemy *enemy = &enemies[e];
if (enemy->object.active && fabsf(worldDelta(shot->x, enemy->object.x)) < 10.0f && fabsf(shot->y - enemy->object.y) < 9.0f) {
releaseLanderHuman(enemy);
enemy->object.active = false; shot->active = false;
addScore(enemy->type == MUTANT ? 250U : enemy->type == BOMBER ? 150U : 150U);
audioPlay(AUDIO_SFX_HIT);
break;
}
}
}
for (unsigned i = 0; i < MAX_ENEMIES; ++i) updateEnemy(&enemies[i]);
spawn_delay = spawn_delay > STEP_MS ? spawn_delay - STEP_MS : 0;
if (spawn_delay == 0) { spawnEnemy((esp_random() % 8U) == 0 ? BOMBER : LANDER); spawn_delay = 900U - (wave > 5U ? 250U : wave * 50U); }
}
void startDefender(uint32_t now_ms) { running = true; beginGame(now_ms); state = READY; }
void stopDefender(void) { cancelHighScoreEntry(); running = false; }
void defenderAction(bool start, bool reverse, uint32_t now_ms)
{
if (!running) return;
if (start && state == READY) {
beginGame(now_ms);
audioPlay(AUDIO_SFX_GAME_START);
}
if (reverse && state == PLAYING && player.active) {
facing = -facing; player.vx *= 0.30f; reverse_lag = REVERSE_LAG_MS;
thrust_x_ms = thrust_y_ms = 0; thrust_x_direction = thrust_y_direction = 0;
}
}
void updateDefender(int x_offset, int y_offset, bool valid, bool fire, bool smart_bomb, 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_DEFENDER, score, now_ms); state = SCORES; }
if (state == SCORES) { if (updateHighScoreEntry(x_offset, y_offset, valid, now_ms)) state = READY; return; }
if (state != PLAYING) return;
if (!valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350) controls_ready = true;
const bool smart_bomb_pressed = smart_bomb && !smart_bomb_held;
smart_bomb_held = smart_bomb;
const float input_x = valid && controls_ready ? clampf((float)x_offset / 1600.0f, -1.0f, 1.0f) : 0.0f;
const float input_y = valid && controls_ready ? clampf((float)-y_offset / 1600.0f, -1.0f, 1.0f) : 0.0f;
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == PLAYING) { accumulator -= STEP_MS; stepGame(input_x, input_y, fire, smart_bomb_pressed); }
}
static float screenX(float world_x)
{
float x = worldDelta(camera_x, world_x);
if (x < -20.0f) x += WORLD_WIDTH;
return x;
}
void drawDefender(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == SCORES) { drawHighScoreEntry(frame); return; }
clear(frame, COLOR_BLACK);
for (int x = 0; x < LCD_WIDTH; ++x) {
const int ground = (int)groundHeight(camera_x + x);
fill_rect(frame, x, ground, 1, LCD_HEIGHT - ground, COLOR_DARKGREEN);
fill_rect(frame, x, ground, 1, 2, COLOR_LIME);
}
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
if (humans[i].state == HUMAN_LOST) continue;
const int x = (int)screenX(humans[i].x);
const uint16_t colour = humans[i].state == HUMAN_CARRIED ? COLOR_CYAN :
humans[i].state == HUMAN_FALLING ? COLOR_WHITE :
humans[i].state == HUMAN_ABDUCTED ? COLOR_ORANGE : COLOR_YELLOW;
if (x >= -8 && x < LCD_WIDTH) draw_bitmap_scaled(frame, x - 4, (int)humans[i].y,
defender_humanoid_bitmap, 8, 8, 8, 10, colour);
}
for (unsigned i = 0; i < MAX_ENEMIES; ++i) if (enemies[i].object.active) {
const Enemy *enemy = &enemies[i]; const int x = (int)screenX(enemy->object.x);
const uint8_t *bitmap = enemy->type == LANDER ? defender_lander_bitmap : enemy->type == MUTANT ? defender_mutant_bitmap : defender_bomber_bitmap;
const uint16_t colour = enemy->type == LANDER ? COLOR_MAGENTA : enemy->type == MUTANT ? COLOR_RED : COLOR_ORANGE;
draw_bitmap_scaled(frame, x - 8, (int)enemy->object.y, bitmap, 8, 8, 16, 12, colour);
}
// Laser bolts travel horizontally: a bright core with a short dotted cyan trail.
for (unsigned i = 0; i < MAX_SHOTS; ++i) if (shots[i].active) {
const int x = (int)screenX(shots[i].x);
const int y = (int)shots[i].y + 4;
const int direction = shots[i].vx < 0.0f ? -1 : 1;
if (x < -18 || x >= LCD_WIDTH + 18) continue;
draw_line(frame, x - direction * 31, y, x - direction * 4, y, COLOR_DARKCYAN);
fill_rect(frame, x - (direction < 0 ? 4 : 0), y - 1, 5, 3, COLOR_WHITE);
fill_rect(frame, x - direction * 36, y, 2, 1, COLOR_CYAN);
fill_rect(frame, x - direction * 44, y, 2, 1, COLOR_BLUE);
}
if (player.active) draw_bitmap_scaled(frame, 152, (int)player.y, facing > 0 ? defender_ship_right : defender_ship_left, 8, 8, PLAYER_W, PLAYER_H, COLOR_CYAN);
fill_rect(frame, 0, 0, LCD_WIDTH, PLAY_TOP, COLOR_BLACK);
for (unsigned i = 0; i < HUMANOID_COUNT; ++i) {
const int x = (int)(humans[i].x * LCD_WIDTH / WORLD_WIDTH);
const uint16_t colour = humans[i].state == HUMAN_LOST ? COLOR_RED :
humans[i].state == HUMAN_CARRIED ? COLOR_CYAN :
humans[i].state == HUMAN_FALLING ? COLOR_WHITE :
humans[i].state == HUMAN_ABDUCTED ? COLOR_ORANGE : COLOR_YELLOW;
fill_rect(frame, x, 2, 2, 4, colour);
}
char text[32]; snprintf(text, sizeof(text), "SCORE %u", (unsigned)score); drawPet2001TextTransparent(frame, 3, 7, text, COLOR_WHITE, 1);
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives); drawPet2001TextCentredTransparent(frame, 164, 7, text, COLOR_YELLOW, 1);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave); drawPet2001TextTransparent(frame, 205, 7, text, COLOR_CYAN, 1);
snprintf(text, sizeof(text), "BOMB %u", (unsigned)smart_bombs); drawPet2001TextTransparent(frame, 264, 7, text, COLOR_ORANGE, 1);
if (state == READY) {
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 21, "DEFENDER", COLOR_CYAN, 3);
drawPet2001TextCentredTransparent(frame, 160, 59, "RESCUE THE HUMANOIDS", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 76, "STICK FLY A FIRE", COLOR_YELLOW, 1);
drawPet2001TextCentredTransparent(frame, 160, 92, "C REVERSE B SMART BOMB", COLOR_ORANGE, 1);
drawPet2001TextCentredTransparent(frame, 160, 140, "BOOT BACK", COLOR_LIGHTGRAY, 1);
} else if (state == FINISHED) drawPet2001TextCentredTransparent(frame, 160, 78, "GAME OVER", COLOR_RED, 2);
}