Crawler
Source: src/crawler.c · 630 lines · 30 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 91
static int clampValue(int value, int minimum, int maximum)
- Exit this function
return value < minimum ? minimum : value > maximum ? maximum : value;
Read original function source
static int clampValue(int value, int minimum, int maximum)
{
return value < minimum ? minimum : value > maximum ? maximum : value;
}absoluteValue() · source line 96
static int absoluteValue(int value)
- Exit this function
return value < 0 ? -value : value;
Read original function source
static int absoluteValue(int value)
{
return value < 0 ? -value : value;
}playerDiamondHalfWidth() · source line 101
static int playerDiamondHalfWidth(int y_offset)
Calls into this documentation:
- Execute
const int distance = absoluteValue(y_offset); - Decision
distance > PLAYER_HALF_HEIGHTTrue- Exit this function
return -1;
FalseContinue after this decision.
- Exit this function
return PLAYER_HALF_WIDTH * (PLAYER_HALF_HEIGHT - distance) / PLAYER_HALF_HEIGHT;
Read original function source
static int playerDiamondHalfWidth(int y_offset)
{
const int distance = absoluteValue(y_offset);
if (distance > PLAYER_HALF_HEIGHT) return -1;
return PLAYER_HALF_WIDTH * (PLAYER_HALF_HEIGHT - distance) / PLAYER_HALF_HEIGHT;
}crawlerHitsPlayer() · source line 108
static bool crawlerHitsPlayer(const crawler_segment_t *segment)
Calls into this documentation:
- Decision
segment == NULLTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
const int player_centre_x = player_x + PLAYER_HALF_WIDTH; - Execute
const int player_centre_y = player_y + PLAYER_HALF_HEIGHT; - FOR loop
int y = segment->y; y < segment->y + SEGMENT_SIZE; ++yRepeat body while the loop condition permits; then continue below.
- Execute
const int half_width = playerDiamondHalfWidth(y - player_centre_y); - Decision
half_width < 0True- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
int x = segment->x; x < segment->x + SEGMENT_SIZE; ++xRepeat body while the loop condition permits; then continue below.
- Decision
absoluteValue(x - player_centre_x) <= half_widthTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool crawlerHitsPlayer(const crawler_segment_t *segment)
{
if (segment == NULL) return false;
const int player_centre_x = player_x + PLAYER_HALF_WIDTH;
const int player_centre_y = player_y + PLAYER_HALF_HEIGHT;
for (int y = segment->y; y < segment->y + SEGMENT_SIZE; ++y) {
const int half_width = playerDiamondHalfWidth(y - player_centre_y);
if (half_width < 0) continue;
for (int x = segment->x; x < segment->x + SEGMENT_SIZE; ++x) {
if (absoluteValue(x - player_centre_x) <= half_width) return true;
}
}
return false;
}randomCrawlerLength() · source line 123
static unsigned randomCrawlerLength(void)
- Exit this function
return CRAWLER_MIN_SEGMENTS + esp_random() % (CRAWLER_MAX_SEGMENTS - CRAWLER_MIN_SEGMENTS + 1U);
Read original function source
static unsigned randomCrawlerLength(void)
{
return CRAWLER_MIN_SEGMENTS +
esp_random() % (CRAWLER_MAX_SEGMENTS - CRAWLER_MIN_SEGMENTS + 1U);
}topSpawnReady() · source line 129
static bool topSpawnReady(uint32_t now_ms)
- Exit this function
return (int32_t)(now_ms - top_spawn_ready_at) >= 0;
Read original function source
static bool topSpawnReady(uint32_t now_ms)
{
return (int32_t)(now_ms - top_spawn_ready_at) >= 0;
}sectionMaskFromHead() · source line 134
static uint32_t sectionMaskFromHead(int head_segment)
- Execute
uint32_t mask = 0U; - FOR loop
int segment = head_segment; segment >= 0; segment = followers[(unsigned)segment]Repeat body while the loop condition permits; then continue below.
- Execute
mask |= 1U << (unsigned)segment;
- Exit this function
return mask;
Read original function source
static uint32_t sectionMaskFromHead(int head_segment)
{
uint32_t mask = 0U;
for (int segment = head_segment; segment >= 0; segment = followers[(unsigned)segment]) {
mask |= 1U << (unsigned)segment;
}
return mask;
}configureCrawlerSection() · source line 143
static void configureCrawlerSection(unsigned section, unsigned first_segment, unsigned count,
int head_x, int head_y, int direction,
int vertical_direction)- Execute
uint32_t mask = 0U; - FOR loop
unsigned offset = 0U; offset < count; ++offsetRepeat body while the loop condition permits; then continue below.
- Execute
const unsigned segment = first_segment + offset; - Execute
segments[segment].x = head_x - direction * (int)offset * SEGMENT_SIZE; - Execute
segments[segment].y = head_y; - Execute
segments[segment].alive = true; - Execute
followers[segment] = offset + 1U < count ? (int8_t)(segment + 1U) : -1; - Execute
mask |= 1U << segment;
- Execute
sections[section].segment_mask = mask; - Execute
sections[section].head_segment = (int)first_segment; - Execute
sections[section].direction = direction; - Execute
sections[section].vertical_direction = vertical_direction;
Read original function source
static void configureCrawlerSection(unsigned section, unsigned first_segment, unsigned count,
int head_x, int head_y, int direction,
int vertical_direction)
{
uint32_t mask = 0U;
for (unsigned offset = 0U; offset < count; ++offset) {
const unsigned segment = first_segment + offset;
segments[segment].x = head_x - direction * (int)offset * SEGMENT_SIZE;
segments[segment].y = head_y;
segments[segment].alive = true;
followers[segment] = offset + 1U < count ? (int8_t)(segment + 1U) : -1;
mask |= 1U << segment;
}
sections[section].segment_mask = mask;
sections[section].head_segment = (int)first_segment;
sections[section].direction = direction;
sections[section].vertical_direction = vertical_direction;
}resetMushrooms() · source line 162
static void resetMushrooms(void)
- FOR loop
unsigned row = 0U; row < MUSHROOM_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < MUSHROOM_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
mushrooms[row][column] = (esp_random() % 100U) < MUSHROOM_DENSITY_PERCENT ? MUSHROOM_HEALTH : 0U;
Read original function source
static void resetMushrooms(void)
{
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
mushrooms[row][column] = (esp_random() % 100U) < MUSHROOM_DENSITY_PERCENT ?
MUSHROOM_HEALTH : 0U;
}
}
}resetCrawler() · source line 172
static void resetCrawler(void)
Calls into this documentation:
- Execute
const unsigned count = INITIAL_CRAWLER_SEGMENTS; - FOR loop
unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
segments[index].alive = false; - Execute
followers[index] = -1; - Execute
sections[index].segment_mask = 0U; - Execute
sections[index].head_segment = -1; - Execute
sections[index].direction = 0; - Execute
sections[index].vertical_direction = 0;
- Execute
configureCrawlerSection(0U, 0U, count, LCD_WIDTH - (int)count * SEGMENT_SIZE - 10, PLAYFIELD_TOP + 5, -1, 1); - Execute
top_spawn_ready_at = 0U;
Read original function source
static void resetCrawler(void)
{
const unsigned count = INITIAL_CRAWLER_SEGMENTS;
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
segments[index].alive = false;
followers[index] = -1;
sections[index].segment_mask = 0U;
sections[index].head_segment = -1;
sections[index].direction = 0;
sections[index].vertical_direction = 0;
}
configureCrawlerSection(0U, 0U, count, LCD_WIDTH - (int)count * SEGMENT_SIZE - 10,
PLAYFIELD_TOP + 5, -1, 1);
top_spawn_ready_at = 0U;
}resetShots() · source line 188
static void resetShots(void)
- FOR loop
unsigned index = 0U; index < MAX_SHOTS; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
shots[index].active = false;
Read original function source
static void resetShots(void)
{
for (unsigned index = 0U; index < MAX_SHOTS; ++index) shots[index].active = false;
}remainingSegments() · source line 193
static unsigned remainingSegments(void)
- Execute
unsigned remaining = 0U; - FOR loop
unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
segments[index].aliveTrue- Execute
++remaining;
FalseContinue after this decision.
- Exit this function
return remaining;
Read original function source
static unsigned remainingSegments(void)
{
unsigned remaining = 0U;
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
if (segments[index].alive) ++remaining;
}
return remaining;
}sectionLeader() · source line 202
static int sectionLeader(unsigned section)
- Decision
section >= MAX_CRAWLER_SEGMENTS || sections[section].segment_mask == 0U || sections[section].head_segment < 0True- Exit this function
return -1;
FalseContinue after this decision.
- Exit this function
return sections[section].head_segment;
Read original function source
static int sectionLeader(unsigned section)
{
if (section >= MAX_CRAWLER_SEGMENTS || sections[section].segment_mask == 0U ||
sections[section].head_segment < 0) return -1;
return sections[section].head_segment;
}segmentIsHead() · source line 209
static bool segmentIsHead(unsigned segment)
Calls into this documentation:
- FOR loop
unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++sectionRepeat body while the loop condition permits; then continue below.
- Decision
sectionLeader(section) == (int)segmentTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool segmentIsHead(unsigned segment)
{
for (unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++section) {
if (sectionLeader(section) == (int)segment) return true;
}
return false;
}splitCrawlerAt() · source line 217
static void splitCrawlerAt(unsigned segment)
Calls into this documentation:
- Execute
const uint32_t hit_bit = 1U << segment; - FOR loop
unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++sectionRepeat body while the loop condition permits; then continue below.
- Decision
(sections[section].segment_mask & hit_bit) == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int original_head = sections[section].head_segment; - Execute
const int original_direction = sections[section].direction; - Execute
const int original_vertical_direction = sections[section].vertical_direction; - Execute
int predecessor = -1; - Execute
int current = original_head; - WHILE loop
current >= 0 && current != (int)segmentRepeat body while the loop condition permits; then continue below.
- Execute
predecessor = current; - Execute
current = followers[(unsigned)current];
- Decision
current < 0True- Exit this function
return;
FalseContinue after this decision.
- Execute
const int rear_head = followers[segment]; - Decision
predecessor >= 0True- Execute
followers[(unsigned)predecessor] = -1;
FalseContinue after this decision.
- Execute
followers[segment] = -1; - Decision
predecessor >= 0True- Execute
sections[section].head_segment = original_head; - Execute
sections[section].segment_mask = sectionMaskFromHead(original_head);
False- Decision
rear_head >= 0True- Execute
sections[section].head_segment = rear_head; - Execute
sections[section].segment_mask = sectionMaskFromHead(rear_head);
False- Execute
sections[section].head_segment = -1; - Execute
sections[section].segment_mask = 0U;
- Decision
predecessor >= 0 && rear_head >= 0True- FOR loop
unsigned free_section = 0U; free_section < MAX_CRAWLER_SEGMENTS; ++free_sectionRepeat body while the loop condition permits; then continue below.
- Decision
sections[free_section].segment_mask == 0UTrue- Execute
sections[free_section].head_segment = rear_head; - Execute
sections[free_section].segment_mask = sectionMaskFromHead(rear_head); - Execute
sections[free_section].direction = -original_direction; - Execute
sections[free_section].vertical_direction = original_vertical_direction; - Exit this function
return;
FalseContinue after this decision.
FalseContinue after this decision.
- Exit this function
return;
Read original function source
static void splitCrawlerAt(unsigned segment)
{
const uint32_t hit_bit = 1U << segment;
for (unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++section) {
if ((sections[section].segment_mask & hit_bit) == 0U) continue;
const int original_head = sections[section].head_segment;
const int original_direction = sections[section].direction;
const int original_vertical_direction = sections[section].vertical_direction;
int predecessor = -1;
int current = original_head;
while (current >= 0 && current != (int)segment) {
predecessor = current;
current = followers[(unsigned)current];
}
if (current < 0) return;
const int rear_head = followers[segment];
if (predecessor >= 0) followers[(unsigned)predecessor] = -1;
followers[segment] = -1;
if (predecessor >= 0) {
sections[section].head_segment = original_head;
sections[section].segment_mask = sectionMaskFromHead(original_head);
} else if (rear_head >= 0) {
sections[section].head_segment = rear_head;
sections[section].segment_mask = sectionMaskFromHead(rear_head);
} else {
sections[section].head_segment = -1;
sections[section].segment_mask = 0U;
}
if (predecessor >= 0 && rear_head >= 0) {
for (unsigned free_section = 0U; free_section < MAX_CRAWLER_SEGMENTS;
++free_section) {
if (sections[free_section].segment_mask == 0U) {
sections[free_section].head_segment = rear_head;
sections[free_section].segment_mask = sectionMaskFromHead(rear_head);
sections[free_section].direction = -original_direction;
sections[free_section].vertical_direction = original_vertical_direction;
return;
}
}
}
return;
}
}crawlerHitsMushroom() · source line 261
static bool crawlerHitsMushroom(int x, int y)
- FOR loop
unsigned row = 0U; row < MUSHROOM_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < MUSHROOM_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
mushrooms[row][column] == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int mushroom_x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET; - Execute
const int mushroom_y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING + MUSHROOM_Y_OFFSET; - Decision
x < mushroom_x + MUSHROOM_WIDTH && x + SEGMENT_SIZE > mushroom_x && y < mushroom_y + MUSHROOM_HEIGHT && y + SEGMENT_SIZE > mushroom_yTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool crawlerHitsMushroom(int x, int y)
{
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
if (mushrooms[row][column] == 0U) continue;
const int mushroom_x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET;
const int mushroom_y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING +
MUSHROOM_Y_OFFSET;
if (x < mushroom_x + MUSHROOM_WIDTH && x + SEGMENT_SIZE > mushroom_x &&
y < mushroom_y + MUSHROOM_HEIGHT && y + SEGMENT_SIZE > mushroom_y) {
return true;
}
}
}
return false;
}damageMushroomAt() · source line 278
static bool damageMushroomAt(int x, int y)
- FOR loop
unsigned row = 0U; row < MUSHROOM_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < MUSHROOM_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
mushrooms[row][column] == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int mushroom_x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET; - Execute
const int mushroom_y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING + MUSHROOM_Y_OFFSET; - Decision
x >= mushroom_x && x < mushroom_x + MUSHROOM_WIDTH && y >= mushroom_y && y < mushroom_y + MUSHROOM_HEIGHTTrue- Execute
--mushrooms[row][column]; - Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool damageMushroomAt(int x, int y)
{
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
if (mushrooms[row][column] == 0U) continue;
const int mushroom_x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET;
const int mushroom_y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING +
MUSHROOM_Y_OFFSET;
if (x >= mushroom_x && x < mushroom_x + MUSHROOM_WIDTH &&
y >= mushroom_y && y < mushroom_y + MUSHROOM_HEIGHT) {
--mushrooms[row][column];
return true;
}
}
}
return false;
}addMushroomAt() · source line 296
static void addMushroomAt(int x, int y)
- Execute
const int centre_x = x + SEGMENT_SIZE / 2; - Execute
const int centre_y = y + SEGMENT_SIZE / 2; - Execute
const int column = (centre_x - MUSHROOM_X_OFFSET + MUSHROOM_X_SPACING / 2) / MUSHROOM_X_SPACING; - Execute
const int row = (centre_y - PLAYFIELD_TOP - MUSHROOM_Y_OFFSET + MUSHROOM_Y_SPACING / 2) / MUSHROOM_Y_SPACING; - Decision
row >= 0 && row < (int)MUSHROOM_ROWS && column >= 0 && column < (int)MUSHROOM_COLUMNSTrue- Execute
mushrooms[(unsigned)row][(unsigned)column] = MUSHROOM_HEALTH;
FalseContinue after this decision.
Read original function source
static void addMushroomAt(int x, int y)
{
const int centre_x = x + SEGMENT_SIZE / 2;
const int centre_y = y + SEGMENT_SIZE / 2;
const int column = (centre_x - MUSHROOM_X_OFFSET + MUSHROOM_X_SPACING / 2) /
MUSHROOM_X_SPACING;
const int row = (centre_y - PLAYFIELD_TOP - MUSHROOM_Y_OFFSET + MUSHROOM_Y_SPACING / 2) /
MUSHROOM_Y_SPACING;
if (row >= 0 && row < (int)MUSHROOM_ROWS && column >= 0 &&
column < (int)MUSHROOM_COLUMNS) {
mushrooms[(unsigned)row][(unsigned)column] = MUSHROOM_HEALTH;
}
}beginGame() · source line 310
static void beginGame(uint32_t now_ms)
Calls into this documentation:
- Execute
score = 0U; - Execute
wave = 1U; - Execute
lives = 3U; - Execute
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2; - Execute
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2; - Execute
resetMushrooms(); - Execute
resetCrawler(); - Execute
resetShots(); - Execute
crawler_last_step = now_ms; - Execute
shot_last_step = now_ms; - Execute
next_fire = now_ms; - Execute
state = CRAWLER_PLAYING; - Execute
audioPlay(AUDIO_SFX_GAME_START);
Read original function source
static void beginGame(uint32_t now_ms)
{
score = 0U;
wave = 1U;
lives = 3U;
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2;
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2;
resetMushrooms();
resetCrawler();
resetShots();
crawler_last_step = now_ms;
shot_last_step = now_ms;
next_fire = now_ms;
state = CRAWLER_PLAYING;
audioPlay(AUDIO_SFX_GAME_START);
}loseLife() · source line 327
static void loseLife(uint32_t now_ms)
Calls into this documentation:
- Decision
lives > 0UTrue- Execute
--lives;
FalseContinue after this decision.
- Execute
resetShots(); - Execute
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION); - Decision
lives == 0UTrue- Execute
state = CRAWLER_FINISHED; - Execute
state_started = now_ms; - Execute
ESP_LOGI("crawler", "GAME OVER: SCORE %u WAVE %u", (unsigned)score, (unsigned)wave); - Exit this function
return;
FalseContinue after this decision.
- Execute
state = CRAWLER_LIFE_LOST; - Execute
state_started = now_ms;
Read original function source
static void loseLife(uint32_t now_ms)
{
if (lives > 0U) --lives;
resetShots();
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION);
if (lives == 0U) {
state = CRAWLER_FINISHED;
state_started = now_ms;
ESP_LOGI("crawler", "GAME OVER: SCORE %u WAVE %u", (unsigned)score, (unsigned)wave);
return;
}
state = CRAWLER_LIFE_LOST;
state_started = now_ms;
}fireShot() · source line 342
static void fireShot(void)
Calls into this documentation:
- FOR loop
unsigned index = 0U; index < MAX_SHOTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
!shots[index].activeTrue- Execute
shots[index].x = player_x + PLAYER_WIDTH / 2; - Execute
shots[index].y = player_y - 2; - Execute
shots[index].active = true; - Execute
audioPlay(AUDIO_SFX_PLAYER_SHOT); - Exit this function
return;
FalseContinue after this decision.
Read original function source
static void fireShot(void)
{
for (unsigned index = 0U; index < MAX_SHOTS; ++index) {
if (!shots[index].active) {
shots[index].x = player_x + PLAYER_WIDTH / 2;
shots[index].y = player_y - 2;
shots[index].active = true;
audioPlay(AUDIO_SFX_PLAYER_SHOT);
return;
}
}
}findFreeSegmentRun() · source line 355
static bool findFreeSegmentRun(unsigned count, unsigned *first_segment)
- Decision
first_segment == NULL || count == 0U || count > MAX_CRAWLER_SEGMENTSTrue- Exit this function
return false;
FalseContinue after this decision.
- FOR loop
unsigned first = 0U; first + count <= MAX_CRAWLER_SEGMENTS; ++firstRepeat body while the loop condition permits; then continue below.
- Execute
bool available = true; - FOR loop
unsigned offset = 0U; offset < count; ++offsetRepeat body while the loop condition permits; then continue below.
- Decision
segments[first + offset].aliveTrue- Execute
available = false; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
availableTrue- Execute
*first_segment = first; - Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool findFreeSegmentRun(unsigned count, unsigned *first_segment)
{
if (first_segment == NULL || count == 0U || count > MAX_CRAWLER_SEGMENTS) return false;
for (unsigned first = 0U; first + count <= MAX_CRAWLER_SEGMENTS; ++first) {
bool available = true;
for (unsigned offset = 0U; offset < count; ++offset) {
if (segments[first + offset].alive) {
available = false;
break;
}
}
if (available) {
*first_segment = first;
return true;
}
}
return false;
}spawnTopCrawler() · source line 374
static void spawnTopCrawler(uint32_t now_ms)
Calls into this documentation:
- Decision
!topSpawnReady(now_ms)True- Exit this function
return;
FalseContinue after this decision.
- Execute
const unsigned count = randomCrawlerLength(); - Execute
unsigned first_segment = 0U; - Execute
unsigned free_section = 0U; - Decision
!findFreeSegmentRun(count, &first_segment)True- Exit this function
return;
FalseContinue after this decision.
- WHILE loop
free_section < MAX_CRAWLER_SEGMENTS && sections[free_section].segment_mask != 0URepeat body while the loop condition permits; then continue below.
- Execute
++free_section;
- Decision
free_section == MAX_CRAWLER_SEGMENTSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const int first_x = (int)(esp_random() % (LCD_WIDTH - (int)count * SEGMENT_SIZE)); - Execute
const int direction = (esp_random() & 1U) == 0U ? -1 : 1; - Execute
const int head_x = direction < 0 ? first_x : first_x + (int)(count - 1U) * SEGMENT_SIZE; - Execute
configureCrawlerSection(free_section, first_segment, count, head_x, PLAYFIELD_TOP + 5, direction, 1); - Execute
top_spawn_ready_at = now_ms + TOP_SPAWN_GUARD_MS;
Read original function source
static void spawnTopCrawler(uint32_t now_ms)
{
if (!topSpawnReady(now_ms)) return;
const unsigned count = randomCrawlerLength();
unsigned first_segment = 0U;
unsigned free_section = 0U;
if (!findFreeSegmentRun(count, &first_segment)) return;
while (free_section < MAX_CRAWLER_SEGMENTS &&
sections[free_section].segment_mask != 0U) {
++free_section;
}
if (free_section == MAX_CRAWLER_SEGMENTS) return;
const int first_x = (int)(esp_random() %
(LCD_WIDTH - (int)count * SEGMENT_SIZE));
const int direction = (esp_random() & 1U) == 0U ? -1 : 1;
const int head_x = direction < 0 ? first_x : first_x + (int)(count - 1U) * SEGMENT_SIZE;
configureCrawlerSection(free_section, first_segment, count, head_x, PLAYFIELD_TOP + 5,
direction, 1);
top_spawn_ready_at = now_ms + TOP_SPAWN_GUARD_MS;
}updateShots() · source line 395
static void updateShots(void)
Calls into this documentation:
- FOR loop
unsigned index = 0U; index < MAX_SHOTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
!shots[index].activeTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
shots[index].y -= 4; - Decision
shots[index].y < PLAYFIELD_TOPTrue- Execute
shots[index].active = false; - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
bool hit_segment = false; - FOR loop
unsigned segment = 0U; segment < MAX_CRAWLER_SEGMENTS; ++segmentRepeat body while the loop condition permits; then continue below.
- Decision
!segments[segment].aliveTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
shots[index].x >= segments[segment].x && shots[index].x < segments[segment].x + SEGMENT_SIZE && shots[index].y >= segments[segment].y && shots[index].y < segments[segment].y + SEGMENT_SIZETrue- Execute
splitCrawlerAt(segment); - Execute
addMushroomAt(segments[segment].x, segments[segment].y); - Execute
segments[segment].alive = false; - Execute
shots[index].active = false; - Execute
score = score > UINT32_MAX - 10U ? UINT32_MAX : score + 10U; - Execute
hit_segment = true; - Execute
audioPlay(AUDIO_SFX_HIT); - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
!hit_segment && damageMushroomAt(shots[index].x, shots[index].y)True- Execute
shots[index].active = false;
FalseContinue after this decision.
Read original function source
static void updateShots(void)
{
for (unsigned index = 0U; index < MAX_SHOTS; ++index) {
if (!shots[index].active) continue;
shots[index].y -= 4;
if (shots[index].y < PLAYFIELD_TOP) {
shots[index].active = false;
continue;
}
bool hit_segment = false;
for (unsigned segment = 0U; segment < MAX_CRAWLER_SEGMENTS; ++segment) {
if (!segments[segment].alive) continue;
if (shots[index].x >= segments[segment].x &&
shots[index].x < segments[segment].x + SEGMENT_SIZE &&
shots[index].y >= segments[segment].y &&
shots[index].y < segments[segment].y + SEGMENT_SIZE) {
splitCrawlerAt(segment);
addMushroomAt(segments[segment].x, segments[segment].y);
segments[segment].alive = false;
shots[index].active = false;
score = score > UINT32_MAX - 10U ? UINT32_MAX : score + 10U;
hit_segment = true;
audioPlay(AUDIO_SFX_HIT);
break;
}
}
if (!hit_segment && damageMushroomAt(shots[index].x, shots[index].y)) {
shots[index].active = false;
}
}
}moveCrawler() · source line 427
static void moveCrawler(uint32_t now_ms)
Calls into this documentation:
- Execute
bool top_spawn_requested = false; - FOR loop
unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++sectionRepeat body while the loop condition permits; then continue below.
- Execute
const int leader = sectionLeader(section); - Decision
leader < 0True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int previous_head_x = segments[(unsigned)leader].x; - Execute
const int previous_head_y = segments[(unsigned)leader].y; - Execute
const int next_x = previous_head_x + sections[section].direction * SEGMENT_SIZE; - Execute
const bool blocked = next_x < 0 || next_x + SEGMENT_SIZE > LCD_WIDTH || crawlerHitsMushroom(next_x, previous_head_y); - Decision
blockedTrue- Execute
sections[section].direction = -sections[section].direction; - Execute
int vertical_step = sections[section].vertical_direction * MUSHROOM_Y_SPACING; - Execute
const int next_y = previous_head_y + vertical_step; - Decision
next_y < PLAYFIELD_TOP || next_y + SEGMENT_SIZE > LCD_HEIGHTTrue- Decision
topSpawnReady(now_ms)True- Execute
top_spawn_requested = true;
FalseContinue after this decision.
- Execute
sections[section].vertical_direction = -sections[section].vertical_direction; - Execute
vertical_step = sections[section].vertical_direction * MUSHROOM_Y_SPACING;
FalseContinue after this decision.
- Execute
segments[(unsigned)leader].y += vertical_step;
False- Execute
segments[(unsigned)leader].x = next_x;
- Execute
int follower = followers[(unsigned)leader]; - Execute
int previous_x = previous_head_x; - Execute
int previous_y = previous_head_y; - WHILE loop
follower >= 0Repeat body while the loop condition permits; then continue below.
- Execute
const int follower_x = segments[(unsigned)follower].x; - Execute
const int follower_y = segments[(unsigned)follower].y; - Execute
segments[(unsigned)follower].x = previous_x; - Execute
segments[(unsigned)follower].y = previous_y; - Execute
previous_x = follower_x; - Execute
previous_y = follower_y; - Execute
follower = followers[(unsigned)follower];
- Decision
top_spawn_requestedTrue- Execute
spawnTopCrawler(now_ms);
FalseContinue after this decision.
- FOR loop
unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
!segments[index].aliveTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
crawlerHitsPlayer(&segments[index])True- Execute
loseLife(now_ms); - Exit this function
return;
FalseContinue after this decision.
Read original function source
static void moveCrawler(uint32_t now_ms)
{
bool top_spawn_requested = false;
for (unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++section) {
const int leader = sectionLeader(section);
if (leader < 0) continue;
const int previous_head_x = segments[(unsigned)leader].x;
const int previous_head_y = segments[(unsigned)leader].y;
const int next_x = previous_head_x + sections[section].direction * SEGMENT_SIZE;
const bool blocked = next_x < 0 || next_x + SEGMENT_SIZE > LCD_WIDTH ||
crawlerHitsMushroom(next_x, previous_head_y);
if (blocked) {
sections[section].direction = -sections[section].direction;
int vertical_step = sections[section].vertical_direction * MUSHROOM_Y_SPACING;
const int next_y = previous_head_y + vertical_step;
if (next_y < PLAYFIELD_TOP || next_y + SEGMENT_SIZE > LCD_HEIGHT) {
if (topSpawnReady(now_ms)) {
top_spawn_requested = true;
}
sections[section].vertical_direction = -sections[section].vertical_direction;
vertical_step = sections[section].vertical_direction * MUSHROOM_Y_SPACING;
}
segments[(unsigned)leader].y += vertical_step;
} else {
segments[(unsigned)leader].x = next_x;
}
int follower = followers[(unsigned)leader];
int previous_x = previous_head_x;
int previous_y = previous_head_y;
while (follower >= 0) {
const int follower_x = segments[(unsigned)follower].x;
const int follower_y = segments[(unsigned)follower].y;
segments[(unsigned)follower].x = previous_x;
segments[(unsigned)follower].y = previous_y;
previous_x = follower_x;
previous_y = follower_y;
follower = followers[(unsigned)follower];
}
}
if (top_spawn_requested) spawnTopCrawler(now_ms);
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
if (!segments[index].alive) continue;
if (crawlerHitsPlayer(&segments[index])) {
loseLife(now_ms);
return;
}
}
}startCrawler() · source line 476
void startCrawler(uint32_t now_ms)
Calls into this documentation:
- Execute
running = true; - Execute
state = CRAWLER_READY; - Execute
score = 0U; - Execute
wave = 1U; - Execute
lives = 3U; - Execute
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2; - Execute
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2; - Execute
resetMushrooms(); - Execute
resetCrawler(); - Execute
resetShots(); - Execute
last_update = now_ms; - Execute
crawler_last_step = now_ms; - Execute
shot_last_step = now_ms; - Execute
next_fire = now_ms; - Execute
controls_ready = false; - Execute
ESP_LOGI("crawler", "START");
Read original function source
void startCrawler(uint32_t now_ms)
{
running = true;
state = CRAWLER_READY;
score = 0U;
wave = 1U;
lives = 3U;
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2;
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2;
resetMushrooms();
resetCrawler();
resetShots();
last_update = now_ms;
crawler_last_step = now_ms;
shot_last_step = now_ms;
next_fire = now_ms;
controls_ready = false;
ESP_LOGI("crawler", "START");
}stopCrawler() · source line 496
void stopCrawler(void)
Calls into this documentation:
- Execute
cancelHighScoreEntry(); - Execute
running = false; - Execute
ESP_LOGI("crawler", "STOP");
Read original function source
void stopCrawler(void)
{
cancelHighScoreEntry();
running = false;
ESP_LOGI("crawler", "STOP");
}crawlerAction() · source line 503
void crawlerAction(bool action, bool fire_held, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == CRAWLER_READYTrue- Decision
actionTrue- Execute
beginGame(now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state != CRAWLER_PLAYING || !fire_held || now_ms < next_fireTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
fireShot(); - Execute
next_fire = now_ms + FIRE_INTERVAL_MS;
Read original function source
void crawlerAction(bool action, bool fire_held, uint32_t now_ms)
{
if (!running) return;
if (state == CRAWLER_READY) {
if (action) beginGame(now_ms);
return;
}
if (state != CRAWLER_PLAYING || !fire_held || now_ms < next_fire) return;
fireShot();
next_fire = now_ms + FIRE_INTERVAL_MS;
}updateCrawler() · source line 515
void updateCrawler(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 == CRAWLER_FINISHEDTrue- Decision
now_ms - state_started >= 1000UTrue- Execute
startHighScoreEntry(HIGH_SCORE_CRAWLER, score, now_ms); - Execute
state = CRAWLER_SCORES;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == CRAWLER_SCORESTrue- Decision
updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)True- Execute
state = CRAWLER_READY;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == CRAWLER_LIFE_LOSTTrue- Decision
now_ms - state_started >= LIFE_LOST_MSTrue- Execute
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2; - Execute
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2; - Execute
resetCrawler(); - Execute
crawler_last_step = now_ms; - Execute
state = CRAWLER_PLAYING;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state != CRAWLER_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 int direction = left == right ? 0 : left ? -1 : 1; - Execute
const int joystick_move = joystick_valid && controls_ready && !left && !right ? x_offset / 600 : 0; - Execute
const int joystick_vertical_move = joystick_valid && controls_ready ? y_offset / 600 : 0; - Execute
const int elapsed_limited = (int)(elapsed > 30U ? 30U : elapsed); - Execute
const int button_move = direction * elapsed_limited / 2; - Execute
player_x = clampValue(player_x + (left || right ? button_move : joystick_move), 0, LCD_WIDTH - PLAYER_WIDTH); - Execute
player_y = clampValue(player_y - joystick_vertical_move, PLAYER_ZONE_TOP, LCD_HEIGHT - PLAYER_HEIGHT - 2); - WHILE loop
now_ms - shot_last_step >= SHOT_STEP_MSRepeat body while the loop condition permits; then continue below.
- Execute
shot_last_step += SHOT_STEP_MS; - Execute
updateShots();
- WHILE loop
now_ms - crawler_last_step >= CRAWLER_STEP_MS && state == CRAWLER_PLAYINGRepeat body while the loop condition permits; then continue below.
- Execute
crawler_last_step += CRAWLER_STEP_MS; - Execute
moveCrawler(now_ms);
- Decision
state == CRAWLER_PLAYING && remainingSegments() == 0UTrue- Execute
audioPlay(AUDIO_SFX_WAVE_CLEAR); - Decision
wave < UINT32_MAXTrue- Execute
++wave;
FalseContinue after this decision.
- Execute
resetCrawler();
FalseContinue after this decision.
Read original function source
void updateCrawler(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 == CRAWLER_FINISHED) {
if (now_ms - state_started >= 1000U) {
startHighScoreEntry(HIGH_SCORE_CRAWLER, score, now_ms);
state = CRAWLER_SCORES;
}
return;
}
if (state == CRAWLER_SCORES) {
if (updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)) state = CRAWLER_READY;
return;
}
if (state == CRAWLER_LIFE_LOST) {
if (now_ms - state_started >= LIFE_LOST_MS) {
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2;
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2;
resetCrawler();
crawler_last_step = now_ms;
state = CRAWLER_PLAYING;
}
return;
}
if (state != CRAWLER_PLAYING) return;
if (!joystick_valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350) controls_ready = true;
const int direction = left == right ? 0 : left ? -1 : 1;
const int joystick_move = joystick_valid && controls_ready && !left && !right
? x_offset / 600 : 0;
const int joystick_vertical_move = joystick_valid && controls_ready ? y_offset / 600 : 0;
const int elapsed_limited = (int)(elapsed > 30U ? 30U : elapsed);
const int button_move = direction * elapsed_limited / 2;
player_x = clampValue(player_x + (left || right ? button_move : joystick_move), 0,
LCD_WIDTH - PLAYER_WIDTH);
player_y = clampValue(player_y - joystick_vertical_move, PLAYER_ZONE_TOP,
LCD_HEIGHT - PLAYER_HEIGHT - 2);
while (now_ms - shot_last_step >= SHOT_STEP_MS) {
shot_last_step += SHOT_STEP_MS;
updateShots();
}
while (now_ms - crawler_last_step >= CRAWLER_STEP_MS && state == CRAWLER_PLAYING) {
crawler_last_step += CRAWLER_STEP_MS;
moveCrawler(now_ms);
}
if (state == CRAWLER_PLAYING && remainingSegments() == 0U) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
if (wave < UINT32_MAX) ++wave;
resetCrawler();
}
}drawCrawler() · source line 570
void drawCrawler(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == CRAWLER_SCORESTrue- Execute
drawHighScoreEntry(frame); - Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
char text[28]; - Decision
state == CRAWLER_READYTrue- Execute
draw_text_centred(frame, LCD_WIDTH / 2, 10, "CRAWLER", 2, COLOR_LIME); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 42, "CLEAR THE CRAWLER", 1, COLOR_CYAN); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 63, "10 POINTS EACH", 1, COLOR_YELLOW); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 109, "D/B OR STICK MOVE", 1, COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 124, "A/K FIRE", 1, COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 151, "A/K START", 2, COLOR_YELLOW); - Exit this function
return;
FalseContinue after this decision.
- 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, LCD_WIDTH / 2, 3, text, 1, COLOR_CYAN); - Execute
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave); - Execute
draw_text(frame, 253, 3, text, 1, COLOR_YELLOW); - Execute
draw_line(frame, 0, 13, LCD_WIDTH - 1, 13, COLOR_DARKGRAY); - FOR loop
unsigned row = 0U; row < MUSHROOM_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0U; column < MUSHROOM_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
mushrooms[row][column] == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET; - Execute
const int y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING + MUSHROOM_Y_OFFSET; - Execute
const uint16_t cap_colour = mushrooms[row][column] == 4U ? COLOR_MAGENTA : mushrooms[row][column] == 3U ? COLOR_CYAN : mushrooms[row][column] == 2U ? COLOR_YELLOW : COLOR_WHITE; - Execute
draw_ui_bitmap_8x8(frame, x, y, mushroom_bitmap, 1, cap_colour);
- FOR loop
unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
!segments[index].aliveTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const uint16_t colour = segmentIsHead(index) ? COLOR_RED : COLOR_LIME; - Execute
fill_circle(frame, segments[index].x + SEGMENT_SIZE / 2, segments[index].y + SEGMENT_SIZE / 2, SEGMENT_SIZE / 2, colour);
- FOR loop
unsigned index = 0U; index < MAX_SHOTS; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
shots[index].activeTrue- Execute
draw_line(frame, shots[index].x, shots[index].y, shots[index].x, shots[index].y + 4, COLOR_WHITE);
FalseContinue after this decision.
- Execute
const int player_centre_x = player_x + PLAYER_HALF_WIDTH; - Execute
const int player_centre_y = player_y + PLAYER_HALF_HEIGHT; - FOR loop
int y_offset = -PLAYER_HALF_HEIGHT; y_offset <= PLAYER_HALF_HEIGHT; ++y_offsetRepeat body while the loop condition permits; then continue below.
- Execute
const int half_width = playerDiamondHalfWidth(y_offset); - Execute
fill_rect(frame, player_centre_x - half_width, player_centre_y + y_offset, half_width * 2 + 1, 1, COLOR_CYAN);
- Decision
state == CRAWLER_LIFE_LOSTTrue- Execute
draw_text_centred(frame, LCD_WIDTH / 2, 83, "LIFE LOST", 2, COLOR_RED);
False- Decision
state == CRAWLER_FINISHEDTrue- Execute
draw_text_centred(frame, LCD_WIDTH / 2, 83, "GAME OVER", 2, COLOR_RED);
FalseContinue after this decision.
Read original function source
void drawCrawler(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == CRAWLER_SCORES) {
drawHighScoreEntry(frame);
return;
}
clear(frame, COLOR_BLACK);
char text[28];
if (state == CRAWLER_READY) {
draw_text_centred(frame, LCD_WIDTH / 2, 10, "CRAWLER", 2, COLOR_LIME);
draw_text_centred(frame, LCD_WIDTH / 2, 42, "CLEAR THE CRAWLER", 1, COLOR_CYAN);
draw_text_centred(frame, LCD_WIDTH / 2, 63, "10 POINTS EACH", 1, COLOR_YELLOW);
draw_text_centred(frame, LCD_WIDTH / 2, 109, "D/B OR STICK MOVE", 1, COLOR_LIGHTGRAY);
draw_text_centred(frame, LCD_WIDTH / 2, 124, "A/K FIRE", 1, COLOR_LIGHTGRAY);
draw_text_centred(frame, LCD_WIDTH / 2, 151, "A/K START", 2, COLOR_YELLOW);
return;
}
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, LCD_WIDTH / 2, 3, text, 1, COLOR_CYAN);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave);
draw_text(frame, 253, 3, text, 1, COLOR_YELLOW);
draw_line(frame, 0, 13, LCD_WIDTH - 1, 13, COLOR_DARKGRAY);
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
if (mushrooms[row][column] == 0U) continue;
const int x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET;
const int y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING +
MUSHROOM_Y_OFFSET;
const uint16_t cap_colour = mushrooms[row][column] == 4U ? COLOR_MAGENTA :
mushrooms[row][column] == 3U ? COLOR_CYAN :
mushrooms[row][column] == 2U ? COLOR_YELLOW : COLOR_WHITE;
draw_ui_bitmap_8x8(frame, x, y, mushroom_bitmap, 1, cap_colour);
}
}
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
if (!segments[index].alive) continue;
const uint16_t colour = segmentIsHead(index) ? COLOR_RED : COLOR_LIME;
fill_circle(frame, segments[index].x + SEGMENT_SIZE / 2,
segments[index].y + SEGMENT_SIZE / 2, SEGMENT_SIZE / 2, colour);
}
for (unsigned index = 0U; index < MAX_SHOTS; ++index) {
if (shots[index].active) draw_line(frame, shots[index].x, shots[index].y,
shots[index].x, shots[index].y + 4, COLOR_WHITE);
}
const int player_centre_x = player_x + PLAYER_HALF_WIDTH;
const int player_centre_y = player_y + PLAYER_HALF_HEIGHT;
for (int y_offset = -PLAYER_HALF_HEIGHT; y_offset <= PLAYER_HALF_HEIGHT; ++y_offset) {
const int half_width = playerDiamondHalfWidth(y_offset);
fill_rect(frame, player_centre_x - half_width, player_centre_y + y_offset,
half_width * 2 + 1, 1, COLOR_CYAN);
}
if (state == CRAWLER_LIFE_LOST) {
draw_text_centred(frame, LCD_WIDTH / 2, 83, "LIFE LOST", 2, COLOR_RED);
} else if (state == CRAWLER_FINISHED) {
draw_text_centred(frame, LCD_WIDTH / 2, 83, "GAME OVER", 2, COLOR_RED);
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "crawler.h"
#include "audio.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "high_scores.h"
#include "esp_log.h"
#include "esp_random.h"
#include <limits.h>
#include <stdio.h>
#define PLAYFIELD_TOP 15
#define PLAYER_ZONE_TOP PLAYFIELD_TOP
#define PLAYER_WIDTH 15
#define PLAYER_HEIGHT 9
#define PLAYER_HALF_WIDTH (PLAYER_WIDTH / 2)
#define PLAYER_HALF_HEIGHT (PLAYER_HEIGHT / 2)
#define SEGMENT_SIZE 7
#define INITIAL_CRAWLER_SEGMENTS 12U
#define CRAWLER_MIN_SEGMENTS 3U
#define CRAWLER_MAX_SEGMENTS 8U
#define MAX_CRAWLER_SEGMENTS 32U
#define MAX_SHOTS 10U
#define MUSHROOM_COLUMNS 20U
#define MUSHROOM_ROWS 15U
#define MUSHROOM_X_SPACING 16
#define MUSHROOM_Y_SPACING 8
#define MUSHROOM_X_OFFSET 5
#define MUSHROOM_Y_OFFSET 1
#define MUSHROOM_WIDTH 8
#define MUSHROOM_HEIGHT 8
#define MUSHROOM_DENSITY_PERCENT 8U
#define MUSHROOM_HEALTH 4U
#define CRAWLER_STEP_MS 75U
#define SHOT_STEP_MS 18U
#define FIRE_INTERVAL_MS 110U
#define TOP_SPAWN_GUARD_MS 300U
#define LIFE_LOST_MS 900U
typedef enum {
CRAWLER_READY,
CRAWLER_PLAYING,
CRAWLER_LIFE_LOST,
CRAWLER_FINISHED,
CRAWLER_SCORES,
} crawler_state_t;
typedef struct {
int x;
int y;
bool alive;
} crawler_segment_t;
typedef struct {
int x;
int y;
bool active;
} crawler_shot_t;
typedef struct {
uint32_t segment_mask;
int head_segment;
int direction;
int vertical_direction;
} crawler_section_t;
static crawler_segment_t segments[MAX_CRAWLER_SEGMENTS];
static int8_t followers[MAX_CRAWLER_SEGMENTS];
static crawler_shot_t shots[MAX_SHOTS];
static crawler_section_t sections[MAX_CRAWLER_SEGMENTS];
static uint8_t mushrooms[MUSHROOM_ROWS][MUSHROOM_COLUMNS];
static const uint8_t mushroom_bitmap[8] = {
0x00, 0x1C, 0x3E, 0x7F, 0x7F, 0x7F, 0x1C, 0x1C,
};
static crawler_state_t state;
static uint32_t score;
static uint32_t wave;
static uint32_t last_update;
static uint32_t crawler_last_step;
static uint32_t shot_last_step;
static uint32_t next_fire;
static uint32_t state_started;
static uint8_t lives;
static int player_x;
static int player_y;
static uint32_t top_spawn_ready_at;
static bool controls_ready;
static bool running;
static int clampValue(int value, int minimum, int maximum)
{
return value < minimum ? minimum : value > maximum ? maximum : value;
}
static int absoluteValue(int value)
{
return value < 0 ? -value : value;
}
static int playerDiamondHalfWidth(int y_offset)
{
const int distance = absoluteValue(y_offset);
if (distance > PLAYER_HALF_HEIGHT) return -1;
return PLAYER_HALF_WIDTH * (PLAYER_HALF_HEIGHT - distance) / PLAYER_HALF_HEIGHT;
}
static bool crawlerHitsPlayer(const crawler_segment_t *segment)
{
if (segment == NULL) return false;
const int player_centre_x = player_x + PLAYER_HALF_WIDTH;
const int player_centre_y = player_y + PLAYER_HALF_HEIGHT;
for (int y = segment->y; y < segment->y + SEGMENT_SIZE; ++y) {
const int half_width = playerDiamondHalfWidth(y - player_centre_y);
if (half_width < 0) continue;
for (int x = segment->x; x < segment->x + SEGMENT_SIZE; ++x) {
if (absoluteValue(x - player_centre_x) <= half_width) return true;
}
}
return false;
}
static unsigned randomCrawlerLength(void)
{
return CRAWLER_MIN_SEGMENTS +
esp_random() % (CRAWLER_MAX_SEGMENTS - CRAWLER_MIN_SEGMENTS + 1U);
}
static bool topSpawnReady(uint32_t now_ms)
{
return (int32_t)(now_ms - top_spawn_ready_at) >= 0;
}
static uint32_t sectionMaskFromHead(int head_segment)
{
uint32_t mask = 0U;
for (int segment = head_segment; segment >= 0; segment = followers[(unsigned)segment]) {
mask |= 1U << (unsigned)segment;
}
return mask;
}
static void configureCrawlerSection(unsigned section, unsigned first_segment, unsigned count,
int head_x, int head_y, int direction,
int vertical_direction)
{
uint32_t mask = 0U;
for (unsigned offset = 0U; offset < count; ++offset) {
const unsigned segment = first_segment + offset;
segments[segment].x = head_x - direction * (int)offset * SEGMENT_SIZE;
segments[segment].y = head_y;
segments[segment].alive = true;
followers[segment] = offset + 1U < count ? (int8_t)(segment + 1U) : -1;
mask |= 1U << segment;
}
sections[section].segment_mask = mask;
sections[section].head_segment = (int)first_segment;
sections[section].direction = direction;
sections[section].vertical_direction = vertical_direction;
}
static void resetMushrooms(void)
{
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
mushrooms[row][column] = (esp_random() % 100U) < MUSHROOM_DENSITY_PERCENT ?
MUSHROOM_HEALTH : 0U;
}
}
}
static void resetCrawler(void)
{
const unsigned count = INITIAL_CRAWLER_SEGMENTS;
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
segments[index].alive = false;
followers[index] = -1;
sections[index].segment_mask = 0U;
sections[index].head_segment = -1;
sections[index].direction = 0;
sections[index].vertical_direction = 0;
}
configureCrawlerSection(0U, 0U, count, LCD_WIDTH - (int)count * SEGMENT_SIZE - 10,
PLAYFIELD_TOP + 5, -1, 1);
top_spawn_ready_at = 0U;
}
static void resetShots(void)
{
for (unsigned index = 0U; index < MAX_SHOTS; ++index) shots[index].active = false;
}
static unsigned remainingSegments(void)
{
unsigned remaining = 0U;
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
if (segments[index].alive) ++remaining;
}
return remaining;
}
static int sectionLeader(unsigned section)
{
if (section >= MAX_CRAWLER_SEGMENTS || sections[section].segment_mask == 0U ||
sections[section].head_segment < 0) return -1;
return sections[section].head_segment;
}
static bool segmentIsHead(unsigned segment)
{
for (unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++section) {
if (sectionLeader(section) == (int)segment) return true;
}
return false;
}
static void splitCrawlerAt(unsigned segment)
{
const uint32_t hit_bit = 1U << segment;
for (unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++section) {
if ((sections[section].segment_mask & hit_bit) == 0U) continue;
const int original_head = sections[section].head_segment;
const int original_direction = sections[section].direction;
const int original_vertical_direction = sections[section].vertical_direction;
int predecessor = -1;
int current = original_head;
while (current >= 0 && current != (int)segment) {
predecessor = current;
current = followers[(unsigned)current];
}
if (current < 0) return;
const int rear_head = followers[segment];
if (predecessor >= 0) followers[(unsigned)predecessor] = -1;
followers[segment] = -1;
if (predecessor >= 0) {
sections[section].head_segment = original_head;
sections[section].segment_mask = sectionMaskFromHead(original_head);
} else if (rear_head >= 0) {
sections[section].head_segment = rear_head;
sections[section].segment_mask = sectionMaskFromHead(rear_head);
} else {
sections[section].head_segment = -1;
sections[section].segment_mask = 0U;
}
if (predecessor >= 0 && rear_head >= 0) {
for (unsigned free_section = 0U; free_section < MAX_CRAWLER_SEGMENTS;
++free_section) {
if (sections[free_section].segment_mask == 0U) {
sections[free_section].head_segment = rear_head;
sections[free_section].segment_mask = sectionMaskFromHead(rear_head);
sections[free_section].direction = -original_direction;
sections[free_section].vertical_direction = original_vertical_direction;
return;
}
}
}
return;
}
}
static bool crawlerHitsMushroom(int x, int y)
{
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
if (mushrooms[row][column] == 0U) continue;
const int mushroom_x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET;
const int mushroom_y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING +
MUSHROOM_Y_OFFSET;
if (x < mushroom_x + MUSHROOM_WIDTH && x + SEGMENT_SIZE > mushroom_x &&
y < mushroom_y + MUSHROOM_HEIGHT && y + SEGMENT_SIZE > mushroom_y) {
return true;
}
}
}
return false;
}
static bool damageMushroomAt(int x, int y)
{
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
if (mushrooms[row][column] == 0U) continue;
const int mushroom_x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET;
const int mushroom_y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING +
MUSHROOM_Y_OFFSET;
if (x >= mushroom_x && x < mushroom_x + MUSHROOM_WIDTH &&
y >= mushroom_y && y < mushroom_y + MUSHROOM_HEIGHT) {
--mushrooms[row][column];
return true;
}
}
}
return false;
}
static void addMushroomAt(int x, int y)
{
const int centre_x = x + SEGMENT_SIZE / 2;
const int centre_y = y + SEGMENT_SIZE / 2;
const int column = (centre_x - MUSHROOM_X_OFFSET + MUSHROOM_X_SPACING / 2) /
MUSHROOM_X_SPACING;
const int row = (centre_y - PLAYFIELD_TOP - MUSHROOM_Y_OFFSET + MUSHROOM_Y_SPACING / 2) /
MUSHROOM_Y_SPACING;
if (row >= 0 && row < (int)MUSHROOM_ROWS && column >= 0 &&
column < (int)MUSHROOM_COLUMNS) {
mushrooms[(unsigned)row][(unsigned)column] = MUSHROOM_HEALTH;
}
}
static void beginGame(uint32_t now_ms)
{
score = 0U;
wave = 1U;
lives = 3U;
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2;
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2;
resetMushrooms();
resetCrawler();
resetShots();
crawler_last_step = now_ms;
shot_last_step = now_ms;
next_fire = now_ms;
state = CRAWLER_PLAYING;
audioPlay(AUDIO_SFX_GAME_START);
}
static void loseLife(uint32_t now_ms)
{
if (lives > 0U) --lives;
resetShots();
audioPlay(AUDIO_SFX_PLAYER_EXPLOSION);
if (lives == 0U) {
state = CRAWLER_FINISHED;
state_started = now_ms;
ESP_LOGI("crawler", "GAME OVER: SCORE %u WAVE %u", (unsigned)score, (unsigned)wave);
return;
}
state = CRAWLER_LIFE_LOST;
state_started = now_ms;
}
static void fireShot(void)
{
for (unsigned index = 0U; index < MAX_SHOTS; ++index) {
if (!shots[index].active) {
shots[index].x = player_x + PLAYER_WIDTH / 2;
shots[index].y = player_y - 2;
shots[index].active = true;
audioPlay(AUDIO_SFX_PLAYER_SHOT);
return;
}
}
}
static bool findFreeSegmentRun(unsigned count, unsigned *first_segment)
{
if (first_segment == NULL || count == 0U || count > MAX_CRAWLER_SEGMENTS) return false;
for (unsigned first = 0U; first + count <= MAX_CRAWLER_SEGMENTS; ++first) {
bool available = true;
for (unsigned offset = 0U; offset < count; ++offset) {
if (segments[first + offset].alive) {
available = false;
break;
}
}
if (available) {
*first_segment = first;
return true;
}
}
return false;
}
static void spawnTopCrawler(uint32_t now_ms)
{
if (!topSpawnReady(now_ms)) return;
const unsigned count = randomCrawlerLength();
unsigned first_segment = 0U;
unsigned free_section = 0U;
if (!findFreeSegmentRun(count, &first_segment)) return;
while (free_section < MAX_CRAWLER_SEGMENTS &&
sections[free_section].segment_mask != 0U) {
++free_section;
}
if (free_section == MAX_CRAWLER_SEGMENTS) return;
const int first_x = (int)(esp_random() %
(LCD_WIDTH - (int)count * SEGMENT_SIZE));
const int direction = (esp_random() & 1U) == 0U ? -1 : 1;
const int head_x = direction < 0 ? first_x : first_x + (int)(count - 1U) * SEGMENT_SIZE;
configureCrawlerSection(free_section, first_segment, count, head_x, PLAYFIELD_TOP + 5,
direction, 1);
top_spawn_ready_at = now_ms + TOP_SPAWN_GUARD_MS;
}
static void updateShots(void)
{
for (unsigned index = 0U; index < MAX_SHOTS; ++index) {
if (!shots[index].active) continue;
shots[index].y -= 4;
if (shots[index].y < PLAYFIELD_TOP) {
shots[index].active = false;
continue;
}
bool hit_segment = false;
for (unsigned segment = 0U; segment < MAX_CRAWLER_SEGMENTS; ++segment) {
if (!segments[segment].alive) continue;
if (shots[index].x >= segments[segment].x &&
shots[index].x < segments[segment].x + SEGMENT_SIZE &&
shots[index].y >= segments[segment].y &&
shots[index].y < segments[segment].y + SEGMENT_SIZE) {
splitCrawlerAt(segment);
addMushroomAt(segments[segment].x, segments[segment].y);
segments[segment].alive = false;
shots[index].active = false;
score = score > UINT32_MAX - 10U ? UINT32_MAX : score + 10U;
hit_segment = true;
audioPlay(AUDIO_SFX_HIT);
break;
}
}
if (!hit_segment && damageMushroomAt(shots[index].x, shots[index].y)) {
shots[index].active = false;
}
}
}
static void moveCrawler(uint32_t now_ms)
{
bool top_spawn_requested = false;
for (unsigned section = 0U; section < MAX_CRAWLER_SEGMENTS; ++section) {
const int leader = sectionLeader(section);
if (leader < 0) continue;
const int previous_head_x = segments[(unsigned)leader].x;
const int previous_head_y = segments[(unsigned)leader].y;
const int next_x = previous_head_x + sections[section].direction * SEGMENT_SIZE;
const bool blocked = next_x < 0 || next_x + SEGMENT_SIZE > LCD_WIDTH ||
crawlerHitsMushroom(next_x, previous_head_y);
if (blocked) {
sections[section].direction = -sections[section].direction;
int vertical_step = sections[section].vertical_direction * MUSHROOM_Y_SPACING;
const int next_y = previous_head_y + vertical_step;
if (next_y < PLAYFIELD_TOP || next_y + SEGMENT_SIZE > LCD_HEIGHT) {
if (topSpawnReady(now_ms)) {
top_spawn_requested = true;
}
sections[section].vertical_direction = -sections[section].vertical_direction;
vertical_step = sections[section].vertical_direction * MUSHROOM_Y_SPACING;
}
segments[(unsigned)leader].y += vertical_step;
} else {
segments[(unsigned)leader].x = next_x;
}
int follower = followers[(unsigned)leader];
int previous_x = previous_head_x;
int previous_y = previous_head_y;
while (follower >= 0) {
const int follower_x = segments[(unsigned)follower].x;
const int follower_y = segments[(unsigned)follower].y;
segments[(unsigned)follower].x = previous_x;
segments[(unsigned)follower].y = previous_y;
previous_x = follower_x;
previous_y = follower_y;
follower = followers[(unsigned)follower];
}
}
if (top_spawn_requested) spawnTopCrawler(now_ms);
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
if (!segments[index].alive) continue;
if (crawlerHitsPlayer(&segments[index])) {
loseLife(now_ms);
return;
}
}
}
void startCrawler(uint32_t now_ms)
{
running = true;
state = CRAWLER_READY;
score = 0U;
wave = 1U;
lives = 3U;
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2;
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2;
resetMushrooms();
resetCrawler();
resetShots();
last_update = now_ms;
crawler_last_step = now_ms;
shot_last_step = now_ms;
next_fire = now_ms;
controls_ready = false;
ESP_LOGI("crawler", "START");
}
void stopCrawler(void)
{
cancelHighScoreEntry();
running = false;
ESP_LOGI("crawler", "STOP");
}
void crawlerAction(bool action, bool fire_held, uint32_t now_ms)
{
if (!running) return;
if (state == CRAWLER_READY) {
if (action) beginGame(now_ms);
return;
}
if (state != CRAWLER_PLAYING || !fire_held || now_ms < next_fire) return;
fireShot();
next_fire = now_ms + FIRE_INTERVAL_MS;
}
void updateCrawler(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 == CRAWLER_FINISHED) {
if (now_ms - state_started >= 1000U) {
startHighScoreEntry(HIGH_SCORE_CRAWLER, score, now_ms);
state = CRAWLER_SCORES;
}
return;
}
if (state == CRAWLER_SCORES) {
if (updateHighScoreEntry(x_offset, y_offset, joystick_valid, now_ms)) state = CRAWLER_READY;
return;
}
if (state == CRAWLER_LIFE_LOST) {
if (now_ms - state_started >= LIFE_LOST_MS) {
player_x = (LCD_WIDTH - PLAYER_WIDTH) / 2;
player_y = LCD_HEIGHT - PLAYER_HEIGHT - 2;
resetCrawler();
crawler_last_step = now_ms;
state = CRAWLER_PLAYING;
}
return;
}
if (state != CRAWLER_PLAYING) return;
if (!joystick_valid) controls_ready = false;
else if (x_offset >= -350 && x_offset <= 350) controls_ready = true;
const int direction = left == right ? 0 : left ? -1 : 1;
const int joystick_move = joystick_valid && controls_ready && !left && !right
? x_offset / 600 : 0;
const int joystick_vertical_move = joystick_valid && controls_ready ? y_offset / 600 : 0;
const int elapsed_limited = (int)(elapsed > 30U ? 30U : elapsed);
const int button_move = direction * elapsed_limited / 2;
player_x = clampValue(player_x + (left || right ? button_move : joystick_move), 0,
LCD_WIDTH - PLAYER_WIDTH);
player_y = clampValue(player_y - joystick_vertical_move, PLAYER_ZONE_TOP,
LCD_HEIGHT - PLAYER_HEIGHT - 2);
while (now_ms - shot_last_step >= SHOT_STEP_MS) {
shot_last_step += SHOT_STEP_MS;
updateShots();
}
while (now_ms - crawler_last_step >= CRAWLER_STEP_MS && state == CRAWLER_PLAYING) {
crawler_last_step += CRAWLER_STEP_MS;
moveCrawler(now_ms);
}
if (state == CRAWLER_PLAYING && remainingSegments() == 0U) {
audioPlay(AUDIO_SFX_WAVE_CLEAR);
if (wave < UINT32_MAX) ++wave;
resetCrawler();
}
}
void drawCrawler(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == CRAWLER_SCORES) {
drawHighScoreEntry(frame);
return;
}
clear(frame, COLOR_BLACK);
char text[28];
if (state == CRAWLER_READY) {
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 10, "CRAWLER", COLOR_LIME, 2);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 42, "CLEAR THE CRAWLER", COLOR_CYAN, 1);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 63, "10 POINTS EACH", COLOR_YELLOW, 1);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 109, "D/B OR STICK MOVE", COLOR_LIGHTGRAY, 1);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 124, "A/K FIRE", COLOR_LIGHTGRAY, 1);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 151, "A/K START", COLOR_YELLOW, 2);
return;
}
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, LCD_WIDTH / 2, 3, text, COLOR_CYAN, 1);
snprintf(text, sizeof(text), "WAVE %u", (unsigned)wave);
drawPet2001TextTransparent(frame, 253, 3, text, COLOR_YELLOW, 1);
draw_line(frame, 0, 13, LCD_WIDTH - 1, 13, COLOR_DARKGRAY);
for (unsigned row = 0U; row < MUSHROOM_ROWS; ++row) {
for (unsigned column = 0U; column < MUSHROOM_COLUMNS; ++column) {
if (mushrooms[row][column] == 0U) continue;
const int x = (int)column * MUSHROOM_X_SPACING + MUSHROOM_X_OFFSET;
const int y = PLAYFIELD_TOP + (int)row * MUSHROOM_Y_SPACING +
MUSHROOM_Y_OFFSET;
const uint16_t cap_colour = mushrooms[row][column] == 4U ? COLOR_MAGENTA :
mushrooms[row][column] == 3U ? COLOR_CYAN :
mushrooms[row][column] == 2U ? COLOR_YELLOW : COLOR_WHITE;
draw_ui_bitmap_8x8(frame, x, y, mushroom_bitmap, 1, cap_colour);
}
}
for (unsigned index = 0U; index < MAX_CRAWLER_SEGMENTS; ++index) {
if (!segments[index].alive) continue;
const uint16_t colour = segmentIsHead(index) ? COLOR_RED : COLOR_LIME;
fill_circle(frame, segments[index].x + SEGMENT_SIZE / 2,
segments[index].y + SEGMENT_SIZE / 2, SEGMENT_SIZE / 2, colour);
}
for (unsigned index = 0U; index < MAX_SHOTS; ++index) {
if (shots[index].active) draw_line(frame, shots[index].x, shots[index].y,
shots[index].x, shots[index].y + 4, COLOR_WHITE);
}
const int player_centre_x = player_x + PLAYER_HALF_WIDTH;
const int player_centre_y = player_y + PLAYER_HALF_HEIGHT;
for (int y_offset = -PLAYER_HALF_HEIGHT; y_offset <= PLAYER_HALF_HEIGHT; ++y_offset) {
const int half_width = playerDiamondHalfWidth(y_offset);
fill_rect(frame, player_centre_x - half_width, player_centre_y + y_offset,
half_width * 2 + 1, 1, COLOR_CYAN);
}
if (state == CRAWLER_LIFE_LOST) {
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 83, "LIFE LOST", COLOR_RED, 2);
} else if (state == CRAWLER_FINISHED) {
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 83, "GAME OVER", COLOR_RED, 2);
}
}