Battle Zone
Source: src/battle_zone.c · 468 lines · 28 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
random_unit() · source line 38
static float random_unit(void)
- Exit this function
return (float)(esp_random() & 0xffffU) / 65535.0f;
Read original function source
static float random_unit(void)
{
return (float)(esp_random() & 0xffffU) / 65535.0f;
}clamp() · source line 43
static float clamp(float value, float minimum, float maximum)
- Exit this function
return value < minimum ? minimum : value > maximum ? maximum : value;
Read original function source
static float clamp(float value, float minimum, float maximum)
{
return value < minimum ? minimum : value > maximum ? maximum : value;
}wrap_angle() · source line 48
static float wrap_angle(float angle)
- WHILE loop
angle < -PIRepeat body while the loop condition permits; then continue below.
- Execute
angle += 2.0f * PI;
- WHILE loop
angle >= PIRepeat body while the loop condition permits; then continue below.
- Execute
angle -= 2.0f * PI;
- Exit this function
return angle;
Read original function source
static float wrap_angle(float angle)
{
while (angle < -PI) angle += 2.0f * PI;
while (angle >= PI) angle -= 2.0f * PI;
return angle;
}distance_squared() · source line 55
static float distance_squared(float ax, float az, float bx, float bz)
- Execute
const float dx = ax - bx, dz = az - bz; - Exit this function
return dx * dx + dz * dz;
Read original function source
static float distance_squared(float ax, float az, float bx, float bz)
{
const float dx = ax - bx, dz = az - bz;
return dx * dx + dz * dz;
}position_blocked() · source line 61
static bool position_blocked(float x, float z, float radius)
Calls into this documentation:
- FOR loop
uint8_t index = 0U; index < MAX_COVERS; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const float combined_radius = covers[index].radius + radius; - Decision
distance_squared(x, z, covers[index].x, covers[index].z) < combined_radius * combined_radiusTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool position_blocked(float x, float z, float radius)
{
for (uint8_t index = 0U; index < MAX_COVERS; ++index) {
const float combined_radius = covers[index].radius + radius;
if (distance_squared(x, z, covers[index].x, covers[index].z) < combined_radius * combined_radius)
return true;
}
return false;
}reset_tank() · source line 71
static void reset_tank(uint32_t now_ms)
Calls into this documentation:
- Execute
tank_x = tank_z = 0.0f; - Execute
tank_angle = -PI / 2.0f; - Execute
protection_until = now_ms + PLAYER_PROTECTION_MS; - Execute
engine_running = false; - Execute
audioSetEngineThrust(false); - Execute
controls_ready = false;
Read original function source
static void reset_tank(uint32_t now_ms)
{
tank_x = tank_z = 0.0f;
tank_angle = -PI / 2.0f;
protection_until = now_ms + PLAYER_PROTECTION_MS;
engine_running = false;
audioSetEngineThrust(false);
controls_ready = false;
}spawn_wave() · source line 81
static void spawn_wave(void)
Calls into this documentation:
- Execute
memset(enemies, 0, sizeof(enemies)); - Execute
memset(shots, 0, sizeof(shots)); - Execute
covers[0] = (cover_t){-32.0f, -26.0f, 7.0f}; - Execute
covers[1] = (cover_t){31.0f, -31.0f, 8.0f}; - Execute
covers[2] = (cover_t){-43.0f, 24.0f, 6.0f}; - Execute
covers[3] = (cover_t){37.0f, 31.0f, 7.0f}; - Execute
covers[4] = (cover_t){0.0f, 57.0f, 7.0f}; - Execute
const uint8_t count = wave < 6U ? (uint8_t)(wave + 1U) : MAX_ENEMIES; - FOR loop
uint8_t index = 0U; index < count; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
float x, z; - DO / WHILE loop
position_blocked(x, z, 3.5f)Run body, then test condition; repeat while true.
- Execute
const float angle = random_unit() * 2.0f * PI; - Execute
const float distance = 42.0f + random_unit() * 42.0f; - Execute
x = cosf(angle) * distance; - Execute
z = sinf(angle) * distance;
- Execute
enemies[index] = (enemy_t){x, z, true, 900U + (uint32_t)(random_unit() * 1600.0f)};
Read original function source
static void spawn_wave(void)
{
memset(enemies, 0, sizeof(enemies));
memset(shots, 0, sizeof(shots));
covers[0] = (cover_t){-32.0f, -26.0f, 7.0f};
covers[1] = (cover_t){31.0f, -31.0f, 8.0f};
covers[2] = (cover_t){-43.0f, 24.0f, 6.0f};
covers[3] = (cover_t){37.0f, 31.0f, 7.0f};
covers[4] = (cover_t){0.0f, 57.0f, 7.0f};
const uint8_t count = wave < 6U ? (uint8_t)(wave + 1U) : MAX_ENEMIES;
for (uint8_t index = 0U; index < count; ++index) {
float x, z;
do {
const float angle = random_unit() * 2.0f * PI;
const float distance = 42.0f + random_unit() * 42.0f;
x = cosf(angle) * distance;
z = sinf(angle) * distance;
} while (position_blocked(x, z, 3.5f));
enemies[index] = (enemy_t){x, z, true,
900U + (uint32_t)(random_unit() * 1600.0f)};
}
}begin_game() · source line 104
static void begin_game(uint32_t now_ms)
Calls into this documentation:
- Execute
score = 0U; - Execute
wave = 1U; - Execute
lives = 3U; - Execute
fire_due = accumulator = 0U; - Execute
last_update = now_ms; - Execute
reset_tank(now_ms); - Execute
spawn_wave(); - Execute
state = BZ_PLAYING; - Execute
audioPlay(AUDIO_SFX_GAME_START);
Read original function source
static void begin_game(uint32_t now_ms)
{
score = 0U;
wave = 1U;
lives = 3U;
fire_due = accumulator = 0U;
last_update = now_ms;
reset_tank(now_ms);
spawn_wave();
state = BZ_PLAYING;
audioPlay(AUDIO_SFX_GAME_START);
}fire_shot() · source line 117
static void fire_shot(bool enemy_shot, float x, float z, float angle)
Calls into this documentation:
- FOR loop
uint8_t index = 0U; index < MAX_SHOTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
shots[index].remaining != 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float speed = enemy_shot ? ENEMY_SHOT_SPEED : PLAYER_SHOT_SPEED; - Execute
shots[index] = (shot_t){x, z, cosf(angle) * speed, sinf(angle) * speed, enemy_shot ? 1800U : 1300U, enemy_shot}; - Execute
audioPlay(enemy_shot ? AUDIO_SFX_ENEMY_SHOT : AUDIO_SFX_PLAYER_SHOT); - Exit this function
return;
Read original function source
static void fire_shot(bool enemy_shot, float x, float z, float angle)
{
for (uint8_t index = 0U; index < MAX_SHOTS; ++index) {
if (shots[index].remaining != 0U) continue;
const float speed = enemy_shot ? ENEMY_SHOT_SPEED : PLAYER_SHOT_SPEED;
shots[index] = (shot_t){x, z, cosf(angle) * speed, sinf(angle) * speed,
enemy_shot ? 1800U : 1300U, enemy_shot};
audioPlay(enemy_shot ? AUDIO_SFX_ENEMY_SHOT : AUDIO_SFX_PLAYER_SHOT);
return;
}
}fire_player_shot() · source line 129
static void fire_player_shot(uint32_t now_ms)
Calls into this documentation:
- Decision
fire_due != 0UTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
fire_shot(false, tank_x + cosf(tank_angle) * 4.0f, tank_z + sinf(tank_angle) * 4.0f, tank_angle); - Execute
fire_due = PLAYER_SHOT_DELAY_MS; - Execute
(void)now_ms;
Read original function source
static void fire_player_shot(uint32_t now_ms)
{
if (fire_due != 0U) return;
fire_shot(false, tank_x + cosf(tank_angle) * 4.0f, tank_z + sinf(tank_angle) * 4.0f,
tank_angle);
fire_due = PLAYER_SHOT_DELAY_MS;
(void)now_ms;
}destroy_enemy() · source line 138
static void destroy_enemy(uint8_t index)
Calls into this documentation:
- Execute
enemies[index].active = false; - Execute
score += 100U; - Execute
audioPlay(AUDIO_SFX_HIT);
Read original function source
static void destroy_enemy(uint8_t index)
{
enemies[index].active = false;
score += 100U;
audioPlay(AUDIO_SFX_HIT);
}lose_life() · source line 145
static void lose_life(uint32_t now_ms)
Calls into this documentation:
- Decision
now_ms < protection_untilTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
lives != 0UTrue- Execute
--lives;
FalseContinue after this decision.
- Execute
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION); - Execute
audioSetEngineThrust(false); - Execute
engine_running = false; - Decision
lives == 0UTrue- Execute
state = BZ_FINISHED;
False- Execute
memset(shots, 0, sizeof(shots)); - Execute
reset_tank(now_ms);
Read original function source
static void lose_life(uint32_t now_ms)
{
if (now_ms < protection_until) return;
if (lives != 0U) --lives;
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION);
audioSetEngineThrust(false);
engine_running = false;
if (lives == 0U) {
state = BZ_FINISHED;
} else {
memset(shots, 0, sizeof(shots));
reset_tank(now_ms);
}
}choose_auto_controls() · source line 160
static void choose_auto_controls(int *turn, float *drive, bool *fire)
Calls into this documentation:
- Execute
const enemy_t *target = NULL; - Execute
float best_distance = 0.0f; - FOR loop
uint8_t index = 0U; index < MAX_ENEMIES; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
!enemies[index].activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float distance = distance_squared(tank_x, tank_z, enemies[index].x, enemies[index].z); - Decision
target == NULL || distance < best_distanceTrue- Execute
target = &enemies[index]; - Execute
best_distance = distance;
FalseContinue after this decision.
- Decision
target == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const float dx = target->x - tank_x, dz = target->z - tank_z; - Execute
const float cross = cosf(tank_angle) * dz - sinf(tank_angle) * dx; - Execute
const float forward = cosf(tank_angle) * dx + sinf(tank_angle) * dz; - Execute
*turn = cross > 2.0f ? 1 : cross < -2.0f ? -1 : 0; - Execute
*drive = forward < 18.0f ? -0.6f : forward > 50.0f ? 0.7f : 0.0f; - Execute
*fire = forward > 0.0f && fabsf(cross) < 3.5f;
Read original function source
static void choose_auto_controls(int *turn, float *drive, bool *fire)
{
const enemy_t *target = NULL;
float best_distance = 0.0f;
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) {
if (!enemies[index].active) continue;
const float distance = distance_squared(tank_x, tank_z, enemies[index].x, enemies[index].z);
if (target == NULL || distance < best_distance) { target = &enemies[index]; best_distance = distance; }
}
if (target == NULL) return;
const float dx = target->x - tank_x, dz = target->z - tank_z;
const float cross = cosf(tank_angle) * dz - sinf(tank_angle) * dx;
const float forward = cosf(tank_angle) * dx + sinf(tank_angle) * dz;
*turn = cross > 2.0f ? 1 : cross < -2.0f ? -1 : 0;
*drive = forward < 18.0f ? -0.6f : forward > 50.0f ? 0.7f : 0.0f;
*fire = forward > 0.0f && fabsf(cross) < 3.5f;
}update_enemies() · source line 178
static void update_enemies(uint32_t now_ms, float seconds)
Calls into this documentation:
- FOR loop
uint8_t index = 0U; index < MAX_ENEMIES; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
enemy_t *enemy = &enemies[index]; - Decision
!enemy->activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float dx = tank_x - enemy->x, dz = tank_z - enemy->z; - Execute
const float distance = sqrtf(dx * dx + dz * dz); - Decision
distance > 0.1fTrue- Execute
const float strafe = (index & 1U) == 0U ? 1.0f : -1.0f; - Execute
const float next_x = enemy->x + (dx / distance * 6.0f - dz / distance * strafe * 2.0f) * seconds; - Execute
const float next_z = enemy->z + (dz / distance * 6.0f + dx / distance * strafe * 2.0f) * seconds; - Decision
!position_blocked(next_x, next_z, 3.5f)True- Execute
enemy->x = next_x; - Execute
enemy->z = next_z;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
now_ms >= enemy->fire_due && distance < 66.0fTrue- Execute
fire_shot(true, enemy->x, enemy->z, atan2f(dz, dx)); - Execute
enemy->fire_due = now_ms + 1200U + (uint32_t)(random_unit() * 1100.0f);
FalseContinue after this decision.
- Decision
distance < 5.0fTrue- Execute
lose_life(now_ms);
FalseContinue after this decision.
Read original function source
static void update_enemies(uint32_t now_ms, float seconds)
{
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) {
enemy_t *enemy = &enemies[index];
if (!enemy->active) continue;
const float dx = tank_x - enemy->x, dz = tank_z - enemy->z;
const float distance = sqrtf(dx * dx + dz * dz);
if (distance > 0.1f) {
const float strafe = (index & 1U) == 0U ? 1.0f : -1.0f;
const float next_x = enemy->x + (dx / distance * 6.0f - dz / distance * strafe * 2.0f) * seconds;
const float next_z = enemy->z + (dz / distance * 6.0f + dx / distance * strafe * 2.0f) * seconds;
if (!position_blocked(next_x, next_z, 3.5f)) {
enemy->x = next_x;
enemy->z = next_z;
}
}
if (now_ms >= enemy->fire_due && distance < 66.0f) {
fire_shot(true, enemy->x, enemy->z, atan2f(dz, dx));
enemy->fire_due = now_ms + 1200U + (uint32_t)(random_unit() * 1100.0f);
}
if (distance < 5.0f) lose_life(now_ms);
}
}update_shots() · source line 202
static void update_shots(uint32_t now_ms, float seconds)
Calls into this documentation:
- FOR loop
uint8_t shot_index = 0U; shot_index < MAX_SHOTS; ++shot_indexRepeat body while the loop condition permits; then continue below.
- Execute
shot_t *shot = &shots[shot_index]; - Decision
shot->remaining == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
shot->x += shot->dx * seconds; - Execute
shot->z += shot->dz * seconds; - Execute
shot->remaining = shot->remaining > STEP_MS ? shot->remaining - STEP_MS : 0U; - FOR loop
uint8_t cover_index = 0U; cover_index < MAX_COVERS; ++cover_indexRepeat body while the loop condition permits; then continue below.
- Execute
const cover_t *cover = &covers[cover_index]; - Decision
distance_squared(shot->x, shot->z, cover->x, cover->z) >= cover->radius * cover->radiusTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
shot->remaining = 0U; - Exit enclosing loop or switch
break;
- Decision
shot->remaining == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
shot->enemy_shotTrue- Decision
distance_squared(shot->x, shot->z, tank_x, tank_z) < 14.0fTrue- Execute
shot->remaining = 0U; - Execute
lose_life(now_ms);
FalseContinue after this decision.
- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
uint8_t enemy_index = 0U; enemy_index < MAX_ENEMIES; ++enemy_indexRepeat body while the loop condition permits; then continue below.
- Decision
!enemies[enemy_index].activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
distance_squared(shot->x, shot->z, enemies[enemy_index].x, enemies[enemy_index].z) >= 18.0fTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
shot->remaining = 0U; - Execute
destroy_enemy(enemy_index); - Exit enclosing loop or switch
break;
Read original function source
static void update_shots(uint32_t now_ms, float seconds)
{
for (uint8_t shot_index = 0U; shot_index < MAX_SHOTS; ++shot_index) {
shot_t *shot = &shots[shot_index];
if (shot->remaining == 0U) continue;
shot->x += shot->dx * seconds;
shot->z += shot->dz * seconds;
shot->remaining = shot->remaining > STEP_MS ? shot->remaining - STEP_MS : 0U;
for (uint8_t cover_index = 0U; cover_index < MAX_COVERS; ++cover_index) {
const cover_t *cover = &covers[cover_index];
if (distance_squared(shot->x, shot->z, cover->x, cover->z) >= cover->radius * cover->radius) continue;
shot->remaining = 0U;
break;
}
if (shot->remaining == 0U) continue;
if (shot->enemy_shot) {
if (distance_squared(shot->x, shot->z, tank_x, tank_z) < 14.0f) {
shot->remaining = 0U;
lose_life(now_ms);
}
continue;
}
for (uint8_t enemy_index = 0U; enemy_index < MAX_ENEMIES; ++enemy_index) {
if (!enemies[enemy_index].active) continue;
if (distance_squared(shot->x, shot->z, enemies[enemy_index].x, enemies[enemy_index].z) >= 18.0f) continue;
shot->remaining = 0U;
destroy_enemy(enemy_index);
break;
}
}
}enemies_remaining() · source line 234
static bool enemies_remaining(void)
- FOR loop
uint8_t index = 0U; index < MAX_ENEMIES; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
enemies[index].activeTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool enemies_remaining(void)
{
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) if (enemies[index].active) return true;
return false;
}simulate_step() · source line 240
static void simulate_step(int turn, float drive, bool fire, uint32_t now_ms)
Calls into this documentation:
- Execute
const float seconds = STEP_MS / 1000.0f; - Decision
fire_due > 0UTrue- Execute
fire_due = fire_due > STEP_MS ? fire_due - STEP_MS : 0U;
FalseContinue after this decision.
- Execute
tank_angle = wrap_angle(tank_angle + (float)turn * 2.25f * seconds); - Execute
const bool moving = drive > 0.05f || drive < -0.05f; - Decision
engine_running != movingTrue- Execute
engine_running = moving; - Execute
audioSetEngineThrust(moving);
FalseContinue after this decision.
- Execute
const float next_x = clamp(tank_x + cosf(tank_angle) * drive * 30.0f * seconds, -ARENA_HALF_SIZE, ARENA_HALF_SIZE); - Execute
const float next_z = clamp(tank_z + sinf(tank_angle) * drive * 30.0f * seconds, -ARENA_HALF_SIZE, ARENA_HALF_SIZE); - Decision
!position_blocked(next_x, next_z, 4.0f)True- Execute
tank_x = next_x; - Execute
tank_z = next_z;
FalseContinue after this decision.
- Decision
fireTrue- Execute
fire_player_shot(now_ms);
FalseContinue after this decision.
- Execute
update_enemies(now_ms, seconds); - Execute
update_shots(now_ms, seconds); - Decision
!enemies_remaining() && state == BZ_PLAYINGTrue- Execute
audioPlay(AUDIO_SFX_WAVE_CLEAR); - Execute
++wave; - Execute
spawn_wave();
FalseContinue after this decision.
- Decision
now_ms >= radar_dueTrue- Execute
audioPlay(AUDIO_SFX_MENU_MOVE); - Execute
radar_due = now_ms + 950U;
FalseContinue after this decision.
Read original function source
static void simulate_step(int turn, float drive, bool fire, uint32_t now_ms)
{
const float seconds = STEP_MS / 1000.0f;
if (fire_due > 0U) fire_due = fire_due > STEP_MS ? fire_due - STEP_MS : 0U;
tank_angle = wrap_angle(tank_angle + (float)turn * 2.25f * seconds);
const bool moving = drive > 0.05f || drive < -0.05f;
if (engine_running != moving) {
engine_running = moving;
audioSetEngineThrust(moving);
}
const float next_x = clamp(tank_x + cosf(tank_angle) * drive * 30.0f * seconds,
-ARENA_HALF_SIZE, ARENA_HALF_SIZE);
const float next_z = clamp(tank_z + sinf(tank_angle) * drive * 30.0f * seconds,
-ARENA_HALF_SIZE, ARENA_HALF_SIZE);
if (!position_blocked(next_x, next_z, 4.0f)) {
tank_x = next_x;
tank_z = next_z;
}
if (fire) fire_player_shot(now_ms);
update_enemies(now_ms, seconds);
update_shots(now_ms, seconds);
if (!enemies_remaining() && state == BZ_PLAYING) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
++wave;
spawn_wave();
}
if (now_ms >= radar_due) {
audioPlay(AUDIO_SFX_MENU_MOVE);
radar_due = now_ms + 950U;
}
}startBattleZone() · source line 272
void startBattleZone(uint32_t now_ms)
Calls into this documentation:
- Execute
running = true; - Execute
state = BZ_READY; - Execute
auto_mode = false; - Execute
last_update = now_ms; - Execute
radar_due = now_ms + 700U; - Execute
modeSelectionReset(&mode_input);
Read original function source
void startBattleZone(uint32_t now_ms)
{
running = true;
state = BZ_READY;
auto_mode = false;
last_update = now_ms;
radar_due = now_ms + 700U;
modeSelectionReset(&mode_input);
}stopBattleZone() · source line 282
void stopBattleZone(void)
Calls into this documentation:
- Execute
audioSetEngineThrust(false); - Execute
engine_running = false; - Execute
running = false;
Read original function source
void stopBattleZone(void)
{
audioSetEngineThrust(false);
engine_running = false;
running = false;
}battleZoneAction() · source line 289
void battleZoneAction(bool start, bool stop_auto, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == BZ_READY && startTrue- Execute
begin_game(now_ms);
False- Decision
state == BZ_FINISHED && startTrue- Execute
begin_game(now_ms);
False- Decision
state == BZ_PLAYING && auto_mode && stop_autoTrue- Execute
auto_mode = false; - Execute
modeSelectionReset(&mode_input); - Execute
audioSetEngineThrust(false); - Execute
engine_running = false; - Execute
state = BZ_READY;
FalseContinue after this decision.
Read original function source
void battleZoneAction(bool start, bool stop_auto, uint32_t now_ms)
{
if (!running) return;
if (state == BZ_READY && start) begin_game(now_ms);
else if (state == BZ_FINISHED && start) begin_game(now_ms);
else if (state == BZ_PLAYING && auto_mode && stop_auto) {
auto_mode = false;
modeSelectionReset(&mode_input);
audioSetEngineThrust(false);
engine_running = false;
state = BZ_READY;
}
}updateBattleZone() · source line 303
void updateBattleZone(int x_offset, int y_offset, bool valid, bool left, bool right,
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 == BZ_READYTrue- Decision
modeSelectionNext(&mode_input, x_offset, y_offset, valid, left, right, now_ms)True- Execute
auto_mode = !auto_mode;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state != BZ_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
int turn = left || right ? (int)right - (int)left : valid && controls_ready ? (x_offset > 600 ? 1 : x_offset < -600 ? -1 : 0) : 0; - Execute
float drive = valid && controls_ready ? (y_offset > 600 ? 1.0f : y_offset < -600 ? -0.65f : 0.0f) : 0.0f; - Execute
bool should_fire = fire; - Decision
auto_modeTrue- Execute
choose_auto_controls(&turn, &drive, &should_fire);
FalseContinue after this decision.
- Execute
accumulator += elapsed > 100U ? 100U : elapsed; - WHILE loop
accumulator >= STEP_MS && state == BZ_PLAYINGRepeat body while the loop condition permits; then continue below.
- Execute
accumulator -= STEP_MS; - Execute
simulate_step(turn, drive, should_fire, now_ms);
Read original function source
void updateBattleZone(int x_offset, int y_offset, bool valid, bool left, bool right,
bool fire, uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - last_update;
last_update = now_ms;
if (state == BZ_READY) {
if (modeSelectionNext(&mode_input, x_offset, y_offset, valid, left, right, now_ms)) auto_mode = !auto_mode;
return;
}
if (state != BZ_PLAYING) return;
if (!valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350) controls_ready = true;
int turn = left || right ? (int)right - (int)left :
valid && controls_ready ? (x_offset > 600 ? 1 : x_offset < -600 ? -1 : 0) : 0;
float drive = valid && controls_ready ? (y_offset > 600 ? 1.0f : y_offset < -600 ? -0.65f : 0.0f) : 0.0f;
bool should_fire = fire;
if (auto_mode) choose_auto_controls(&turn, &drive, &should_fire);
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == BZ_PLAYING) {
accumulator -= STEP_MS;
simulate_step(turn, drive, should_fire, now_ms);
}
}project() · source line 328
static bool project(float world_x, float world_z, float *side, float *forward)
- Execute
const float dx = world_x - tank_x, dz = world_z - tank_z; - Execute
*side = -sinf(tank_angle) * dx + cosf(tank_angle) * dz; - Execute
*forward = cosf(tank_angle) * dx + sinf(tank_angle) * dz; - Exit this function
return *forward > 3.0f;
Read original function source
static bool project(float world_x, float world_z, float *side, float *forward)
{
const float dx = world_x - tank_x, dz = world_z - tank_z;
*side = -sinf(tank_angle) * dx + cosf(tank_angle) * dz;
*forward = cosf(tank_angle) * dx + sinf(tank_angle) * dz;
return *forward > 3.0f;
}drawBattleZoneLandscape() · source line 336
void drawBattleZoneLandscape(uint16_t *frame)
Calls into this documentation:
- Execute
const uint16_t green = COLOR_LIME; - Execute
const int horizon = BATTLE_ZONE_HORIZON_Y; - Execute
static const int terrain_bands[] = {58, 76, 101, 134, 166}; - FOR loop
size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const int y = terrain_bands[index]; - Execute
draw_line(frame, 0, y, LCD_WIDTH - 1, y, COLOR_DARKGREEN);
- FOR loop
int lane = -3; lane <= 3; ++laneRepeat body while the loop condition permits; then continue below.
- Execute
draw_line(frame, 160 + lane * 10, horizon, 160 + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
- Execute
draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, green); - Execute
draw_line(frame, 15, horizon, 58, horizon - 12, COLOR_DARKGREEN); - Execute
draw_line(frame, 58, horizon - 12, 104, horizon, COLOR_DARKGREEN); - Execute
draw_line(frame, 212, horizon, 258, horizon - 15, COLOR_DARKGREEN); - Execute
draw_line(frame, 258, horizon - 15, 307, horizon, COLOR_DARKGREEN);
Read original function source
void drawBattleZoneLandscape(uint16_t *frame)
{
const uint16_t green = COLOR_LIME;
const int horizon = BATTLE_ZONE_HORIZON_Y;
static const int terrain_bands[] = {58, 76, 101, 134, 166};
for (size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++index) {
const int y = terrain_bands[index];
draw_line(frame, 0, y, LCD_WIDTH - 1, y, COLOR_DARKGREEN);
}
for (int lane = -3; lane <= 3; ++lane)
draw_line(frame, 160 + lane * 10, horizon, 160 + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, green);
draw_line(frame, 15, horizon, 58, horizon - 12, COLOR_DARKGREEN);
draw_line(frame, 58, horizon - 12, 104, horizon, COLOR_DARKGREEN);
draw_line(frame, 212, horizon, 258, horizon - 15, COLOR_DARKGREEN);
draw_line(frame, 258, horizon - 15, 307, horizon, COLOR_DARKGREEN);
}draw_enemy() · source line 354
static void draw_enemy(uint16_t *frame, const enemy_t *enemy)
Calls into this documentation:
- Execute
float side, forward; - Decision
!project(enemy->x, enemy->z, &side, &forward) || fabsf(side) > forward * 1.7fTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const int centre_x = (int)lroundf(160.0f + side * 108.0f / forward); - Execute
const int base_y = (int)lroundf(43.0f + 118.0f / forward); - Execute
const int width = (int)clamp(150.0f / forward, 4.0f, 38.0f); - Execute
const int height = width / 2 + 2; - Execute
draw_rect(frame, centre_x - width / 2, base_y - height, width, height, COLOR_LIME); - Execute
draw_line(frame, centre_x - width / 2 - 2, base_y, centre_x + width / 2 + 2, base_y, COLOR_LIME); - Execute
draw_line(frame, centre_x, base_y - height, centre_x, base_y - height - width / 2, COLOR_LIME);
Read original function source
static void draw_enemy(uint16_t *frame, const enemy_t *enemy)
{
float side, forward;
if (!project(enemy->x, enemy->z, &side, &forward) || fabsf(side) > forward * 1.7f) return;
const int centre_x = (int)lroundf(160.0f + side * 108.0f / forward);
const int base_y = (int)lroundf(43.0f + 118.0f / forward);
const int width = (int)clamp(150.0f / forward, 4.0f, 38.0f);
const int height = width / 2 + 2;
draw_rect(frame, centre_x - width / 2, base_y - height, width, height, COLOR_LIME);
draw_line(frame, centre_x - width / 2 - 2, base_y, centre_x + width / 2 + 2, base_y, COLOR_LIME);
draw_line(frame, centre_x, base_y - height, centre_x, base_y - height - width / 2, COLOR_LIME);
}draw_cover() · source line 367
static void draw_cover(uint16_t *frame, const cover_t *cover)
Calls into this documentation:
- Execute
float side, forward; - Decision
!project(cover->x, cover->z, &side, &forward) || fabsf(side) > forward * 1.7fTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const int centre_x = (int)lroundf(160.0f + side * 108.0f / forward); - Execute
const int base_y = (int)lroundf(43.0f + 118.0f / forward); - Execute
const int width = (int)clamp(230.0f / forward, 7.0f, 54.0f); - Execute
const int height = width / 2 + 5; - Execute
draw_rect(frame, centre_x - width / 2, base_y - height, width, height, COLOR_LIME); - Execute
draw_line(frame, centre_x - width / 2 - 3, base_y - height, centre_x, base_y - height - height / 2, COLOR_LIME); - Execute
draw_line(frame, centre_x, base_y - height - height / 2, centre_x + width / 2 + 3, base_y - height, COLOR_LIME);
Read original function source
static void draw_cover(uint16_t *frame, const cover_t *cover)
{
float side, forward;
if (!project(cover->x, cover->z, &side, &forward) || fabsf(side) > forward * 1.7f) return;
const int centre_x = (int)lroundf(160.0f + side * 108.0f / forward);
const int base_y = (int)lroundf(43.0f + 118.0f / forward);
const int width = (int)clamp(230.0f / forward, 7.0f, 54.0f);
const int height = width / 2 + 5;
draw_rect(frame, centre_x - width / 2, base_y - height, width, height, COLOR_LIME);
draw_line(frame, centre_x - width / 2 - 3, base_y - height,
centre_x, base_y - height - height / 2, COLOR_LIME);
draw_line(frame, centre_x, base_y - height - height / 2,
centre_x + width / 2 + 3, base_y - height, COLOR_LIME);
}draw_radar_point() · source line 382
static void draw_radar_point(uint16_t *frame, int centre_x, int centre_y, int radius,
float world_x, float world_z, uint16_t colour)Calls into this documentation:
- Execute
float offset_x = world_x - tank_x; - Execute
float offset_z = world_z - tank_z; - Execute
const float distance = sqrtf(offset_x * offset_x + offset_z * offset_z); - Execute
const float maximum_distance = ARENA_HALF_SIZE; - Decision
distance > maximum_distanceTrue- Execute
offset_x *= maximum_distance / distance; - Execute
offset_z *= maximum_distance / distance;
FalseContinue after this decision.
- Execute
const int x = centre_x + (int)(offset_x * (radius - 3) / maximum_distance); - Execute
const int y = centre_y + (int)(offset_z * (radius - 3) / maximum_distance); - Execute
fill_circle(frame, x, y, 2, colour);
Read original function source
static void draw_radar_point(uint16_t *frame, int centre_x, int centre_y, int radius,
float world_x, float world_z, uint16_t colour)
{
float offset_x = world_x - tank_x;
float offset_z = world_z - tank_z;
const float distance = sqrtf(offset_x * offset_x + offset_z * offset_z);
const float maximum_distance = ARENA_HALF_SIZE;
if (distance > maximum_distance) {
offset_x *= maximum_distance / distance;
offset_z *= maximum_distance / distance;
}
const int x = centre_x + (int)(offset_x * (radius - 3) / maximum_distance);
const int y = centre_y + (int)(offset_z * (radius - 3) / maximum_distance);
fill_circle(frame, x, y, 2, colour);
}draw_radar() · source line 398
static void draw_radar(uint16_t *frame)
Calls into this documentation:
- Execute
const int centre_x = 284, centre_y = 135, radius = 25; - Execute
draw_ring(frame, centre_x, centre_y, radius, COLOR_DARKGREEN); - Execute
draw_line(frame, centre_x - radius, centre_y, centre_x + radius, centre_y, COLOR_DARKGREEN); - Execute
draw_line(frame, centre_x, centre_y - radius, centre_x, centre_y + radius, COLOR_DARKGREEN); - Execute
fill_circle(frame, centre_x, centre_y, 2, COLOR_LIME); - Execute
draw_line(frame, centre_x, centre_y, centre_x + (int)(cosf(tank_angle) * 6.0f), centre_y + (int)(sinf(tank_angle) * 6.0f), COLOR_LIME); - FOR loop
uint8_t index = 0U; index < MAX_ENEMIES; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
!enemies[index].activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
draw_radar_point(frame, centre_x, centre_y, radius, enemies[index].x, enemies[index].z, COLOR_RED);
- FOR loop
uint8_t index = 0U; index < MAX_COVERS; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
draw_radar_point(frame, centre_x, centre_y, radius, covers[index].x, covers[index].z, COLOR_YELLOW);
Read original function source
static void draw_radar(uint16_t *frame)
{
const int centre_x = 284, centre_y = 135, radius = 25;
draw_ring(frame, centre_x, centre_y, radius, COLOR_DARKGREEN);
draw_line(frame, centre_x - radius, centre_y, centre_x + radius, centre_y, COLOR_DARKGREEN);
draw_line(frame, centre_x, centre_y - radius, centre_x, centre_y + radius, COLOR_DARKGREEN);
fill_circle(frame, centre_x, centre_y, 2, COLOR_LIME);
draw_line(frame, centre_x, centre_y,
centre_x + (int)(cosf(tank_angle) * 6.0f),
centre_y + (int)(sinf(tank_angle) * 6.0f), COLOR_LIME);
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) {
if (!enemies[index].active) continue;
draw_radar_point(frame, centre_x, centre_y, radius,
enemies[index].x, enemies[index].z, COLOR_RED);
}
for (uint8_t index = 0U; index < MAX_COVERS; ++index) {
draw_radar_point(frame, centre_x, centre_y, radius,
covers[index].x, covers[index].z, COLOR_YELLOW);
}
}drawBattleZone() · source line 419
void drawBattleZone(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
char text[40]; - Decision
state == BZ_READYTrue- Execute
draw_text_centred(frame, 160, 15, "BATTLE ZONE", 3, COLOR_LIME); - Execute
draw_text_centred(frame, 83, 55, "PLAY", 2, auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW); - Execute
draw_text_centred(frame, 237, 55, "AUTO", 2, auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 160, 92, "D/B/STICK CHANGE A/K START", 1, COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 160, 111, "D/B TURN STICK UP/DOWN DRIVE", 1, COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 160, 130, "HOLD A/K FIRE", 1, COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 160, 155, "BOOT BACK", 1, COLOR_DARKGREY); - Exit this function
return;
FalseContinue after this decision.
- Execute
drawBattleZoneLandscape(frame); - FOR loop
uint8_t index = 0U; index < MAX_COVERS; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
draw_cover(frame, &covers[index]);
- FOR loop
uint8_t index = 0U; index < MAX_ENEMIES; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
enemies[index].activeTrue- Execute
draw_enemy(frame, &enemies[index]);
FalseContinue after this decision.
- FOR loop
uint8_t index = 0U; index < MAX_SHOTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
shots[index].remaining == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
float side, forward; - Decision
!project(shots[index].x, shots[index].z, &side, &forward) || fabsf(side) > forward * 1.7fTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int x = (int)lroundf(160.0f + side * 108.0f / forward); - Execute
const int y = (int)lroundf(43.0f + 118.0f / forward); - Execute
const float size = shots[index].enemy_shot ? 26.0f / forward : 11.0f / forward; - Execute
const int radius = (int)clamp(size + 1.0f, 1.0f, shots[index].enemy_shot ? 7.0f : 3.0f); - Execute
fill_rect(frame, x - radius, y - radius, radius * 2 + 1, radius * 2 + 1, shots[index].enemy_shot ? COLOR_RED : COLOR_YELLOW);
- Execute
draw_radar(frame); - Execute
snprintf(text, sizeof(text), "SCORE %u", (unsigned)score); - Execute
draw_text(frame, 4, 3, text, 1, COLOR_LIME); - Execute
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave); - Execute
draw_text_centred(frame, 160, 3, text, 1, COLOR_LIME); - Execute
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives); - Execute
draw_text(frame, 258, 3, text, 1, COLOR_LIME); - Decision
protection_until > last_update && ((protection_until - last_update) / 120U) % 2U == 0UTrue- Execute
draw_text_centred(frame, 160, 154, "READY", 1, COLOR_YELLOW);
FalseContinue after this decision.
- Decision
auto_modeTrue- Execute
draw_text(frame, 4, 154, "AUTO", 1, COLOR_CYAN);
FalseContinue after this decision.
- Decision
state == BZ_FINISHEDTrue- Execute
fill_rect(frame, 48, 66, 224, 38, COLOR_BLACK); - Execute
draw_text_centred(frame, 160, 70, "TANK DESTROYED", 2, COLOR_RED); - Execute
draw_text_centred(frame, 160, 90, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
FalseContinue after this decision.
Read original function source
void drawBattleZone(uint16_t *frame)
{
if (!running || frame == NULL) return;
clear(frame, COLOR_BLACK);
char text[40];
if (state == BZ_READY) {
draw_text_centred(frame, 160, 15, "BATTLE ZONE", 3, COLOR_LIME);
draw_text_centred(frame, 83, 55, "PLAY", 2, auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW);
draw_text_centred(frame, 237, 55, "AUTO", 2, auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY);
draw_text_centred(frame, 160, 92, "D/B/STICK CHANGE A/K START", 1, COLOR_LIGHTGRAY);
draw_text_centred(frame, 160, 111, "D/B TURN STICK UP/DOWN DRIVE", 1, COLOR_LIGHTGRAY);
draw_text_centred(frame, 160, 130, "HOLD A/K FIRE", 1, COLOR_LIGHTGRAY);
draw_text_centred(frame, 160, 155, "BOOT BACK", 1, COLOR_DARKGREY);
return;
}
drawBattleZoneLandscape(frame);
for (uint8_t index = 0U; index < MAX_COVERS; ++index) draw_cover(frame, &covers[index]);
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) if (enemies[index].active) draw_enemy(frame, &enemies[index]);
for (uint8_t index = 0U; index < MAX_SHOTS; ++index) {
if (shots[index].remaining == 0U) continue;
float side, forward;
if (!project(shots[index].x, shots[index].z, &side, &forward) || fabsf(side) > forward * 1.7f) continue;
const int x = (int)lroundf(160.0f + side * 108.0f / forward);
const int y = (int)lroundf(43.0f + 118.0f / forward);
const float size = shots[index].enemy_shot ? 26.0f / forward : 11.0f / forward;
const int radius = (int)clamp(size + 1.0f, 1.0f, shots[index].enemy_shot ? 7.0f : 3.0f);
fill_rect(frame, x - radius, y - radius, radius * 2 + 1, radius * 2 + 1,
shots[index].enemy_shot ? COLOR_RED : COLOR_YELLOW);
}
draw_radar(frame);
snprintf(text, sizeof(text), "SCORE %u", (unsigned)score);
draw_text(frame, 4, 3, text, 1, COLOR_LIME);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave);
draw_text_centred(frame, 160, 3, text, 1, COLOR_LIME);
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives);
draw_text(frame, 258, 3, text, 1, COLOR_LIME);
if (protection_until > last_update && ((protection_until - last_update) / 120U) % 2U == 0U)
draw_text_centred(frame, 160, 154, "READY", 1, COLOR_YELLOW);
if (auto_mode) draw_text(frame, 4, 154, "AUTO", 1, COLOR_CYAN);
if (state == BZ_FINISHED) {
fill_rect(frame, 48, 66, 224, 38, COLOR_BLACK);
draw_text_centred(frame, 160, 70, "TANK DESTROYED", 2, COLOR_RED);
draw_text_centred(frame, 160, 90, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "battle_zone.h"
#include "audio.h"
#include "esp_random.h"
#include "game_graphics.h"
#include "mode_selection.h"
#include "PET2001_font.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#define PI 3.14159265f
#define STEP_MS 20U
#define MAX_ENEMIES 8U
#define MAX_SHOTS 8U
#define MAX_COVERS 5U
#define PLAYER_SHOT_DELAY_MS 240U
#define PLAYER_PROTECTION_MS 1800U
#define ENEMY_SHOT_SPEED 30.0f
#define PLAYER_SHOT_SPEED 72.0f
#define ARENA_HALF_SIZE 92.0f
typedef enum { BZ_READY, BZ_PLAYING, BZ_FINISHED } battle_zone_state_t;
typedef struct { float x, z; bool active; uint32_t fire_due; } enemy_t;
typedef struct { float x, z, dx, dz; uint32_t remaining; bool enemy_shot; } shot_t;
typedef struct { float x, z, radius; } cover_t;
static battle_zone_state_t state;
static enemy_t enemies[MAX_ENEMIES];
static shot_t shots[MAX_SHOTS];
static cover_t covers[MAX_COVERS];
static float tank_x, tank_z, tank_angle;
static uint32_t score, wave, last_update, accumulator, fire_due, protection_until, radar_due;
static uint8_t lives;
static bool running, auto_mode, controls_ready, engine_running;
static mode_selection_input_t mode_input;
static float random_unit(void)
{
return (float)(esp_random() & 0xffffU) / 65535.0f;
}
static float clamp(float value, float minimum, float maximum)
{
return value < minimum ? minimum : value > maximum ? maximum : value;
}
static float wrap_angle(float angle)
{
while (angle < -PI) angle += 2.0f * PI;
while (angle >= PI) angle -= 2.0f * PI;
return angle;
}
static float distance_squared(float ax, float az, float bx, float bz)
{
const float dx = ax - bx, dz = az - bz;
return dx * dx + dz * dz;
}
static bool position_blocked(float x, float z, float radius)
{
for (uint8_t index = 0U; index < MAX_COVERS; ++index) {
const float combined_radius = covers[index].radius + radius;
if (distance_squared(x, z, covers[index].x, covers[index].z) < combined_radius * combined_radius)
return true;
}
return false;
}
static void reset_tank(uint32_t now_ms)
{
tank_x = tank_z = 0.0f;
tank_angle = -PI / 2.0f;
protection_until = now_ms + PLAYER_PROTECTION_MS;
engine_running = false;
audioSetEngineThrust(false);
controls_ready = false;
}
static void spawn_wave(void)
{
memset(enemies, 0, sizeof(enemies));
memset(shots, 0, sizeof(shots));
covers[0] = (cover_t){-32.0f, -26.0f, 7.0f};
covers[1] = (cover_t){31.0f, -31.0f, 8.0f};
covers[2] = (cover_t){-43.0f, 24.0f, 6.0f};
covers[3] = (cover_t){37.0f, 31.0f, 7.0f};
covers[4] = (cover_t){0.0f, 57.0f, 7.0f};
const uint8_t count = wave < 6U ? (uint8_t)(wave + 1U) : MAX_ENEMIES;
for (uint8_t index = 0U; index < count; ++index) {
float x, z;
do {
const float angle = random_unit() * 2.0f * PI;
const float distance = 42.0f + random_unit() * 42.0f;
x = cosf(angle) * distance;
z = sinf(angle) * distance;
} while (position_blocked(x, z, 3.5f));
enemies[index] = (enemy_t){x, z, true,
900U + (uint32_t)(random_unit() * 1600.0f)};
}
}
static void begin_game(uint32_t now_ms)
{
score = 0U;
wave = 1U;
lives = 3U;
fire_due = accumulator = 0U;
last_update = now_ms;
reset_tank(now_ms);
spawn_wave();
state = BZ_PLAYING;
audioPlay(AUDIO_SFX_GAME_START);
}
static void fire_shot(bool enemy_shot, float x, float z, float angle)
{
for (uint8_t index = 0U; index < MAX_SHOTS; ++index) {
if (shots[index].remaining != 0U) continue;
const float speed = enemy_shot ? ENEMY_SHOT_SPEED : PLAYER_SHOT_SPEED;
shots[index] = (shot_t){x, z, cosf(angle) * speed, sinf(angle) * speed,
enemy_shot ? 1800U : 1300U, enemy_shot};
audioPlay(enemy_shot ? AUDIO_SFX_ENEMY_SHOT : AUDIO_SFX_PLAYER_SHOT);
return;
}
}
static void fire_player_shot(uint32_t now_ms)
{
if (fire_due != 0U) return;
fire_shot(false, tank_x + cosf(tank_angle) * 4.0f, tank_z + sinf(tank_angle) * 4.0f,
tank_angle);
fire_due = PLAYER_SHOT_DELAY_MS;
(void)now_ms;
}
static void destroy_enemy(uint8_t index)
{
enemies[index].active = false;
score += 100U;
audioPlay(AUDIO_SFX_HIT);
}
static void lose_life(uint32_t now_ms)
{
if (now_ms < protection_until) return;
if (lives != 0U) --lives;
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION);
audioSetEngineThrust(false);
engine_running = false;
if (lives == 0U) {
state = BZ_FINISHED;
} else {
memset(shots, 0, sizeof(shots));
reset_tank(now_ms);
}
}
static void choose_auto_controls(int *turn, float *drive, bool *fire)
{
const enemy_t *target = NULL;
float best_distance = 0.0f;
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) {
if (!enemies[index].active) continue;
const float distance = distance_squared(tank_x, tank_z, enemies[index].x, enemies[index].z);
if (target == NULL || distance < best_distance) { target = &enemies[index]; best_distance = distance; }
}
if (target == NULL) return;
const float dx = target->x - tank_x, dz = target->z - tank_z;
const float cross = cosf(tank_angle) * dz - sinf(tank_angle) * dx;
const float forward = cosf(tank_angle) * dx + sinf(tank_angle) * dz;
*turn = cross > 2.0f ? 1 : cross < -2.0f ? -1 : 0;
*drive = forward < 18.0f ? -0.6f : forward > 50.0f ? 0.7f : 0.0f;
*fire = forward > 0.0f && fabsf(cross) < 3.5f;
}
static void update_enemies(uint32_t now_ms, float seconds)
{
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) {
enemy_t *enemy = &enemies[index];
if (!enemy->active) continue;
const float dx = tank_x - enemy->x, dz = tank_z - enemy->z;
const float distance = sqrtf(dx * dx + dz * dz);
if (distance > 0.1f) {
const float strafe = (index & 1U) == 0U ? 1.0f : -1.0f;
const float next_x = enemy->x + (dx / distance * 6.0f - dz / distance * strafe * 2.0f) * seconds;
const float next_z = enemy->z + (dz / distance * 6.0f + dx / distance * strafe * 2.0f) * seconds;
if (!position_blocked(next_x, next_z, 3.5f)) {
enemy->x = next_x;
enemy->z = next_z;
}
}
if (now_ms >= enemy->fire_due && distance < 66.0f) {
fire_shot(true, enemy->x, enemy->z, atan2f(dz, dx));
enemy->fire_due = now_ms + 1200U + (uint32_t)(random_unit() * 1100.0f);
}
if (distance < 5.0f) lose_life(now_ms);
}
}
static void update_shots(uint32_t now_ms, float seconds)
{
for (uint8_t shot_index = 0U; shot_index < MAX_SHOTS; ++shot_index) {
shot_t *shot = &shots[shot_index];
if (shot->remaining == 0U) continue;
shot->x += shot->dx * seconds;
shot->z += shot->dz * seconds;
shot->remaining = shot->remaining > STEP_MS ? shot->remaining - STEP_MS : 0U;
for (uint8_t cover_index = 0U; cover_index < MAX_COVERS; ++cover_index) {
const cover_t *cover = &covers[cover_index];
if (distance_squared(shot->x, shot->z, cover->x, cover->z) >= cover->radius * cover->radius) continue;
shot->remaining = 0U;
break;
}
if (shot->remaining == 0U) continue;
if (shot->enemy_shot) {
if (distance_squared(shot->x, shot->z, tank_x, tank_z) < 14.0f) {
shot->remaining = 0U;
lose_life(now_ms);
}
continue;
}
for (uint8_t enemy_index = 0U; enemy_index < MAX_ENEMIES; ++enemy_index) {
if (!enemies[enemy_index].active) continue;
if (distance_squared(shot->x, shot->z, enemies[enemy_index].x, enemies[enemy_index].z) >= 18.0f) continue;
shot->remaining = 0U;
destroy_enemy(enemy_index);
break;
}
}
}
static bool enemies_remaining(void)
{
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) if (enemies[index].active) return true;
return false;
}
static void simulate_step(int turn, float drive, bool fire, uint32_t now_ms)
{
const float seconds = STEP_MS / 1000.0f;
if (fire_due > 0U) fire_due = fire_due > STEP_MS ? fire_due - STEP_MS : 0U;
tank_angle = wrap_angle(tank_angle + (float)turn * 2.25f * seconds);
const bool moving = drive > 0.05f || drive < -0.05f;
if (engine_running != moving) {
engine_running = moving;
audioSetEngineThrust(moving);
}
const float next_x = clamp(tank_x + cosf(tank_angle) * drive * 30.0f * seconds,
-ARENA_HALF_SIZE, ARENA_HALF_SIZE);
const float next_z = clamp(tank_z + sinf(tank_angle) * drive * 30.0f * seconds,
-ARENA_HALF_SIZE, ARENA_HALF_SIZE);
if (!position_blocked(next_x, next_z, 4.0f)) {
tank_x = next_x;
tank_z = next_z;
}
if (fire) fire_player_shot(now_ms);
update_enemies(now_ms, seconds);
update_shots(now_ms, seconds);
if (!enemies_remaining() && state == BZ_PLAYING) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
++wave;
spawn_wave();
}
if (now_ms >= radar_due) {
audioPlay(AUDIO_SFX_MENU_MOVE);
radar_due = now_ms + 950U;
}
}
void startBattleZone(uint32_t now_ms)
{
running = true;
state = BZ_READY;
auto_mode = false;
last_update = now_ms;
radar_due = now_ms + 700U;
modeSelectionReset(&mode_input);
}
void stopBattleZone(void)
{
audioSetEngineThrust(false);
engine_running = false;
running = false;
}
void battleZoneAction(bool start, bool stop_auto, uint32_t now_ms)
{
if (!running) return;
if (state == BZ_READY && start) begin_game(now_ms);
else if (state == BZ_FINISHED && start) begin_game(now_ms);
else if (state == BZ_PLAYING && auto_mode && stop_auto) {
auto_mode = false;
modeSelectionReset(&mode_input);
audioSetEngineThrust(false);
engine_running = false;
state = BZ_READY;
}
}
void updateBattleZone(int x_offset, int y_offset, bool valid, bool left, bool right,
bool fire, uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - last_update;
last_update = now_ms;
if (state == BZ_READY) {
if (modeSelectionNext(&mode_input, x_offset, y_offset, valid, left, right, now_ms)) auto_mode = !auto_mode;
return;
}
if (state != BZ_PLAYING) return;
if (!valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350 && y_offset >= -350 && y_offset <= 350) controls_ready = true;
int turn = left || right ? (int)right - (int)left :
valid && controls_ready ? (x_offset > 600 ? 1 : x_offset < -600 ? -1 : 0) : 0;
float drive = valid && controls_ready ? (y_offset > 600 ? 1.0f : y_offset < -600 ? -0.65f : 0.0f) : 0.0f;
bool should_fire = fire;
if (auto_mode) choose_auto_controls(&turn, &drive, &should_fire);
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == BZ_PLAYING) {
accumulator -= STEP_MS;
simulate_step(turn, drive, should_fire, now_ms);
}
}
static bool project(float world_x, float world_z, float *side, float *forward)
{
const float dx = world_x - tank_x, dz = world_z - tank_z;
*side = -sinf(tank_angle) * dx + cosf(tank_angle) * dz;
*forward = cosf(tank_angle) * dx + sinf(tank_angle) * dz;
return *forward > 3.0f;
}
void drawBattleZoneLandscape(uint16_t *frame)
{
const uint16_t green = COLOR_LIME;
const int horizon = BATTLE_ZONE_HORIZON_Y;
static const int terrain_bands[] = {58, 76, 101, 134, 166};
for (size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++index) {
const int y = terrain_bands[index];
draw_line(frame, 0, y, LCD_WIDTH - 1, y, COLOR_DARKGREEN);
}
for (int lane = -3; lane <= 3; ++lane)
draw_line(frame, 160 + lane * 10, horizon, 160 + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, green);
draw_line(frame, 15, horizon, 58, horizon - 12, COLOR_DARKGREEN);
draw_line(frame, 58, horizon - 12, 104, horizon, COLOR_DARKGREEN);
draw_line(frame, 212, horizon, 258, horizon - 15, COLOR_DARKGREEN);
draw_line(frame, 258, horizon - 15, 307, horizon, COLOR_DARKGREEN);
}
static void draw_enemy(uint16_t *frame, const enemy_t *enemy)
{
float side, forward;
if (!project(enemy->x, enemy->z, &side, &forward) || fabsf(side) > forward * 1.7f) return;
const int centre_x = (int)lroundf(160.0f + side * 108.0f / forward);
const int base_y = (int)lroundf(43.0f + 118.0f / forward);
const int width = (int)clamp(150.0f / forward, 4.0f, 38.0f);
const int height = width / 2 + 2;
draw_rect(frame, centre_x - width / 2, base_y - height, width, height, COLOR_LIME);
draw_line(frame, centre_x - width / 2 - 2, base_y, centre_x + width / 2 + 2, base_y, COLOR_LIME);
draw_line(frame, centre_x, base_y - height, centre_x, base_y - height - width / 2, COLOR_LIME);
}
static void draw_cover(uint16_t *frame, const cover_t *cover)
{
float side, forward;
if (!project(cover->x, cover->z, &side, &forward) || fabsf(side) > forward * 1.7f) return;
const int centre_x = (int)lroundf(160.0f + side * 108.0f / forward);
const int base_y = (int)lroundf(43.0f + 118.0f / forward);
const int width = (int)clamp(230.0f / forward, 7.0f, 54.0f);
const int height = width / 2 + 5;
draw_rect(frame, centre_x - width / 2, base_y - height, width, height, COLOR_LIME);
draw_line(frame, centre_x - width / 2 - 3, base_y - height,
centre_x, base_y - height - height / 2, COLOR_LIME);
draw_line(frame, centre_x, base_y - height - height / 2,
centre_x + width / 2 + 3, base_y - height, COLOR_LIME);
}
static void draw_radar_point(uint16_t *frame, int centre_x, int centre_y, int radius,
float world_x, float world_z, uint16_t colour)
{
float offset_x = world_x - tank_x;
float offset_z = world_z - tank_z;
const float distance = sqrtf(offset_x * offset_x + offset_z * offset_z);
const float maximum_distance = ARENA_HALF_SIZE;
if (distance > maximum_distance) {
offset_x *= maximum_distance / distance;
offset_z *= maximum_distance / distance;
}
const int x = centre_x + (int)(offset_x * (radius - 3) / maximum_distance);
const int y = centre_y + (int)(offset_z * (radius - 3) / maximum_distance);
fill_circle(frame, x, y, 2, colour);
}
static void draw_radar(uint16_t *frame)
{
const int centre_x = 284, centre_y = 135, radius = 25;
draw_ring(frame, centre_x, centre_y, radius, COLOR_DARKGREEN);
draw_line(frame, centre_x - radius, centre_y, centre_x + radius, centre_y, COLOR_DARKGREEN);
draw_line(frame, centre_x, centre_y - radius, centre_x, centre_y + radius, COLOR_DARKGREEN);
fill_circle(frame, centre_x, centre_y, 2, COLOR_LIME);
draw_line(frame, centre_x, centre_y,
centre_x + (int)(cosf(tank_angle) * 6.0f),
centre_y + (int)(sinf(tank_angle) * 6.0f), COLOR_LIME);
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) {
if (!enemies[index].active) continue;
draw_radar_point(frame, centre_x, centre_y, radius,
enemies[index].x, enemies[index].z, COLOR_RED);
}
for (uint8_t index = 0U; index < MAX_COVERS; ++index) {
draw_radar_point(frame, centre_x, centre_y, radius,
covers[index].x, covers[index].z, COLOR_YELLOW);
}
}
void drawBattleZone(uint16_t *frame)
{
if (!running || frame == NULL) return;
clear(frame, COLOR_BLACK);
char text[40];
if (state == BZ_READY) {
drawPet2001TextCentredTransparent(frame, 160, 15, "BATTLE ZONE", COLOR_LIME, 3);
drawPet2001TextCentredTransparent(frame, 83, 55, "PLAY",
auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW, 2);
drawPet2001TextCentredTransparent(frame, 237, 55, "AUTO",
auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY, 2);
drawPet2001TextCentredTransparent(frame, 160, 92,
"D/B/STICK CHANGE A/K START", COLOR_LIGHTGRAY, 1);
drawPet2001TextCentredTransparent(frame, 160, 111,
"D/B TURN STICK UP/DOWN DRIVE", COLOR_LIGHTGRAY, 1);
drawPet2001TextCentredTransparent(frame, 160, 130, "HOLD A/K FIRE", COLOR_LIGHTGRAY, 1);
drawPet2001TextCentredTransparent(frame, 160, 155, "BOOT BACK", COLOR_DARKGREY, 1);
return;
}
drawBattleZoneLandscape(frame);
for (uint8_t index = 0U; index < MAX_COVERS; ++index) draw_cover(frame, &covers[index]);
for (uint8_t index = 0U; index < MAX_ENEMIES; ++index) if (enemies[index].active) draw_enemy(frame, &enemies[index]);
for (uint8_t index = 0U; index < MAX_SHOTS; ++index) {
if (shots[index].remaining == 0U) continue;
float side, forward;
if (!project(shots[index].x, shots[index].z, &side, &forward) || fabsf(side) > forward * 1.7f) continue;
const int x = (int)lroundf(160.0f + side * 108.0f / forward);
const int y = (int)lroundf(43.0f + 118.0f / forward);
const float size = shots[index].enemy_shot ? 26.0f / forward : 11.0f / forward;
const int radius = (int)clamp(size + 1.0f, 1.0f, shots[index].enemy_shot ? 7.0f : 3.0f);
fill_rect(frame, x - radius, y - radius, radius * 2 + 1, radius * 2 + 1,
shots[index].enemy_shot ? COLOR_RED : COLOR_YELLOW);
}
draw_radar(frame);
snprintf(text, sizeof(text), "SCORE %u", (unsigned)score);
drawPet2001TextTransparent(frame, 4, 3, text, COLOR_LIME, 1);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave);
drawPet2001TextCentredTransparent(frame, 160, 3, text, COLOR_LIME, 1);
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives);
drawPet2001TextTransparent(frame, 258, 3, text, COLOR_LIME, 1);
if (protection_until > last_update && ((protection_until - last_update) / 120U) % 2U == 0U)
drawPet2001TextCentredTransparent(frame, 160, 154, "READY", COLOR_YELLOW, 1);
if (auto_mode) drawPet2001TextTransparent(frame, 4, 154, "AUTO", COLOR_CYAN, 1);
if (state == BZ_FINISHED) {
fill_rect(frame, 48, 66, 224, 38, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 70, "TANK DESTROYED", COLOR_RED, 2);
drawPet2001TextCentredTransparent(frame, 160, 90, "A/K NEW GAME", COLOR_LIGHTGRAY, 1);
}
}