Breakout
Source: src/breakout.c · 383 lines · 14 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
clampValue() · source line 59
static float clampValue(float value, float minimum, float maximum)
- Exit this function
return value < minimum ? minimum : value > maximum ? maximum : value;
Read original function source
static float clampValue(float value, float minimum, float maximum)
{
return value < minimum ? minimum : value > maximum ? maximum : value;
}remainingBricks() · source line 64
static unsigned remainingBricks(void)
- Execute
unsigned count = 0U; - FOR loop
unsigned row = 0U; row < BRICK_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < BRICK_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
bricks[row][column]True- Execute
++count;
FalseContinue after this decision.
- Exit this function
return count;
Read original function source
static unsigned remainingBricks(void)
{
unsigned count = 0U;
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) {
if (bricks[row][column]) ++count;
}
}
return count;
}attachBall() · source line 75
static void attachBall(void)
- Execute
ball_x = paddle_x + (float)PADDLE_WIDTH * 0.5f; - Execute
ball_y = (float)(PADDLE_Y - BALL_RADIUS - 1); - Execute
ball_velocity_x = 0.0f; - Execute
ball_velocity_y = 0.0f; - Execute
ball_attached = true;
Read original function source
static void attachBall(void)
{
ball_x = paddle_x + (float)PADDLE_WIDTH * 0.5f;
ball_y = (float)(PADDLE_Y - BALL_RADIUS - 1);
ball_velocity_x = 0.0f;
ball_velocity_y = 0.0f;
ball_attached = true;
}beginWave() · source line 84
static void beginWave(void)
Calls into this documentation:
- FOR loop
unsigned row = 0U; row < BRICK_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < BRICK_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
bricks[row][column] = true;
- Execute
paddle_x = (float)(LCD_WIDTH - PADDLE_WIDTH) * 0.5f; - Execute
attachBall(); - Execute
ESP_LOGI("breakout", "WAVE %u", (unsigned)wave);
Read original function source
static void beginWave(void)
{
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) bricks[row][column] = true;
}
paddle_x = (float)(LCD_WIDTH - PADDLE_WIDTH) * 0.5f;
attachBall();
ESP_LOGI("breakout", "WAVE %u", (unsigned)wave);
}beginGame() · source line 94
static void beginGame(uint32_t now_ms)
Calls into this documentation:
- Execute
score = 0U; - Execute
wave = 1U; - Execute
lives = 3U; - Execute
accumulator = 0U; - Execute
state_started = now_ms; - Execute
beginWave(); - Execute
ai_next_decision = now_ms + aiSpeedDelay(ai_speed); - Execute
state = BREAKOUT_PLAYING; - Execute
audioPlay(AUDIO_SFX_GAME_START);
Read original function source
static void beginGame(uint32_t now_ms)
{
score = 0U;
wave = 1U;
lives = 3U;
accumulator = 0U;
state_started = now_ms;
beginWave();
ai_next_decision = now_ms + aiSpeedDelay(ai_speed);
state = BREAKOUT_PLAYING;
audioPlay(AUDIO_SFX_GAME_START);
}launchBall() · source line 107
static void launchBall(void)
- Decision
!ball_attachedTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const float speed = 112.0f + (float)(wave > 8U ? 8U : wave) * 6.0f; - Execute
ball_velocity_x = speed * 0.56f; - Execute
ball_velocity_y = -speed * 0.83f; - Execute
ball_attached = false;
Read original function source
static void launchBall(void)
{
if (!ball_attached) return;
const float speed = 112.0f + (float)(wave > 8U ? 8U : wave) * 6.0f;
ball_velocity_x = speed * 0.56f;
ball_velocity_y = -speed * 0.83f;
ball_attached = false;
}finishGame() · source line 116
static void finishGame(uint32_t now_ms)
Calls into this documentation:
- Execute
state = BREAKOUT_FINISHED; - Execute
state_started = now_ms; - Execute
auto_restart_started = now_ms; - Execute
audioPlay(AUDIO_SFX_GAME_OVER); - Execute
const uint32_t gold = score / 1000U; - Decision
gold != 0U && !(auto_mode ? addAIGold(gold) : addUserGoldWon(gold))True- Execute
ESP_LOGE("breakout", "Failed to save %u Gold", (unsigned)gold);
FalseContinue after this decision.
- Execute
ESP_LOGI("breakout", "GAME OVER: SCORE %u WAVE %u", (unsigned)score, (unsigned)wave);
Read original function source
static void finishGame(uint32_t now_ms)
{
state = BREAKOUT_FINISHED;
state_started = now_ms;
auto_restart_started = now_ms;
audioPlay(AUDIO_SFX_GAME_OVER);
const uint32_t gold = score / 1000U;
if (gold != 0U && !(auto_mode ? addAIGold(gold) : addUserGoldWon(gold))) {
ESP_LOGE("breakout", "Failed to save %u Gold", (unsigned)gold);
}
ESP_LOGI("breakout", "GAME OVER: SCORE %u WAVE %u", (unsigned)score, (unsigned)wave);
}hitBrick() · source line 129
static bool hitBrick(float previous_x, float previous_y)
Calls into this documentation:
- FOR loop
unsigned row = 0U; row < BRICK_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < BRICK_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
!bricks[row][column]True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float left = (float)(BRICK_LEFT + column * (BRICK_WIDTH + BRICK_GAP_X)); - Execute
const float top = (float)(BRICK_TOP + row * (BRICK_HEIGHT + BRICK_GAP_Y)); - Execute
const float right = left + (float)BRICK_WIDTH; - Execute
const float bottom = top + (float)BRICK_HEIGHT; - Decision
ball_x + BALL_RADIUS <= left || ball_x - BALL_RADIUS >= right || ball_y + BALL_RADIUS <= top || ball_y - BALL_RADIUS >= bottomTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
bricks[row][column] = false; - Execute
score = score > UINT32_MAX - brick_points[row] ? UINT32_MAX : score + brick_points[row]; - Decision
previous_y + BALL_RADIUS <= top || previous_y - BALL_RADIUS >= bottomTrue- Execute
ball_velocity_y = -ball_velocity_y;
False- Decision
previous_x + BALL_RADIUS <= left || previous_x - BALL_RADIUS >= rightTrue- Execute
ball_velocity_x = -ball_velocity_x;
False- Execute
ball_velocity_y = -ball_velocity_y;
- Execute
audioPlay(AUDIO_SFX_HIT); - Exit this function
return true;
- Exit this function
return false;
Read original function source
static bool hitBrick(float previous_x, float previous_y)
{
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) {
if (!bricks[row][column]) continue;
const float left = (float)(BRICK_LEFT + column * (BRICK_WIDTH + BRICK_GAP_X));
const float top = (float)(BRICK_TOP + row * (BRICK_HEIGHT + BRICK_GAP_Y));
const float right = left + (float)BRICK_WIDTH;
const float bottom = top + (float)BRICK_HEIGHT;
if (ball_x + BALL_RADIUS <= left || ball_x - BALL_RADIUS >= right ||
ball_y + BALL_RADIUS <= top || ball_y - BALL_RADIUS >= bottom) continue;
bricks[row][column] = false;
score = score > UINT32_MAX - brick_points[row] ? UINT32_MAX : score + brick_points[row];
if (previous_y + BALL_RADIUS <= top || previous_y - BALL_RADIUS >= bottom) ball_velocity_y = -ball_velocity_y;
else if (previous_x + BALL_RADIUS <= left || previous_x - BALL_RADIUS >= right) ball_velocity_x = -ball_velocity_x;
else ball_velocity_y = -ball_velocity_y;
audioPlay(AUDIO_SFX_HIT);
return true;
}
}
return false;
}simulateStep() · source line 152
static void simulateStep(uint32_t now_ms)
Calls into this documentation:
- Decision
ball_attachedTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const float previous_x = ball_x; - Execute
const float previous_y = ball_y; - Execute
ball_x += ball_velocity_x * (float)STEP_MS / 1000.0f; - Execute
ball_y += ball_velocity_y * (float)STEP_MS / 1000.0f; - Execute
bool bounced = false; - Decision
ball_x - BALL_RADIUS < 1.0fTrue- Execute
ball_x = 1.0f + BALL_RADIUS; - Execute
ball_velocity_x = -ball_velocity_x; - Execute
bounced = true;
False- Decision
ball_x + BALL_RADIUS >= (float)(LCD_WIDTH - 1)True- Execute
ball_x = (float)(LCD_WIDTH - 1 - BALL_RADIUS); - Execute
ball_velocity_x = -ball_velocity_x; - Execute
bounced = true;
FalseContinue after this decision.
- Decision
ball_y - BALL_RADIUS < 14.0fTrue- Execute
ball_y = 14.0f + BALL_RADIUS; - Execute
ball_velocity_y = -ball_velocity_y; - Execute
bounced = true;
FalseContinue after this decision.
- Decision
bouncedTrue- Execute
audioPlay(AUDIO_SFX_BOUNCE);
FalseContinue after this decision.
- Execute
(void)hitBrick(previous_x, previous_y); - Decision
ball_velocity_y > 0.0f && ball_y + BALL_RADIUS >= (float)PADDLE_Y && ball_y - BALL_RADIUS <= (float)(PADDLE_Y + PADDLE_HEIGHT) && ball_x + BALL_RADIUS >= paddle_x && ball_x - BALL_RADIUS <= paddle_x + PADDLE_WIDTHTrue- Execute
const float relative = (ball_x - (paddle_x + (float)PADDLE_WIDTH * 0.5f)) / ((float)PADDLE_WIDTH * 0.5f); - Execute
const float speed = 124.0f + (float)(wave > 8U ? 8U : wave) * 6.0f; - Execute
ball_y = (float)(PADDLE_Y - BALL_RADIUS); - Execute
ball_velocity_x = relative * speed; - Execute
ball_velocity_y = -(speed - (relative < 0.0f ? -relative : relative) * speed * 0.25f); - Execute
audioPlay(AUDIO_SFX_BOUNCE);
FalseContinue after this decision.
- Decision
ball_y - BALL_RADIUS >= LCD_HEIGHTTrue- Decision
lives == 0UTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
--lives; - Decision
lives == 0UTrue- Execute
finishGame(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Execute
audioPlay(AUDIO_SFX_LIFE_LOST); - Execute
state = BREAKOUT_LIFE_LOST; - Execute
state_started = now_ms; - Execute
ball_attached = true; - Exit this function
return;
FalseContinue after this decision.
- Decision
remainingBricks() == 0UTrue- Execute
audioPlay(AUDIO_SFX_WAVE_CLEAR); - Decision
wave < UINT32_MAXTrue- Execute
++wave;
FalseContinue after this decision.
- Execute
beginWave();
FalseContinue after this decision.
Read original function source
static void simulateStep(uint32_t now_ms)
{
if (ball_attached) return;
const float previous_x = ball_x;
const float previous_y = ball_y;
ball_x += ball_velocity_x * (float)STEP_MS / 1000.0f;
ball_y += ball_velocity_y * (float)STEP_MS / 1000.0f;
bool bounced = false;
if (ball_x - BALL_RADIUS < 1.0f) {
ball_x = 1.0f + BALL_RADIUS;
ball_velocity_x = -ball_velocity_x;
bounced = true;
} else if (ball_x + BALL_RADIUS >= (float)(LCD_WIDTH - 1)) {
ball_x = (float)(LCD_WIDTH - 1 - BALL_RADIUS);
ball_velocity_x = -ball_velocity_x;
bounced = true;
}
if (ball_y - BALL_RADIUS < 14.0f) {
ball_y = 14.0f + BALL_RADIUS;
ball_velocity_y = -ball_velocity_y;
bounced = true;
}
if (bounced) audioPlay(AUDIO_SFX_BOUNCE);
(void)hitBrick(previous_x, previous_y);
if (ball_velocity_y > 0.0f && ball_y + BALL_RADIUS >= (float)PADDLE_Y &&
ball_y - BALL_RADIUS <= (float)(PADDLE_Y + PADDLE_HEIGHT) &&
ball_x + BALL_RADIUS >= paddle_x && ball_x - BALL_RADIUS <= paddle_x + PADDLE_WIDTH) {
const float relative = (ball_x - (paddle_x + (float)PADDLE_WIDTH * 0.5f)) / ((float)PADDLE_WIDTH * 0.5f);
const float speed = 124.0f + (float)(wave > 8U ? 8U : wave) * 6.0f;
ball_y = (float)(PADDLE_Y - BALL_RADIUS);
ball_velocity_x = relative * speed;
ball_velocity_y = -(speed - (relative < 0.0f ? -relative : relative) * speed * 0.25f);
audioPlay(AUDIO_SFX_BOUNCE);
}
if (ball_y - BALL_RADIUS >= LCD_HEIGHT) {
if (lives == 0U) return;
--lives;
if (lives == 0U) {
finishGame(now_ms);
return;
}
audioPlay(AUDIO_SFX_LIFE_LOST);
state = BREAKOUT_LIFE_LOST;
state_started = now_ms;
ball_attached = true;
return;
}
if (remainingBricks() == 0U) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
if (wave < UINT32_MAX) ++wave;
beginWave();
}
}startBreakout() · source line 206
void startBreakout(uint32_t now_ms)
Calls into this documentation:
- Execute
running = true; - Execute
state = BREAKOUT_READY; - Execute
last_update = now_ms; - Execute
accumulator = 0U; - Execute
score = 0U; - Execute
wave = 1U; - Execute
lives = 3U; - Execute
beginWave(); - Execute
controls_ready = false; - Execute
auto_mode = false; - Execute
ai_speed = AI_SPEED_DEFAULT_INDEX; - Execute
auto_target_x = paddle_x; - Execute
modeSelectionReset(&mode_input); - Execute
ESP_LOGI("breakout", "START");
Read original function source
void startBreakout(uint32_t now_ms)
{
running = true;
state = BREAKOUT_READY;
last_update = now_ms;
accumulator = 0U;
score = 0U;
wave = 1U;
lives = 3U;
beginWave();
controls_ready = false;
auto_mode = false;
ai_speed = AI_SPEED_DEFAULT_INDEX;
auto_target_x = paddle_x;
modeSelectionReset(&mode_input);
ESP_LOGI("breakout", "START");
}stopBreakout() · source line 224
void stopBreakout(void)
Calls into this documentation:
- Execute
cancelHighScoreEntry(); - Execute
running = false; - Execute
ESP_LOGI("breakout", "STOP");
Read original function source
void stopBreakout(void)
{
cancelHighScoreEntry();
running = false;
ESP_LOGI("breakout", "STOP");
}breakoutAction() · source line 231
void breakoutAction(bool action, bool speed, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == BREAKOUT_READYTrue- Decision
actionTrue- Execute
beginGame(now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
auto_modeTrue- Decision
speedTrue- Execute
cycleAISpeed(&ai_speed); - Execute
ai_next_decision = now_ms;
FalseContinue after this decision.
- Decision
actionTrue- Execute
auto_mode = false; - Execute
modeSelectionReset(&mode_input); - Execute
state = BREAKOUT_READY;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
action && state == BREAKOUT_PLAYING && ball_attachedTrue- Execute
launchBall();
FalseContinue after this decision.
Read original function source
void breakoutAction(bool action, bool speed, uint32_t now_ms)
{
if (!running) return;
if (state == BREAKOUT_READY) {
if (action) beginGame(now_ms);
return;
}
if (auto_mode) {
if (speed) {
cycleAISpeed(&ai_speed);
ai_next_decision = now_ms;
}
if (action) {
auto_mode = false;
modeSelectionReset(&mode_input);
state = BREAKOUT_READY;
}
return;
}
if (action && state == BREAKOUT_PLAYING && ball_attached) launchBall();
}updateBreakout() · source line 253
void updateBreakout(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
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 == BREAKOUT_FINISHEDTrue- Decision
auto_modeTrue- Decision
now_ms - auto_restart_started >= aiRestartDelay()True- Execute
beginGame(now_ms);
FalseContinue after this decision.
False- Decision
now_ms - state_started >= 1000UTrue- Execute
startHighScoreEntry(HIGH_SCORE_BREAKOUT, score, now_ms); - Execute
state = BREAKOUT_SCORES;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == BREAKOUT_SCORESTrue- Decision
updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)True- Execute
state = BREAKOUT_READY;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == BREAKOUT_LIFE_LOSTTrue- Decision
now_ms - state_started >= LIFE_LOST_MSTrue- Execute
attachBall(); - Execute
ai_next_decision = now_ms + aiSpeedDelay(ai_speed); - Execute
state = BREAKOUT_PLAYING;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == BREAKOUT_READYTrue- Decision
modeSelectionNext(&mode_input, x_offset, y_offset, joystick_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 != BREAKOUT_PLAYINGTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
!joystick_validTrue- Execute
controls_ready = false;
False- Decision
x_offset >= -350 && x_offset <= 350True- Execute
controls_ready = true;
FalseContinue after this decision.
- Execute
const float delta_seconds = (float)(elapsed > 50U ? 50U : elapsed) / 1000.0f; - Decision
auto_modeTrue- Decision
now_ms >= ai_next_decisionTrue- Execute
auto_target_x = clampValue(ball_x - (float)PADDLE_WIDTH * 0.5f, 1.0f, (float)(LCD_WIDTH - 1 - PADDLE_WIDTH)); - Execute
ai_next_decision = now_ms + aiSpeedDelay(ai_speed); - Decision
ball_attachedTrue- Execute
launchBall();
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
const float distance = auto_target_x - paddle_x; - Execute
const float maximum_step = 205.0f * delta_seconds; - Execute
const float step = clampValue(distance, -maximum_step, maximum_step); - Execute
paddle_x = clampValue(paddle_x + step, 1.0f, (float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
False- Execute
const int direction = left == right ? 0 : left ? -1 : 1; - Execute
const float joystick_speed = joystick_valid && controls_ready && !left && !right ? clampValue((float)x_offset / 1600.0f, -1.0f, 1.0f) * 205.0f : 0.0f; - Execute
const float button_speed = (float)direction * 205.0f; - Execute
paddle_x = clampValue(paddle_x + (left || right ? button_speed : joystick_speed) * delta_seconds, 1.0f, (float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
- Decision
ball_attachedTrue- Execute
ball_x = paddle_x + (float)PADDLE_WIDTH * 0.5f; - Exit this function
return;
FalseContinue after this decision.
- Execute
accumulator += elapsed > 100U ? 100U : elapsed; - WHILE loop
accumulator >= STEP_MS && state == BREAKOUT_PLAYINGRepeat body while the loop condition permits; then continue below.
- Execute
accumulator -= STEP_MS; - Execute
simulateStep(now_ms);
Read original function source
void updateBreakout(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - last_update;
last_update = now_ms;
if (state == BREAKOUT_FINISHED) {
if (auto_mode) {
if (now_ms - auto_restart_started >= aiRestartDelay()) beginGame(now_ms);
} else if (now_ms - state_started >= 1000U) {
startHighScoreEntry(HIGH_SCORE_BREAKOUT, score, now_ms);
state = BREAKOUT_SCORES;
}
return;
}
if (state == BREAKOUT_SCORES) {
if (updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)) state = BREAKOUT_READY;
return;
}
if (state == BREAKOUT_LIFE_LOST) {
if (now_ms - state_started >= LIFE_LOST_MS) {
attachBall();
ai_next_decision = now_ms + aiSpeedDelay(ai_speed);
state = BREAKOUT_PLAYING;
}
return;
}
if (state == BREAKOUT_READY) {
if (modeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right,
now_ms)) {
auto_mode = !auto_mode;
}
return;
}
if (state != BREAKOUT_PLAYING) return;
if (!joystick_valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350) controls_ready = true;
const float delta_seconds = (float)(elapsed > 50U ? 50U : elapsed) / 1000.0f;
if (auto_mode) {
if (now_ms >= ai_next_decision) {
auto_target_x = clampValue(ball_x - (float)PADDLE_WIDTH * 0.5f, 1.0f,
(float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
ai_next_decision = now_ms + aiSpeedDelay(ai_speed);
if (ball_attached) launchBall();
}
const float distance = auto_target_x - paddle_x;
const float maximum_step = 205.0f * delta_seconds;
const float step = clampValue(distance, -maximum_step, maximum_step);
paddle_x = clampValue(paddle_x + step, 1.0f, (float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
} else {
const int direction = left == right ? 0 : left ? -1 : 1;
const float joystick_speed = joystick_valid && controls_ready && !left && !right
? clampValue((float)x_offset / 1600.0f, -1.0f, 1.0f) * 205.0f : 0.0f;
const float button_speed = (float)direction * 205.0f;
paddle_x = clampValue(paddle_x + (left || right ? button_speed : joystick_speed) *
delta_seconds, 1.0f,
(float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
}
if (ball_attached) {
ball_x = paddle_x + (float)PADDLE_WIDTH * 0.5f;
return;
}
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == BREAKOUT_PLAYING) {
accumulator -= STEP_MS;
simulateStep(now_ms);
}
}drawBreakout() · source line 322
void drawBreakout(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == BREAKOUT_SCORESTrue- Execute
drawHighScoreEntry(frame); - Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
char text[40]; - Decision
state == BREAKOUT_READYTrue- Execute
draw_text_centred(frame, 160, 3, "BREAKOUT", 1, COLOR_CYAN);
False- Execute
snprintf(text, sizeof(text), "SCORE %u", (unsigned)score); - Execute
draw_text(frame, 4, 3, text, 1, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives); - Execute
draw_text_centred(frame, 160, 3, text, 1, COLOR_CYAN); - Execute
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave); - Execute
draw_text(frame, 222, 3, text, 1, COLOR_YELLOW);
- Execute
draw_line(frame, 0, 13, LCD_WIDTH - 1, 13, COLOR_DARKGRAY); - FOR loop
unsigned row = 0U; row < BRICK_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < BRICK_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
!bricks[row][column]True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int x = BRICK_LEFT + (int)column * (BRICK_WIDTH + BRICK_GAP_X); - Execute
const int y = BRICK_TOP + (int)row * (BRICK_HEIGHT + BRICK_GAP_Y); - Execute
fill_rect(frame, x, y, BRICK_WIDTH, BRICK_HEIGHT, brick_colours[row]); - Execute
draw_line(frame, x, y, x + BRICK_WIDTH - 1, y, COLOR_WHITE);
- Decision
state != BREAKOUT_READYTrue- Execute
fill_rect(frame, (int)paddle_x, PADDLE_Y, PADDLE_WIDTH, PADDLE_HEIGHT, COLOR_LIGHTGREEN); - Execute
fill_circle(frame, (int)ball_x, (int)ball_y, BALL_RADIUS, COLOR_WHITE);
FalseContinue after this decision.
- Decision
state == BREAKOUT_READYTrue- Execute
draw_text_centred(frame, 100, 97, "PLAY", 2, auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW); - Execute
draw_text_centred(frame, 220, 97, "AUTO", 2, auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY); - Execute
draw_text(frame, 49, 121, "RED 60", 1, COLOR_RED); - Execute
draw_text(frame, 124, 121, "ORANGE 50", 1, COLOR_ORANGE); - Execute
draw_text(frame, 218, 121, "YELLOW 40", 1, COLOR_YELLOW); - Execute
draw_text(frame, 52, 132, "LIME 30", 1, COLOR_LIME); - Execute
draw_text(frame, 132, 132, "CYAN 20", 1, COLOR_CYAN); - Execute
draw_text(frame, 212, 132, "PURPLE 10", 1, COLOR_MEDIUMPURPLE); - Execute
draw_text_centred(frame, 160, 153, "D/B CHANGE A/K START", 1, COLOR_YELLOW);
False- Decision
state == BREAKOUT_LIFE_LOSTTrue- Execute
draw_text_centred(frame, 160, 89, "BALL LOST", 2, COLOR_RED);
False- Decision
state == BREAKOUT_FINISHEDTrue- Execute
draw_text_centred(frame, 160, 89, "GAME OVER", 2, COLOR_RED); - Decision
auto_modeTrue- Execute
const uint32_t elapsed = last_update - auto_restart_started; - Execute
const uint32_t delay = aiRestartDelay(); - Execute
const unsigned seconds = elapsed >= delay ? 0U : (unsigned)((delay - elapsed + 999U) / 1000U); - Execute
snprintf(text, sizeof(text), "NEXT %u", seconds); - Execute
draw_text_centred(frame, 160, 114, text, 1, COLOR_YELLOW);
FalseContinue after this decision.
False- Decision
auto_modeTrue- Execute
snprintf(text, sizeof(text), "AUTO %s E SPEED A/K STOP", aiSpeedShortText(ai_speed)); - Execute
draw_text_centred(frame, 160, 136, text, 1, COLOR_LIGHTGRAY);
False- Decision
ball_attachedTrue- Execute
draw_text_centred(frame, 160, 136, "A/K LAUNCH", 1, COLOR_LIGHTGRAY);
FalseContinue after this decision.
Read original function source
void drawBreakout(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == BREAKOUT_SCORES) {
drawHighScoreEntry(frame);
return;
}
clear(frame, COLOR_BLACK);
char text[40];
if (state == BREAKOUT_READY) {
draw_text_centred(frame, 160, 3, "BREAKOUT", 1, COLOR_CYAN);
} else {
snprintf(text, sizeof(text), "SCORE %u", (unsigned)score);
draw_text(frame, 4, 3, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives);
draw_text_centred(frame, 160, 3, text, 1, COLOR_CYAN);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave);
draw_text(frame, 222, 3, text, 1, COLOR_YELLOW);
}
draw_line(frame, 0, 13, LCD_WIDTH - 1, 13, COLOR_DARKGRAY);
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) {
if (!bricks[row][column]) continue;
const int x = BRICK_LEFT + (int)column * (BRICK_WIDTH + BRICK_GAP_X);
const int y = BRICK_TOP + (int)row * (BRICK_HEIGHT + BRICK_GAP_Y);
fill_rect(frame, x, y, BRICK_WIDTH, BRICK_HEIGHT, brick_colours[row]);
draw_line(frame, x, y, x + BRICK_WIDTH - 1, y, COLOR_WHITE);
}
}
if (state != BREAKOUT_READY) {
fill_rect(frame, (int)paddle_x, PADDLE_Y, PADDLE_WIDTH, PADDLE_HEIGHT, COLOR_LIGHTGREEN);
fill_circle(frame, (int)ball_x, (int)ball_y, BALL_RADIUS, COLOR_WHITE);
}
if (state == BREAKOUT_READY) {
draw_text_centred(frame, 100, 97, "PLAY", 2,
auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW);
draw_text_centred(frame, 220, 97, "AUTO", 2,
auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY);
draw_text(frame, 49, 121, "RED 60", 1, COLOR_RED);
draw_text(frame, 124, 121, "ORANGE 50", 1, COLOR_ORANGE);
draw_text(frame, 218, 121, "YELLOW 40", 1, COLOR_YELLOW);
draw_text(frame, 52, 132, "LIME 30", 1, COLOR_LIME);
draw_text(frame, 132, 132, "CYAN 20", 1, COLOR_CYAN);
draw_text(frame, 212, 132, "PURPLE 10", 1, COLOR_MEDIUMPURPLE);
draw_text_centred(frame, 160, 153, "D/B CHANGE A/K START", 1, COLOR_YELLOW);
} else if (state == BREAKOUT_LIFE_LOST) {
draw_text_centred(frame, 160, 89, "BALL LOST", 2, COLOR_RED);
} else if (state == BREAKOUT_FINISHED) {
draw_text_centred(frame, 160, 89, "GAME OVER", 2, COLOR_RED);
if (auto_mode) {
const uint32_t elapsed = last_update - auto_restart_started;
const uint32_t delay = aiRestartDelay();
const unsigned seconds = elapsed >= delay ? 0U : (unsigned)((delay - elapsed + 999U) / 1000U);
snprintf(text, sizeof(text), "NEXT %u", seconds);
draw_text_centred(frame, 160, 114, text, 1, COLOR_YELLOW);
}
} else if (auto_mode) {
snprintf(text, sizeof(text), "AUTO %s E SPEED A/K STOP", aiSpeedShortText(ai_speed));
draw_text_centred(frame, 160, 136, text, 1, COLOR_LIGHTGRAY);
} else if (ball_attached) {
draw_text_centred(frame, 160, 136, "A/K LAUNCH", 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 "breakout.h"
#include "audio.h"
#include "ai_speed.h"
#include "game_graphics.h"
#include "gold.h"
#include "high_scores.h"
#include "mode_selection.h"
#include "PET2001_font.h"
#include "esp_log.h"
#include <stdio.h>
#include <string.h>
#define BRICK_ROWS 6U
#define BRICK_COLUMNS 10U
#define BRICK_LEFT 20
#define BRICK_TOP 23
#define BRICK_WIDTH 26
#define BRICK_HEIGHT 9
#define BRICK_GAP_X 2
#define BRICK_GAP_Y 3
#define PADDLE_Y 151
#define PADDLE_WIDTH 48
#define PADDLE_HEIGHT 5
#define BALL_RADIUS 3
#define STEP_MS 10U
#define LIFE_LOST_MS 900U
typedef enum { BREAKOUT_READY, BREAKOUT_PLAYING, BREAKOUT_LIFE_LOST, BREAKOUT_FINISHED, BREAKOUT_SCORES } breakout_state_t;
static const uint16_t brick_colours[BRICK_ROWS] = {
COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_LIME, COLOR_CYAN, COLOR_MEDIUMPURPLE
};
static const uint16_t brick_points[BRICK_ROWS] = {60U, 50U, 40U, 30U, 20U, 10U};
static bool bricks[BRICK_ROWS][BRICK_COLUMNS];
static breakout_state_t state;
static float paddle_x;
static float ball_x;
static float ball_y;
static float ball_velocity_x;
static float ball_velocity_y;
static uint32_t score;
static uint32_t wave;
static uint32_t last_update;
static uint32_t accumulator;
static uint32_t state_started;
static uint32_t ai_next_decision;
static uint32_t auto_restart_started;
static uint8_t lives;
static uint8_t ai_speed;
static bool ball_attached;
static bool running;
static bool controls_ready;
static bool auto_mode;
static float auto_target_x;
static mode_selection_input_t mode_input;
static float clampValue(float value, float minimum, float maximum)
{
return value < minimum ? minimum : value > maximum ? maximum : value;
}
static unsigned remainingBricks(void)
{
unsigned count = 0U;
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) {
if (bricks[row][column]) ++count;
}
}
return count;
}
static void attachBall(void)
{
ball_x = paddle_x + (float)PADDLE_WIDTH * 0.5f;
ball_y = (float)(PADDLE_Y - BALL_RADIUS - 1);
ball_velocity_x = 0.0f;
ball_velocity_y = 0.0f;
ball_attached = true;
}
static void beginWave(void)
{
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) bricks[row][column] = true;
}
paddle_x = (float)(LCD_WIDTH - PADDLE_WIDTH) * 0.5f;
attachBall();
ESP_LOGI("breakout", "WAVE %u", (unsigned)wave);
}
static void beginGame(uint32_t now_ms)
{
score = 0U;
wave = 1U;
lives = 3U;
accumulator = 0U;
state_started = now_ms;
beginWave();
ai_next_decision = now_ms + aiSpeedDelay(ai_speed);
state = BREAKOUT_PLAYING;
audioPlay(AUDIO_SFX_GAME_START);
}
static void launchBall(void)
{
if (!ball_attached) return;
const float speed = 112.0f + (float)(wave > 8U ? 8U : wave) * 6.0f;
ball_velocity_x = speed * 0.56f;
ball_velocity_y = -speed * 0.83f;
ball_attached = false;
}
static void finishGame(uint32_t now_ms)
{
state = BREAKOUT_FINISHED;
state_started = now_ms;
auto_restart_started = now_ms;
audioPlay(AUDIO_SFX_GAME_OVER);
const uint32_t gold = score / 1000U;
if (gold != 0U && !(auto_mode ? addAIGold(gold) : addUserGoldWon(gold))) {
ESP_LOGE("breakout", "Failed to save %u Gold", (unsigned)gold);
}
ESP_LOGI("breakout", "GAME OVER: SCORE %u WAVE %u", (unsigned)score, (unsigned)wave);
}
static bool hitBrick(float previous_x, float previous_y)
{
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) {
if (!bricks[row][column]) continue;
const float left = (float)(BRICK_LEFT + column * (BRICK_WIDTH + BRICK_GAP_X));
const float top = (float)(BRICK_TOP + row * (BRICK_HEIGHT + BRICK_GAP_Y));
const float right = left + (float)BRICK_WIDTH;
const float bottom = top + (float)BRICK_HEIGHT;
if (ball_x + BALL_RADIUS <= left || ball_x - BALL_RADIUS >= right ||
ball_y + BALL_RADIUS <= top || ball_y - BALL_RADIUS >= bottom) continue;
bricks[row][column] = false;
score = score > UINT32_MAX - brick_points[row] ? UINT32_MAX : score + brick_points[row];
if (previous_y + BALL_RADIUS <= top || previous_y - BALL_RADIUS >= bottom) ball_velocity_y = -ball_velocity_y;
else if (previous_x + BALL_RADIUS <= left || previous_x - BALL_RADIUS >= right) ball_velocity_x = -ball_velocity_x;
else ball_velocity_y = -ball_velocity_y;
audioPlay(AUDIO_SFX_HIT);
return true;
}
}
return false;
}
static void simulateStep(uint32_t now_ms)
{
if (ball_attached) return;
const float previous_x = ball_x;
const float previous_y = ball_y;
ball_x += ball_velocity_x * (float)STEP_MS / 1000.0f;
ball_y += ball_velocity_y * (float)STEP_MS / 1000.0f;
bool bounced = false;
if (ball_x - BALL_RADIUS < 1.0f) {
ball_x = 1.0f + BALL_RADIUS;
ball_velocity_x = -ball_velocity_x;
bounced = true;
} else if (ball_x + BALL_RADIUS >= (float)(LCD_WIDTH - 1)) {
ball_x = (float)(LCD_WIDTH - 1 - BALL_RADIUS);
ball_velocity_x = -ball_velocity_x;
bounced = true;
}
if (ball_y - BALL_RADIUS < 14.0f) {
ball_y = 14.0f + BALL_RADIUS;
ball_velocity_y = -ball_velocity_y;
bounced = true;
}
if (bounced) audioPlay(AUDIO_SFX_BOUNCE);
(void)hitBrick(previous_x, previous_y);
if (ball_velocity_y > 0.0f && ball_y + BALL_RADIUS >= (float)PADDLE_Y &&
ball_y - BALL_RADIUS <= (float)(PADDLE_Y + PADDLE_HEIGHT) &&
ball_x + BALL_RADIUS >= paddle_x && ball_x - BALL_RADIUS <= paddle_x + PADDLE_WIDTH) {
const float relative = (ball_x - (paddle_x + (float)PADDLE_WIDTH * 0.5f)) / ((float)PADDLE_WIDTH * 0.5f);
const float speed = 124.0f + (float)(wave > 8U ? 8U : wave) * 6.0f;
ball_y = (float)(PADDLE_Y - BALL_RADIUS);
ball_velocity_x = relative * speed;
ball_velocity_y = -(speed - (relative < 0.0f ? -relative : relative) * speed * 0.25f);
audioPlay(AUDIO_SFX_BOUNCE);
}
if (ball_y - BALL_RADIUS >= LCD_HEIGHT) {
if (lives == 0U) return;
--lives;
if (lives == 0U) {
finishGame(now_ms);
return;
}
audioPlay(AUDIO_SFX_LIFE_LOST);
state = BREAKOUT_LIFE_LOST;
state_started = now_ms;
ball_attached = true;
return;
}
if (remainingBricks() == 0U) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
if (wave < UINT32_MAX) ++wave;
beginWave();
}
}
void startBreakout(uint32_t now_ms)
{
running = true;
state = BREAKOUT_READY;
last_update = now_ms;
accumulator = 0U;
score = 0U;
wave = 1U;
lives = 3U;
beginWave();
controls_ready = false;
auto_mode = false;
ai_speed = AI_SPEED_DEFAULT_INDEX;
auto_target_x = paddle_x;
modeSelectionReset(&mode_input);
ESP_LOGI("breakout", "START");
}
void stopBreakout(void)
{
cancelHighScoreEntry();
running = false;
ESP_LOGI("breakout", "STOP");
}
void breakoutAction(bool action, bool speed, uint32_t now_ms)
{
if (!running) return;
if (state == BREAKOUT_READY) {
if (action) beginGame(now_ms);
return;
}
if (auto_mode) {
if (speed) {
cycleAISpeed(&ai_speed);
ai_next_decision = now_ms;
}
if (action) {
auto_mode = false;
modeSelectionReset(&mode_input);
state = BREAKOUT_READY;
}
return;
}
if (action && state == BREAKOUT_PLAYING && ball_attached) launchBall();
}
void updateBreakout(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - last_update;
last_update = now_ms;
if (state == BREAKOUT_FINISHED) {
if (auto_mode) {
if (now_ms - auto_restart_started >= aiRestartDelay()) beginGame(now_ms);
} else if (now_ms - state_started >= 1000U) {
startHighScoreEntry(HIGH_SCORE_BREAKOUT, score, now_ms);
state = BREAKOUT_SCORES;
}
return;
}
if (state == BREAKOUT_SCORES) {
if (updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)) state = BREAKOUT_READY;
return;
}
if (state == BREAKOUT_LIFE_LOST) {
if (now_ms - state_started >= LIFE_LOST_MS) {
attachBall();
ai_next_decision = now_ms + aiSpeedDelay(ai_speed);
state = BREAKOUT_PLAYING;
}
return;
}
if (state == BREAKOUT_READY) {
if (modeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right,
now_ms)) {
auto_mode = !auto_mode;
}
return;
}
if (state != BREAKOUT_PLAYING) return;
if (!joystick_valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350) controls_ready = true;
const float delta_seconds = (float)(elapsed > 50U ? 50U : elapsed) / 1000.0f;
if (auto_mode) {
if (now_ms >= ai_next_decision) {
auto_target_x = clampValue(ball_x - (float)PADDLE_WIDTH * 0.5f, 1.0f,
(float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
ai_next_decision = now_ms + aiSpeedDelay(ai_speed);
if (ball_attached) launchBall();
}
const float distance = auto_target_x - paddle_x;
const float maximum_step = 205.0f * delta_seconds;
const float step = clampValue(distance, -maximum_step, maximum_step);
paddle_x = clampValue(paddle_x + step, 1.0f, (float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
} else {
const int direction = left == right ? 0 : left ? -1 : 1;
const float joystick_speed = joystick_valid && controls_ready && !left && !right
? clampValue((float)x_offset / 1600.0f, -1.0f, 1.0f) * 205.0f : 0.0f;
const float button_speed = (float)direction * 205.0f;
paddle_x = clampValue(paddle_x + (left || right ? button_speed : joystick_speed) *
delta_seconds, 1.0f,
(float)(LCD_WIDTH - 1 - PADDLE_WIDTH));
}
if (ball_attached) {
ball_x = paddle_x + (float)PADDLE_WIDTH * 0.5f;
return;
}
accumulator += elapsed > 100U ? 100U : elapsed;
while (accumulator >= STEP_MS && state == BREAKOUT_PLAYING) {
accumulator -= STEP_MS;
simulateStep(now_ms);
}
}
void drawBreakout(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == BREAKOUT_SCORES) {
drawHighScoreEntry(frame);
return;
}
clear(frame, COLOR_BLACK);
char text[40];
if (state == BREAKOUT_READY) {
drawPet2001TextCentredTransparent(frame, 160, 3, "BREAKOUT", COLOR_CYAN, 1);
} else {
snprintf(text, sizeof(text), "SCORE %u", (unsigned)score);
drawPet2001TextTransparent(frame, 4, 3, text, COLOR_WHITE, 1);
snprintf(text, sizeof(text), "LIVES %u", (unsigned)lives);
drawPet2001TextCentredTransparent(frame, 160, 3, text, COLOR_CYAN, 1);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave);
drawPet2001TextTransparent(frame, 222, 3, text, COLOR_YELLOW, 1);
}
draw_line(frame, 0, 13, LCD_WIDTH - 1, 13, COLOR_DARKGRAY);
for (unsigned row = 0U; row < BRICK_ROWS; ++row) {
for (unsigned column = 0U; column < BRICK_COLUMNS; ++column) {
if (!bricks[row][column]) continue;
const int x = BRICK_LEFT + (int)column * (BRICK_WIDTH + BRICK_GAP_X);
const int y = BRICK_TOP + (int)row * (BRICK_HEIGHT + BRICK_GAP_Y);
fill_rect(frame, x, y, BRICK_WIDTH, BRICK_HEIGHT, brick_colours[row]);
draw_line(frame, x, y, x + BRICK_WIDTH - 1, y, COLOR_WHITE);
}
}
if (state != BREAKOUT_READY) {
fill_rect(frame, (int)paddle_x, PADDLE_Y, PADDLE_WIDTH, PADDLE_HEIGHT, COLOR_LIGHTGREEN);
fill_circle(frame, (int)ball_x, (int)ball_y, BALL_RADIUS, COLOR_WHITE);
}
if (state == BREAKOUT_READY) {
drawPet2001TextCentredTransparent(frame, 100, 97, "PLAY", auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW, 2);
drawPet2001TextCentredTransparent(frame, 220, 97, "AUTO", auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY, 2);
drawPet2001TextTransparent(frame, 49, 121, "RED 60", COLOR_RED, 1);
drawPet2001TextTransparent(frame, 124, 121, "ORANGE 50", COLOR_ORANGE, 1);
drawPet2001TextTransparent(frame, 218, 121, "YELLOW 40", COLOR_YELLOW, 1);
drawPet2001TextTransparent(frame, 52, 132, "LIME 30", COLOR_LIME, 1);
drawPet2001TextTransparent(frame, 132, 132, "CYAN 20", COLOR_CYAN, 1);
drawPet2001TextTransparent(frame, 212, 132, "PURPLE 10", COLOR_MEDIUMPURPLE, 1);
drawPet2001TextCentredTransparent(frame, 160, 153, "D/B CHANGE A/K START", COLOR_YELLOW, 1);
} else if (state == BREAKOUT_LIFE_LOST) {
drawPet2001TextCentredTransparent(frame, 160, 89, "BALL LOST", COLOR_RED, 2);
} else if (state == BREAKOUT_FINISHED) {
drawPet2001TextCentredTransparent(frame, 160, 89, "GAME OVER", COLOR_RED, 2);
if (auto_mode) {
const uint32_t elapsed = last_update - auto_restart_started;
const uint32_t delay = aiRestartDelay();
const unsigned seconds = elapsed >= delay ? 0U : (unsigned)((delay - elapsed + 999U) / 1000U);
snprintf(text, sizeof(text), "NEXT %u", seconds);
drawPet2001TextCentredTransparent(frame, 160, 114, text, COLOR_YELLOW, 1);
}
} else if (auto_mode) {
snprintf(text, sizeof(text), "AUTO %s E SPEED A/K STOP", aiSpeedShortText(ai_speed));
drawPet2001TextCentredTransparent(frame, 160, 136, text, COLOR_LIGHTGRAY, 1);
} else if (ball_attached) {
drawPet2001TextCentredTransparent(frame, 160, 136, "A/K LAUNCH", COLOR_LIGHTGRAY, 1);
}
}