Boulder Dash
Source: src/boulder_dash.c · 720 lines · 17 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
boulderDashNextLevelRandom() · source line 155
static uint32_t boulderDashNextLevelRandom(void)
- Execute
boulder_dash_level_random_state = boulder_dash_level_random_state * 1664525U + 1013904223U; - Exit this function
return boulder_dash_level_random_state;
Read original function source
static uint32_t boulderDashNextLevelRandom(void)
{
// Tiny deterministic PRNG: its state begins from the level's fixed seed,
// so the generated layouts do not vary between separate plays.
boulder_dash_level_random_state = boulder_dash_level_random_state * 1664525U + 1013904223U;
return boulder_dash_level_random_state;
}boulderDashIsReservedLevelCell() · source line 163
static bool boulderDashIsReservedLevelCell(int row, int column)
- Exit this function
return (row <= 2 && column <= 2) || (row >= BOULDER_DASH_CAVE_ROWS - 3 && column >= BOULDER_DASH_CAVE_COLUMNS - 3);
Read original function source
static bool boulderDashIsReservedLevelCell(int row, int column)
{
// Preserve an open, safe two-by-two area around the upper-left spawn and
// lower-right exit, independent of generated barrier/item placement.
return (row <= 2 && column <= 2) ||
(row >= BOULDER_DASH_CAVE_ROWS - 3 &&
column >= BOULDER_DASH_CAVE_COLUMNS - 3);
}boulderDashWalkableCaveIsConnected() · source line 172
static bool boulderDashWalkableCaveIsConnected(void)
- Execute
bool visited[BOULDER_DASH_CAVE_ROWS][BOULDER_DASH_CAVE_COLUMNS] = {{false}}; - Execute
uint8_t pending_cells[BOULDER_DASH_CAVE_ROWS * BOULDER_DASH_CAVE_COLUMNS]; - Execute
uint16_t read_index = 0U; - Execute
uint16_t write_index = 0U; - Execute
pending_cells[write_index++] = BOULDER_DASH_CAVE_COLUMNS + 1U; - Execute
visited[1][1] = true; - WHILE loop
read_index < write_indexRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t cell = pending_cells[read_index++]; - Execute
const int row = cell / BOULDER_DASH_CAVE_COLUMNS; - Execute
const int column = cell % BOULDER_DASH_CAVE_COLUMNS; - Execute
static const int row_offsets[4] = {-1, 1, 0, 0}; - Execute
static const int column_offsets[4] = {0, 0, -1, 1}; - FOR loop
int direction = 0; direction < 4; ++directionRepeat body while the loop condition permits; then continue below.
- Execute
const int next_row = row + row_offsets[direction]; - Execute
const int next_column = column + column_offsets[direction]; - Decision
next_row < 0 || next_row >= BOULDER_DASH_CAVE_ROWS || next_column < 0 || next_column >= BOULDER_DASH_CAVE_COLUMNS || visited[next_row][next_column] || boulder_dash_cave[next_row][next_column] == BOULDER_DASH_STEELTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
visited[next_row][next_column] = true; - Execute
pending_cells[write_index++] = (uint8_t)(next_row * BOULDER_DASH_CAVE_COLUMNS + next_column);
- FOR loop
int row = 1; row < BOULDER_DASH_CAVE_ROWS - 1; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 1; column < BOULDER_DASH_CAVE_COLUMNS - 1; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
boulder_dash_cave[row][column] != BOULDER_DASH_STEEL && !visited[row][column]True- Exit this function
return false;
FalseContinue after this decision.
- Exit this function
return true;
Read original function source
static bool boulderDashWalkableCaveIsConnected(void)
{
// Each proposed steel block is accepted only if every remaining non-steel
// tile can still be reached from the spawn. This prevents a generated wall
// pattern from sealing off part of the cave before gems are added.
bool visited[BOULDER_DASH_CAVE_ROWS][BOULDER_DASH_CAVE_COLUMNS] = {{false}};
uint8_t pending_cells[BOULDER_DASH_CAVE_ROWS * BOULDER_DASH_CAVE_COLUMNS];
uint16_t read_index = 0U;
uint16_t write_index = 0U;
pending_cells[write_index++] = BOULDER_DASH_CAVE_COLUMNS + 1U;
visited[1][1] = true;
while (read_index < write_index) {
const uint8_t cell = pending_cells[read_index++];
const int row = cell / BOULDER_DASH_CAVE_COLUMNS;
const int column = cell % BOULDER_DASH_CAVE_COLUMNS;
static const int row_offsets[4] = {-1, 1, 0, 0};
static const int column_offsets[4] = {0, 0, -1, 1};
for (int direction = 0; direction < 4; ++direction) {
const int next_row = row + row_offsets[direction];
const int next_column = column + column_offsets[direction];
if (next_row < 0 || next_row >= BOULDER_DASH_CAVE_ROWS ||
next_column < 0 || next_column >= BOULDER_DASH_CAVE_COLUMNS ||
visited[next_row][next_column] ||
boulder_dash_cave[next_row][next_column] == BOULDER_DASH_STEEL) {
continue;
}
visited[next_row][next_column] = true;
pending_cells[write_index++] =
(uint8_t)(next_row * BOULDER_DASH_CAVE_COLUMNS + next_column);
}
}
for (int row = 1; row < BOULDER_DASH_CAVE_ROWS - 1; ++row) {
for (int column = 1; column < BOULDER_DASH_CAVE_COLUMNS - 1; ++column) {
if (boulder_dash_cave[row][column] != BOULDER_DASH_STEEL &&
!visited[row][column]) {
return false;
}
}
}
return true;
}placeProceduralLevelTiles() · source line 217
static void placeProceduralLevelTiles(boulder_dash_tile_t tile, uint8_t count)
Calls into this documentation:
- Execute
uint8_t placed = 0U; - Execute
uint16_t attempts = 0U; - WHILE loop
placed < count && attempts++ < BOULDER_DASH_LEVEL_PLACEMENT_ATTEMPTSRepeat body while the loop condition permits; then continue below.
- Execute
const int row = 1 + (int)(boulderDashNextLevelRandom() % (BOULDER_DASH_CAVE_ROWS - 2)); - Execute
const int column = 1 + (int)(boulderDashNextLevelRandom() % (BOULDER_DASH_CAVE_COLUMNS - 2)); - Decision
boulderDashIsReservedLevelCell(row, column) || boulder_dash_cave[row][column] != BOULDER_DASH_DIRTTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
tile == BOULDER_DASH_BOULDER && boulder_dash_cave[row + 1][column] != BOULDER_DASH_DIRTTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
boulder_dash_cave[row][column] = tile; - Execute
++placed;
Read original function source
static void placeProceduralLevelTiles(boulder_dash_tile_t tile, uint8_t count)
{
uint8_t placed = 0U;
uint16_t attempts = 0U;
while (placed < count && attempts++ < BOULDER_DASH_LEVEL_PLACEMENT_ATTEMPTS) {
const int row = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_ROWS - 2));
const int column = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_COLUMNS - 2));
if (boulderDashIsReservedLevelCell(row, column) ||
boulder_dash_cave[row][column] != BOULDER_DASH_DIRT) {
continue;
}
// New boulders begin on dirt rather than open space, so they do not
// drop the instant the level begins.
if (tile == BOULDER_DASH_BOULDER &&
boulder_dash_cave[row + 1][column] != BOULDER_DASH_DIRT) {
continue;
}
boulder_dash_cave[row][column] = tile;
++placed;
}
}buildProceduralBoulderDashLevel() · source line 241
static void buildProceduralBoulderDashLevel(const boulder_dash_level_definition_t *level)
Calls into this documentation:
- Execute
boulder_dash_level_random_state = level->seed; - FOR loop
int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
boulder_dash_cave[row][column] = row == 0 || row == BOULDER_DASH_CAVE_ROWS - 1 || column == 0 || column == BOULDER_DASH_CAVE_COLUMNS - 1 ? BOULDER_DASH_STEEL : BOULDER_DASH_DIRT;
- Execute
boulder_dash_cave[1][1] = BOULDER_DASH_PLAYER; - Execute
boulder_dash_cave[1][2] = BOULDER_DASH_EMPTY; - Execute
boulder_dash_cave[2][1] = BOULDER_DASH_EMPTY; - Execute
boulder_dash_cave[2][2] = BOULDER_DASH_EMPTY; - Execute
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 2][BOULDER_DASH_CAVE_COLUMNS - 2] = BOULDER_DASH_EXIT; - Execute
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 2][BOULDER_DASH_CAVE_COLUMNS - 3] = BOULDER_DASH_EMPTY; - Execute
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 3][BOULDER_DASH_CAVE_COLUMNS - 2] = BOULDER_DASH_EMPTY; - Execute
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 3][BOULDER_DASH_CAVE_COLUMNS - 3] = BOULDER_DASH_EMPTY; - Execute
uint8_t steel_placed = 0U; - Execute
uint16_t attempts = 0U; - WHILE loop
steel_placed < level->steel_blocks && attempts++ < BOULDER_DASH_LEVEL_PLACEMENT_ATTEMPTSRepeat body while the loop condition permits; then continue below.
- Execute
const int row = 1 + (int)(boulderDashNextLevelRandom() % (BOULDER_DASH_CAVE_ROWS - 2)); - Execute
const int column = 1 + (int)(boulderDashNextLevelRandom() % (BOULDER_DASH_CAVE_COLUMNS - 2)); - Decision
boulderDashIsReservedLevelCell(row, column) || boulder_dash_cave[row][column] != BOULDER_DASH_DIRTTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
boulder_dash_cave[row][column] = BOULDER_DASH_STEEL; - Decision
boulderDashWalkableCaveIsConnected()True- Execute
++steel_placed;
False- Execute
boulder_dash_cave[row][column] = BOULDER_DASH_DIRT;
- Execute
placeProceduralLevelTiles(BOULDER_DASH_DIAMOND, level->diamonds); - Execute
placeProceduralLevelTiles(BOULDER_DASH_BOULDER, level->boulders);
Read original function source
static void buildProceduralBoulderDashLevel(const boulder_dash_level_definition_t *level)
{
boulder_dash_level_random_state = level->seed;
for (int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
boulder_dash_cave[row][column] =
row == 0 || row == BOULDER_DASH_CAVE_ROWS - 1 ||
column == 0 || column == BOULDER_DASH_CAVE_COLUMNS - 1
? BOULDER_DASH_STEEL
: BOULDER_DASH_DIRT;
}
}
boulder_dash_cave[1][1] = BOULDER_DASH_PLAYER;
boulder_dash_cave[1][2] = BOULDER_DASH_EMPTY;
boulder_dash_cave[2][1] = BOULDER_DASH_EMPTY;
boulder_dash_cave[2][2] = BOULDER_DASH_EMPTY;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 2][BOULDER_DASH_CAVE_COLUMNS - 2] =
BOULDER_DASH_EXIT;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 2][BOULDER_DASH_CAVE_COLUMNS - 3] =
BOULDER_DASH_EMPTY;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 3][BOULDER_DASH_CAVE_COLUMNS - 2] =
BOULDER_DASH_EMPTY;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 3][BOULDER_DASH_CAVE_COLUMNS - 3] =
BOULDER_DASH_EMPTY;
uint8_t steel_placed = 0U;
uint16_t attempts = 0U;
while (steel_placed < level->steel_blocks &&
attempts++ < BOULDER_DASH_LEVEL_PLACEMENT_ATTEMPTS) {
const int row = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_ROWS - 2));
const int column = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_COLUMNS - 2));
if (boulderDashIsReservedLevelCell(row, column) ||
boulder_dash_cave[row][column] != BOULDER_DASH_DIRT) {
continue;
}
boulder_dash_cave[row][column] = BOULDER_DASH_STEEL;
if (boulderDashWalkableCaveIsConnected()) {
++steel_placed;
} else {
boulder_dash_cave[row][column] = BOULDER_DASH_DIRT;
}
}
placeProceduralLevelTiles(BOULDER_DASH_DIAMOND, level->diamonds);
placeProceduralLevelTiles(BOULDER_DASH_BOULDER, level->boulders);
}resetBoulderDashCave() · source line 291
static void resetBoulderDashCave(uint32_t now_ms, bool reset_run_score)
Calls into this documentation:
- Execute
const boulder_dash_level_definition_t *const level = &boulder_dash_levels[boulder_dash_level_index]; - Decision
boulder_dash_level_index == 0UTrue- Execute
memcpy(boulder_dash_cave, boulder_dash_level_one_cave, sizeof(boulder_dash_cave));
False- Execute
buildProceduralBoulderDashLevel(level);
- Execute
boulder_dash_diamonds_collected = 0U; - Execute
boulder_dash_diamonds_required = 0U; - Decision
reset_run_scoreTrue- Execute
boulder_dash_score = 0;
FalseContinue after this decision.
- Execute
boulder_dash_roll_left_first = true; - FOR loop
int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
boulder_dash_cave[row][column] == BOULDER_DASH_PLAYERTrue- Execute
boulder_dash_player_column = column; - Execute
boulder_dash_player_row = row; - Execute
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
False- Decision
boulder_dash_cave[row][column] == BOULDER_DASH_DIAMONDTrue- Execute
++boulder_dash_diamonds_required;
FalseContinue after this decision.
- Execute
boulder_dash_state = BOULDER_DASH_PLAYING; - Execute
boulder_dash_last_move_at = now_ms; - Execute
boulder_dash_last_gravity_at = now_ms;
Read original function source
static void resetBoulderDashCave(uint32_t now_ms, bool reset_run_score)
{
const boulder_dash_level_definition_t *const level =
&boulder_dash_levels[boulder_dash_level_index];
if (boulder_dash_level_index == 0U) {
memcpy(boulder_dash_cave, boulder_dash_level_one_cave, sizeof(boulder_dash_cave));
} else {
buildProceduralBoulderDashLevel(level);
}
boulder_dash_diamonds_collected = 0U;
boulder_dash_diamonds_required = 0U;
if (reset_run_score) boulder_dash_score = 0;
boulder_dash_roll_left_first = true;
// The player is stored as coordinates rather than a map tile so the tile
// beneath them remains available for boulders and other future interactions.
for (int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
if (boulder_dash_cave[row][column] == BOULDER_DASH_PLAYER) {
boulder_dash_player_column = column;
boulder_dash_player_row = row;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
} else if (boulder_dash_cave[row][column] == BOULDER_DASH_DIAMOND) {
++boulder_dash_diamonds_required;
}
}
}
boulder_dash_state = BOULDER_DASH_PLAYING;
boulder_dash_last_move_at = now_ms;
boulder_dash_last_gravity_at = now_ms;
}boulderDashCrushPlayer() · source line 324
static void boulderDashCrushPlayer(void)
- Decision
boulder_dash_lives > 0UTrue- Execute
--boulder_dash_lives;
FalseContinue after this decision.
- Execute
boulder_dash_state = boulder_dash_lives == 0U ? BOULDER_DASH_GAME_OVER : BOULDER_DASH_CRUSHED;
Read original function source
static void boulderDashCrushPlayer(void)
{
// A loss always consumes one of the run's lives. The level remains ready
// for an A/K restart while a life remains; the third loss ends the run.
if (boulder_dash_lives > 0U) --boulder_dash_lives;
boulder_dash_state = boulder_dash_lives == 0U
? BOULDER_DASH_GAME_OVER
: BOULDER_DASH_CRUSHED;
}drawBoulderDashTile() · source line 334
static void drawBoulderDashTile(uint16_t *frame, int x, int y, boulder_dash_tile_t tile,
bool exit_open)Calls into this documentation:
- Select a case
tileEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case BOULDER_DASH_EMPTY: - Execute
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK); - Exit enclosing loop or switch
break; - Case / label
case BOULDER_DASH_DIRT: - Execute
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_SADDLEBROWN); - Execute
draw_bitmap_scaled(frame, x, y, boulder_dash_dirt_mask, 16, 16, 16, 16, COLOR_PERU); - Exit enclosing loop or switch
break; - Case / label
case BOULDER_DASH_STEEL: - Execute
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_DARKSLATEGRAY); - Execute
draw_bitmap_scaled(frame, x, y, boulder_dash_steel_mask, 16, 16, 16, 16, COLOR_LIGHTGREY); - Exit enclosing loop or switch
break; - Case / label
case BOULDER_DASH_BOULDER: - Execute
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK); - Execute
draw_bitmap_scaled(frame, x, y, boulder_dash_boulder_mask, 16, 16, 16, 16, COLOR_SLATEGRAY); - Exit enclosing loop or switch
break; - Case / label
case BOULDER_DASH_DIAMOND: - Execute
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_DARKBLUE); - Execute
draw_bitmap_scaled(frame, x, y, boulder_dash_diamond_mask, 16, 16, 16, 16, COLOR_CYAN); - Exit enclosing loop or switch
break; - Case / label
case BOULDER_DASH_PLAYER: - Execute
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK); - Execute
draw_bitmap_scaled(frame, x, y, boulder_dash_player_mask, 16, 16, 16, 16, COLOR_ORANGE); - Exit enclosing loop or switch
break; - Case / label
case BOULDER_DASH_EXIT: - Execute
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, exit_open ? COLOR_DARKGREEN : COLOR_DARKRED); - Execute
draw_bitmap_scaled(frame, x, y, boulder_dash_exit_mask, 16, 16, 16, 16, exit_open ? COLOR_YELLOW : COLOR_LIGHTGREY); - Exit enclosing loop or switch
break;
Read original function source
static void drawBoulderDashTile(uint16_t *frame, int x, int y, boulder_dash_tile_t tile,
bool exit_open)
{
switch (tile) {
case BOULDER_DASH_EMPTY:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK);
break;
case BOULDER_DASH_DIRT:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_SADDLEBROWN);
draw_bitmap_scaled(frame, x, y, boulder_dash_dirt_mask, 16, 16, 16, 16, COLOR_PERU);
break;
case BOULDER_DASH_STEEL:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_DARKSLATEGRAY);
draw_bitmap_scaled(frame, x, y, boulder_dash_steel_mask, 16, 16, 16, 16, COLOR_LIGHTGREY);
break;
case BOULDER_DASH_BOULDER:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK);
draw_bitmap_scaled(frame, x, y, boulder_dash_boulder_mask, 16, 16, 16, 16, COLOR_SLATEGRAY);
break;
case BOULDER_DASH_DIAMOND:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_DARKBLUE);
draw_bitmap_scaled(frame, x, y, boulder_dash_diamond_mask, 16, 16, 16, 16, COLOR_CYAN);
break;
case BOULDER_DASH_PLAYER:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK);
draw_bitmap_scaled(frame, x, y, boulder_dash_player_mask, 16, 16, 16, 16, COLOR_ORANGE);
break;
case BOULDER_DASH_EXIT:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE,
exit_open ? COLOR_DARKGREEN : COLOR_DARKRED);
draw_bitmap_scaled(frame, x, y, boulder_dash_exit_mask, 16, 16, 16, 16,
exit_open ? COLOR_YELLOW : COLOR_LIGHTGREY);
break;
}
}boulderDashExitOpen() · source line 370
static bool boulderDashExitOpen(void)
- Exit this function
return boulder_dash_diamonds_collected >= boulder_dash_diamonds_required;
Read original function source
static bool boulderDashExitOpen(void)
{
return boulder_dash_diamonds_collected >= boulder_dash_diamonds_required;
}tryMoveBoulderDashPlayer() · source line 375
static void tryMoveBoulderDashPlayer(int column_delta, int row_delta, bool hold_a_to_stay,
uint32_t now_ms)Calls into this documentation:
- Decision
boulder_dash_state != BOULDER_DASH_PLAYINGTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const int next_column = boulder_dash_player_column + column_delta; - Execute
const int next_row = boulder_dash_player_row + row_delta; - Decision
next_column < 0 || next_column >= BOULDER_DASH_CAVE_COLUMNS || next_row < 0 || next_row >= BOULDER_DASH_CAVE_ROWSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
boulder_dash_tile_t *const destination = &boulder_dash_cave[next_row][next_column]; - Decision
*destination == BOULDER_DASH_STEELTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
*destination == BOULDER_DASH_BOULDERTrue- Execute
const int push_column = next_column + column_delta; - Decision
row_delta != 0 || push_column < 0 || push_column >= BOULDER_DASH_CAVE_COLUMNS || boulder_dash_cave[next_row][push_column] != BOULDER_DASH_EMPTYTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
boulder_dash_cave[next_row][push_column] = BOULDER_DASH_BOULDER; - Execute
*destination = BOULDER_DASH_EMPTY; - Decision
!hold_a_to_stayTrue- Execute
boulder_dash_player_column = next_column; - Execute
boulder_dash_player_row = next_row;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
*destination == BOULDER_DASH_EXITTrue- Decision
boulderDashExitOpen()True- Execute
boulder_dash_state = BOULDER_DASH_LEVEL_FLASH; - Execute
boulder_dash_level_flash_started_at = now_ms; - Execute
boulder_dash_level_flash_light = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
*destination == BOULDER_DASH_DIRTTrue- Execute
boulder_dash_score -= BOULDER_DASH_NEW_DIRT_PENALTY;
False- Decision
*destination == BOULDER_DASH_DIAMONDTrue- Execute
++boulder_dash_diamonds_collected; - Execute
boulder_dash_score += BOULDER_DASH_DIAMOND_POINTS;
FalseContinue after this decision.
- Execute
*destination = BOULDER_DASH_EMPTY; - Execute
boulder_dash_player_column = next_column; - Execute
boulder_dash_player_row = next_row;
Read original function source
static void tryMoveBoulderDashPlayer(int column_delta, int row_delta, bool hold_a_to_stay,
uint32_t now_ms)
{
if (boulder_dash_state != BOULDER_DASH_PLAYING) return;
const int next_column = boulder_dash_player_column + column_delta;
const int next_row = boulder_dash_player_row + row_delta;
if (next_column < 0 || next_column >= BOULDER_DASH_CAVE_COLUMNS ||
next_row < 0 || next_row >= BOULDER_DASH_CAVE_ROWS) return;
boulder_dash_tile_t *const destination = &boulder_dash_cave[next_row][next_column];
if (*destination == BOULDER_DASH_STEEL) return;
if (*destination == BOULDER_DASH_BOULDER) {
// Rocks can only be pushed sideways, and only into an already empty
// cell. Dirt, diamonds, exits, and vertical pushes remain blocked.
// Holding A makes this a careful push: the rock moves, but the player
// remains in place. Without A, the player follows into the old rock tile.
const int push_column = next_column + column_delta;
if (row_delta != 0 || push_column < 0 || push_column >= BOULDER_DASH_CAVE_COLUMNS ||
boulder_dash_cave[next_row][push_column] != BOULDER_DASH_EMPTY) return;
boulder_dash_cave[next_row][push_column] = BOULDER_DASH_BOULDER;
*destination = BOULDER_DASH_EMPTY;
if (!hold_a_to_stay) {
boulder_dash_player_column = next_column;
boulder_dash_player_row = next_row;
}
return;
}
if (*destination == BOULDER_DASH_EXIT) {
if (boulderDashExitOpen()) {
boulder_dash_state = BOULDER_DASH_LEVEL_FLASH;
boulder_dash_level_flash_started_at = now_ms;
boulder_dash_level_flash_light = true;
}
return;
}
// Dirt disappears as it is crossed; diamonds do too, while adding to the
// count needed to unlock the exit. Empty tiles cost nothing after their
// first digging pass.
if (*destination == BOULDER_DASH_DIRT) {
boulder_dash_score -= BOULDER_DASH_NEW_DIRT_PENALTY;
} else if (*destination == BOULDER_DASH_DIAMOND) {
++boulder_dash_diamonds_collected;
boulder_dash_score += BOULDER_DASH_DIAMOND_POINTS;
}
*destination = BOULDER_DASH_EMPTY;
boulder_dash_player_column = next_column;
boulder_dash_player_row = next_row;
}tryRollBoulder() · source line 426
static bool tryRollBoulder(int row, int column, int direction)
Calls into this documentation:
- Execute
const int side_column = column + direction; - Execute
const int diagonal_row = row + 1; - Decision
side_column < 0 || side_column >= BOULDER_DASH_CAVE_COLUMNSTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
boulder_dash_player_row == row && boulder_dash_player_column == side_columnTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
boulder_dash_cave[row][side_column] != BOULDER_DASH_EMPTYTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
boulder_dash_player_row == diagonal_row && boulder_dash_player_column == side_columnTrue- Execute
boulder_dash_cave[diagonal_row][side_column] = BOULDER_DASH_BOULDER; - Execute
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY; - Execute
boulderDashCrushPlayer(); - Exit this function
return true;
FalseContinue after this decision.
- Decision
boulder_dash_cave[diagonal_row][side_column] != BOULDER_DASH_EMPTYTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
boulder_dash_cave[diagonal_row][side_column] = BOULDER_DASH_BOULDER; - Execute
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY; - Execute
boulder_dash_roll_left_first = !boulder_dash_roll_left_first; - Exit this function
return true;
Read original function source
static bool tryRollBoulder(int row, int column, int direction)
{
const int side_column = column + direction;
const int diagonal_row = row + 1;
if (side_column < 0 || side_column >= BOULDER_DASH_CAVE_COLUMNS) return false;
// A player beside the boulder blocks its path. A player in the diagonal
// destination is struck by the rolling boulder just like a falling one.
if (boulder_dash_player_row == row && boulder_dash_player_column == side_column) return false;
if (boulder_dash_cave[row][side_column] != BOULDER_DASH_EMPTY) return false;
if (boulder_dash_player_row == diagonal_row &&
boulder_dash_player_column == side_column) {
boulder_dash_cave[diagonal_row][side_column] = BOULDER_DASH_BOULDER;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
boulderDashCrushPlayer();
return true;
}
if (boulder_dash_cave[diagonal_row][side_column] != BOULDER_DASH_EMPTY) return false;
boulder_dash_cave[diagonal_row][side_column] = BOULDER_DASH_BOULDER;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
boulder_dash_roll_left_first = !boulder_dash_roll_left_first;
return true;
}updateBoulderDashGravity() · source line 451
static void updateBoulderDashGravity(void)
Calls into this documentation:
- FOR loop
int row = BOULDER_DASH_CAVE_ROWS - 2; row >= 0; --rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
boulder_dash_cave[row][column] != BOULDER_DASH_BOULDERTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int below_row = row + 1; - Decision
boulder_dash_player_row == below_row && boulder_dash_player_column == columnTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const boulder_dash_tile_t below_tile = boulder_dash_cave[below_row][column]; - Decision
below_tile == BOULDER_DASH_EMPTYTrue- Execute
boulder_dash_cave[below_row][column] = BOULDER_DASH_BOULDER; - Execute
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY; - Next loop iteration
continue;
FalseContinue after this decision.
- Decision
below_tile != BOULDER_DASH_BOULDER && below_tile != BOULDER_DASH_DIAMONDTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int first_direction = boulder_dash_roll_left_first ? -1 : 1; - Decision
tryRollBoulder(row, column, first_direction) || tryRollBoulder(row, column, -first_direction)True- Decision
boulder_dash_state != BOULDER_DASH_PLAYINGTrue- Exit this function
return;
FalseContinue after this decision.
FalseContinue after this decision.
Read original function source
static void updateBoulderDashGravity(void)
{
// Bottom-up processing prevents a boulder from falling multiple cells in a
// single gravity tick after it enters a new empty cell.
for (int row = BOULDER_DASH_CAVE_ROWS - 2; row >= 0; --row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
if (boulder_dash_cave[row][column] != BOULDER_DASH_BOULDER) continue;
const int below_row = row + 1;
// A player directly underneath temporarily supports the boulder.
// This permits digging sideways beneath it; once the player moves
// clear, the empty cell below makes the boulder fall next tick.
// Diagonal rolling remains dangerous and is handled in tryRollBoulder.
if (boulder_dash_player_row == below_row &&
boulder_dash_player_column == column) continue;
const boulder_dash_tile_t below_tile = boulder_dash_cave[below_row][column];
if (below_tile == BOULDER_DASH_EMPTY) {
boulder_dash_cave[below_row][column] = BOULDER_DASH_BOULDER;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
continue;
}
// Only round boulders and diamonds provide a surface a boulder can
// roll from. Dirt and steel deliberately hold it still.
if (below_tile != BOULDER_DASH_BOULDER && below_tile != BOULDER_DASH_DIAMOND) {
continue;
}
const int first_direction = boulder_dash_roll_left_first ? -1 : 1;
if (tryRollBoulder(row, column, first_direction) ||
tryRollBoulder(row, column, -first_direction)) {
if (boulder_dash_state != BOULDER_DASH_PLAYING) return;
}
}
}
}startBoulderDash() · source line 487
void startBoulderDash(uint32_t now_ms)
Calls into this documentation:
- Execute
boulder_dash_running = true; - Execute
boulder_dash_level_index = 0U; - Execute
boulder_dash_lives = BOULDER_DASH_STARTING_LIVES; - Execute
resetBoulderDashCave(now_ms, true);
Read original function source
void startBoulderDash(uint32_t now_ms)
{
boulder_dash_running = true;
boulder_dash_level_index = 0U;
boulder_dash_lives = BOULDER_DASH_STARTING_LIVES;
resetBoulderDashCave(now_ms, true);
}stopBoulderDash() · source line 495
void stopBoulderDash(void)
- Execute
boulder_dash_running = false;
Read original function source
void stopBoulderDash(void)
{
boulder_dash_running = false;
}boulderDashAction() · source line 500
void boulderDashAction(bool reset_cave, uint32_t now_ms)
Calls into this documentation:
- Decision
boulder_dash_running && reset_cave && boulder_dash_state == BOULDER_DASH_CRUSHEDTrue- Execute
resetBoulderDashCave(now_ms, false);
False- Decision
boulder_dash_running && reset_cave && boulder_dash_state == BOULDER_DASH_COMPLETETrue- Execute
boulder_dash_level_index = (uint8_t)((boulder_dash_level_index + 1U) % BOULDER_DASH_LEVEL_COUNT); - Execute
resetBoulderDashCave(now_ms, false);
False- Decision
boulder_dash_running && reset_cave && boulder_dash_state == BOULDER_DASH_GAME_OVERTrue- Execute
boulder_dash_level_index = 0U; - Execute
boulder_dash_lives = BOULDER_DASH_STARTING_LIVES; - Execute
resetBoulderDashCave(now_ms, true);
FalseContinue after this decision.
Read original function source
void boulderDashAction(bool reset_cave, uint32_t now_ms)
{
// During normal play A is held as the careful-push modifier. A/K resets
// after death; after completion it advances through all ten levels. The
// success flash is always allowed to finish before either action occurs.
if (boulder_dash_running && reset_cave &&
boulder_dash_state == BOULDER_DASH_CRUSHED) {
resetBoulderDashCave(now_ms, false);
} else if (boulder_dash_running && reset_cave &&
boulder_dash_state == BOULDER_DASH_COMPLETE) {
boulder_dash_level_index =
(uint8_t)((boulder_dash_level_index + 1U) % BOULDER_DASH_LEVEL_COUNT);
resetBoulderDashCave(now_ms, false);
} else if (boulder_dash_running && reset_cave &&
boulder_dash_state == BOULDER_DASH_GAME_OVER) {
boulder_dash_level_index = 0U;
boulder_dash_lives = BOULDER_DASH_STARTING_LIVES;
resetBoulderDashCave(now_ms, true);
}
}updateBoulderDash() · source line 521
void updateBoulderDash(int x_offset, int y_offset, bool valid, bool left, bool right,
bool hold_a_to_stay, uint32_t now_ms)Calls into this documentation:
- Decision
!boulder_dash_runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
boulder_dash_state == BOULDER_DASH_LEVEL_FLASHTrue- Execute
const uint32_t flash_elapsed_ms = now_ms - boulder_dash_level_flash_started_at; - Execute
boulder_dash_level_flash_light = (flash_elapsed_ms / BOULDER_DASH_LEVEL_FLASH_INTERVAL_MS) % 2U == 0U; - Decision
flash_elapsed_ms >= BOULDER_DASH_LEVEL_FLASH_DURATION_MSTrue- Execute
boulder_dash_state = BOULDER_DASH_COMPLETE;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
boulder_dash_state != BOULDER_DASH_PLAYINGTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
int column_delta = 0; - Execute
int row_delta = 0; - Decision
left != rightTrue- Execute
column_delta = left ? -1 : 1;
False- Decision
valid && (x_offset >= BOULDER_DASH_JOYSTICK_THRESHOLD || x_offset <= -BOULDER_DASH_JOYSTICK_THRESHOLD || y_offset >= BOULDER_DASH_JOYSTICK_THRESHOLD || y_offset <= -BOULDER_DASH_JOYSTICK_THRESHOLD)True- Execute
const int horizontal_strength = x_offset < 0 ? -x_offset : x_offset; - Execute
const int vertical_strength = y_offset < 0 ? -y_offset : y_offset; - Decision
horizontal_strength >= vertical_strengthTrue- Execute
column_delta = x_offset < 0 ? -1 : 1;
False- Execute
row_delta = y_offset < 0 ? 1 : -1;
FalseContinue after this decision.
- Decision
(column_delta != 0 || row_delta != 0) && now_ms - boulder_dash_last_move_at >= BOULDER_DASH_MOVE_REPEAT_MSTrue- Execute
tryMoveBoulderDashPlayer(column_delta, row_delta, hold_a_to_stay, now_ms); - Execute
boulder_dash_last_move_at = now_ms;
FalseContinue after this decision.
- Decision
now_ms - boulder_dash_last_gravity_at >= BOULDER_DASH_GRAVITY_MSTrue- Execute
updateBoulderDashGravity(); - Execute
boulder_dash_last_gravity_at = now_ms;
FalseContinue after this decision.
Read original function source
void updateBoulderDash(int x_offset, int y_offset, bool valid, bool left, bool right,
bool hold_a_to_stay, uint32_t now_ms)
{
if (!boulder_dash_running) return;
if (boulder_dash_state == BOULDER_DASH_LEVEL_FLASH) {
const uint32_t flash_elapsed_ms = now_ms - boulder_dash_level_flash_started_at;
boulder_dash_level_flash_light =
(flash_elapsed_ms / BOULDER_DASH_LEVEL_FLASH_INTERVAL_MS) % 2U == 0U;
if (flash_elapsed_ms >= BOULDER_DASH_LEVEL_FLASH_DURATION_MS) {
boulder_dash_state = BOULDER_DASH_COMPLETE;
}
return;
}
if (boulder_dash_state != BOULDER_DASH_PLAYING) return;
int column_delta = 0;
int row_delta = 0;
if (left != right) {
column_delta = left ? -1 : 1;
} else if (valid &&
(x_offset >= BOULDER_DASH_JOYSTICK_THRESHOLD ||
x_offset <= -BOULDER_DASH_JOYSTICK_THRESHOLD ||
y_offset >= BOULDER_DASH_JOYSTICK_THRESHOLD ||
y_offset <= -BOULDER_DASH_JOYSTICK_THRESHOLD)) {
const int horizontal_strength = x_offset < 0 ? -x_offset : x_offset;
const int vertical_strength = y_offset < 0 ? -y_offset : y_offset;
if (horizontal_strength >= vertical_strength) {
column_delta = x_offset < 0 ? -1 : 1;
} else {
// This project's calibrated joystick reports negative Y when pushed
// physically down, while cave rows increase toward the bottom.
row_delta = y_offset < 0 ? 1 : -1;
}
}
if ((column_delta != 0 || row_delta != 0) &&
now_ms - boulder_dash_last_move_at >= BOULDER_DASH_MOVE_REPEAT_MS) {
tryMoveBoulderDashPlayer(column_delta, row_delta, hold_a_to_stay, now_ms);
boulder_dash_last_move_at = now_ms;
}
if (now_ms - boulder_dash_last_gravity_at >= BOULDER_DASH_GRAVITY_MS) {
updateBoulderDashGravity();
boulder_dash_last_gravity_at = now_ms;
}
}drawBoulderDash() · source line 567
void drawBoulderDash(uint16_t *frame)
Calls into this documentation:
- Decision
frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
char score_text[18]; - Execute
char diamond_text[18]; - Execute
char level_text[24]; - Decision
boulder_dash_state == BOULDER_DASH_LEVEL_FLASHTrue- Execute
clear(frame, boulder_dash_level_flash_light ? COLOR_WHITE : COLOR_DARKBLUE); - Exit this function
return;
FalseContinue after this decision.
- Decision
boulder_dash_state == BOULDER_DASH_COMPLETETrue- Execute
clear(frame, COLOR_DARKBLUE); - Decision
boulder_dash_level_index + 1U == BOULDER_DASH_LEVEL_COUNTTrue- Execute
draw_text_centred(frame, LCD_WIDTH / 2, 48, "GAME COMPLETE", 2, COLOR_LIGHTGREEN);
False- Execute
(void)snprintf(level_text, sizeof(level_text), "LEVEL %u COMPLETE", (unsigned)(boulder_dash_level_index + 1U)); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 48, level_text, 2, COLOR_LIGHTGREEN);
- Execute
(void)snprintf(score_text, sizeof(score_text), "SCORE %ld", (long)boulder_dash_score); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 82, score_text, 2, COLOR_WHITE); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 116, boulder_dash_level_index + 1U == BOULDER_DASH_LEVEL_COUNT ? "A/K LEVEL 1" : "A/K NEXT LEVEL", 1, COLOR_YELLOW); - Exit this function
return;
FalseContinue after this decision.
- Decision
boulder_dash_state == BOULDER_DASH_GAME_OVERTrue- Execute
clear(frame, COLOR_DARKBLUE); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 48, "GAME OVER", 2, COLOR_LIGHTCORAL); - Execute
(void)snprintf(score_text, sizeof(score_text), "SCORE %ld", (long)boulder_dash_score); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 82, score_text, 2, COLOR_WHITE); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 116, "A/K NEW GAME", 1, COLOR_YELLOW); - Exit this function
return;
FalseContinue after this decision.
- Execute
fill_rect(frame, 0, 0, LCD_WIDTH, BOULDER_DASH_STATUS_HEIGHT, COLOR_DARKBLUE); - Execute
(void)snprintf(level_text, sizeof(level_text), "BOULDER L%u", (unsigned)(boulder_dash_level_index + 1U)); - Execute
draw_text(frame, 8, 4, level_text, 2, COLOR_WHITE); - Decision
boulder_dash_state == BOULDER_DASH_CRUSHEDTrue- Execute
draw_text(frame, 210, 3, "CRUSHED", 1, COLOR_LIGHTCORAL); - Execute
(void)snprintf(level_text, sizeof(level_text), "A RESET L %u", (unsigned)boulder_dash_lives); - Execute
draw_text(frame, 210, 13, level_text, 1, COLOR_YELLOW);
False- Execute
(void)snprintf(score_text, sizeof(score_text), "S %ld", (long)boulder_dash_score); - Execute
(void)snprintf(diamond_text, sizeof(diamond_text), "D %u/%u", (unsigned)boulder_dash_diamonds_collected, (unsigned)boulder_dash_diamonds_required); - Execute
draw_text(frame, 210, 3, score_text, 1, COLOR_CYAN); - Execute
draw_text(frame, 260, 3, diamond_text, 1, COLOR_CYAN); - Execute
(void)snprintf(level_text, sizeof(level_text), "%s L %u", boulderDashExitOpen() ? "EXIT OPEN" : "EXIT LOCK", (unsigned)boulder_dash_lives); - Execute
draw_text(frame, 210, 13, level_text, 1, boulderDashExitOpen() ? COLOR_LIGHTGREEN : COLOR_LIGHTGREY);
- FOR loop
int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
drawBoulderDashTile(frame, column * BOULDER_DASH_TILE_SIZE, BOULDER_DASH_STATUS_HEIGHT + row * BOULDER_DASH_TILE_SIZE, boulder_dash_cave[row][column], boulderDashExitOpen());
- Decision
boulder_dash_state == BOULDER_DASH_PLAYINGTrue- Execute
const int player_x = boulder_dash_player_column * BOULDER_DASH_TILE_SIZE; - Execute
const int player_y = BOULDER_DASH_STATUS_HEIGHT + boulder_dash_player_row * BOULDER_DASH_TILE_SIZE; - Execute
drawBoulderDashTile(frame, player_x, player_y, BOULDER_DASH_PLAYER, boulderDashExitOpen());
FalseContinue after this decision.
Read original function source
void drawBoulderDash(uint16_t *frame)
{
if (frame == NULL) return;
char score_text[18];
char diamond_text[18];
char level_text[24];
if (boulder_dash_state == BOULDER_DASH_LEVEL_FLASH) {
clear(frame, boulder_dash_level_flash_light ? COLOR_WHITE : COLOR_DARKBLUE);
return;
}
if (boulder_dash_state == BOULDER_DASH_COMPLETE) {
clear(frame, COLOR_DARKBLUE);
if (boulder_dash_level_index + 1U == BOULDER_DASH_LEVEL_COUNT) {
draw_text_centred(frame, LCD_WIDTH / 2, 48, "GAME COMPLETE", 2, COLOR_LIGHTGREEN);
} else {
(void)snprintf(level_text, sizeof(level_text), "LEVEL %u COMPLETE",
(unsigned)(boulder_dash_level_index + 1U));
draw_text_centred(frame, LCD_WIDTH / 2, 48, level_text, 2, COLOR_LIGHTGREEN);
}
(void)snprintf(score_text, sizeof(score_text), "SCORE %ld", (long)boulder_dash_score);
draw_text_centred(frame, LCD_WIDTH / 2, 82, score_text, 2, COLOR_WHITE);
draw_text_centred(frame, LCD_WIDTH / 2, 116,
boulder_dash_level_index + 1U == BOULDER_DASH_LEVEL_COUNT
? "A/K LEVEL 1"
: "A/K NEXT LEVEL",
1, COLOR_YELLOW);
return;
}
if (boulder_dash_state == BOULDER_DASH_GAME_OVER) {
clear(frame, COLOR_DARKBLUE);
draw_text_centred(frame, LCD_WIDTH / 2, 48, "GAME OVER", 2, COLOR_LIGHTCORAL);
(void)snprintf(score_text, sizeof(score_text), "SCORE %ld", (long)boulder_dash_score);
draw_text_centred(frame, LCD_WIDTH / 2, 82, score_text, 2, COLOR_WHITE);
draw_text_centred(frame, LCD_WIDTH / 2, 116, "A/K NEW GAME", 1, COLOR_YELLOW);
return;
}
fill_rect(frame, 0, 0, LCD_WIDTH, BOULDER_DASH_STATUS_HEIGHT, COLOR_DARKBLUE);
(void)snprintf(level_text, sizeof(level_text), "BOULDER L%u",
(unsigned)(boulder_dash_level_index + 1U));
draw_text(frame, 8, 4, level_text, 2, COLOR_WHITE);
if (boulder_dash_state == BOULDER_DASH_CRUSHED) {
draw_text(frame, 210, 3, "CRUSHED", 1, COLOR_LIGHTCORAL);
(void)snprintf(level_text, sizeof(level_text), "A RESET L %u",
(unsigned)boulder_dash_lives);
draw_text(frame, 210, 13, level_text, 1, COLOR_YELLOW);
} else {
(void)snprintf(score_text, sizeof(score_text), "S %ld", (long)boulder_dash_score);
(void)snprintf(diamond_text, sizeof(diamond_text), "D %u/%u",
(unsigned)boulder_dash_diamonds_collected,
(unsigned)boulder_dash_diamonds_required);
draw_text(frame, 210, 3, score_text, 1, COLOR_CYAN);
draw_text(frame, 260, 3, diamond_text, 1, COLOR_CYAN);
(void)snprintf(level_text, sizeof(level_text), "%s L %u",
boulderDashExitOpen() ? "EXIT OPEN" : "EXIT LOCK",
(unsigned)boulder_dash_lives);
draw_text(frame, 210, 13, level_text,
1, boulderDashExitOpen() ? COLOR_LIGHTGREEN : COLOR_LIGHTGREY);
}
for (int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
drawBoulderDashTile(frame, column * BOULDER_DASH_TILE_SIZE,
BOULDER_DASH_STATUS_HEIGHT + row * BOULDER_DASH_TILE_SIZE,
boulder_dash_cave[row][column], boulderDashExitOpen());
}
}
if (boulder_dash_state == BOULDER_DASH_PLAYING) {
const int player_x = boulder_dash_player_column * BOULDER_DASH_TILE_SIZE;
const int player_y = BOULDER_DASH_STATUS_HEIGHT +
boulder_dash_player_row * BOULDER_DASH_TILE_SIZE;
drawBoulderDashTile(frame, player_x, player_y, BOULDER_DASH_PLAYER,
boulderDashExitOpen());
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "boulder_dash.h"
#include "game_colors.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "screen_transition.h"
#include "esp_random.h"
#include <stdio.h>
#include <string.h>
// -----------------------------------------------------------------------------
// Boulder Dash frame: a 20 x 9 cave of native 16 x 16 cells fills the 320 x 144
// playfield. The remaining 26 display rows form the fixed status strip.
//
// These one-bit masks are deliberately temporary built-in sprites. Keeping them
// together makes the later artwork/resource layer a direct replacement, without
// changing the cave map or the game's screen integration.
// -----------------------------------------------------------------------------
#define BOULDER_DASH_TILE_SIZE 16
#define BOULDER_DASH_CAVE_COLUMNS 20
#define BOULDER_DASH_CAVE_ROWS 9
#define BOULDER_DASH_STATUS_HEIGHT 26
#define BOULDER_DASH_TILE_MASK_BYTES 32
#define BOULDER_DASH_LEVEL_COUNT 10U
#define BOULDER_DASH_LEVEL_PLACEMENT_ATTEMPTS 512U
// Visual tuning controls for the first playable cave.
// Keep gravity slower than movement so a player can see and react to each fall.
#define BOULDER_DASH_MOVE_REPEAT_MS 140U
#define BOULDER_DASH_GRAVITY_MS 320U
#define BOULDER_DASH_JOYSTICK_THRESHOLD 600
// Completion flash tuning: total length must remain a multiple of the interval
// so the sequence ends on the same dark-blue colour on every run.
#define BOULDER_DASH_LEVEL_FLASH_DURATION_MS 720U
#define BOULDER_DASH_LEVEL_FLASH_INTERVAL_MS 90U
// Level-start effects reuse the global transition renderer. Each new cave randomly
// selects one; input and gravity remain paused until its captured frame is complete.
#define BOULDER_DASH_LEVEL_REVEAL_MS 700U
#define BOULDER_DASH_LEVEL_REVEAL_PERCENT 100U
#define BOULDER_DASH_LEVEL_SLICE_COUNT 12U
#define BOULDER_DASH_LEVEL_SLICE_MS 650U
// Scoring rules: diamonds reward exploration; each previously untouched dirt
// tile costs one point exactly once, because digging turns it into empty space.
#define BOULDER_DASH_DIAMOND_POINTS 10
#define BOULDER_DASH_NEW_DIRT_PENALTY 1
#define BOULDER_DASH_STARTING_LIVES 3U
typedef enum {
BOULDER_DASH_EMPTY,
BOULDER_DASH_DIRT,
BOULDER_DASH_STEEL,
BOULDER_DASH_BOULDER,
BOULDER_DASH_DIAMOND,
BOULDER_DASH_PLAYER,
BOULDER_DASH_EXIT,
} boulder_dash_tile_t;
// Level 1 is hand-authored. Levels 2-10 use their fixed seeds to produce the
// same cave on every replay, while these values tune their gradual difficulty.
typedef struct {
uint32_t seed;
uint8_t diamonds;
uint8_t boulders;
uint8_t steel_blocks;
} boulder_dash_level_definition_t;
static const boulder_dash_level_definition_t boulder_dash_levels[BOULDER_DASH_LEVEL_COUNT] = {
{0U, 6U, 2U, 0U},
{0x4c455632U, 8U, 3U, 12U},
{0x4c455633U, 12U, 5U, 21U},
{0x4c455634U, 14U, 4U, 26U},
{0x4c455635U, 16U, 5U, 31U},
{0x4c455636U, 18U, 6U, 36U},
{0x4c455637U, 20U, 7U, 41U},
{0x4c455638U, 22U, 8U, 46U},
{0x4c455639U, 24U, 9U, 50U},
{0x4c455a30U, 26U, 10U, 54U},
};
// Each sprite uses 16 rows of two bytes, with the most-significant bit at left.
static const uint8_t boulder_dash_dirt_mask[BOULDER_DASH_TILE_MASK_BYTES] = {
0x00, 0x00, 0x10, 0x80, 0x00, 0x04, 0x42, 0x00, 0x00, 0x20, 0x08, 0x00,
0x80, 0x10, 0x01, 0x00, 0x20, 0x04, 0x04, 0x40, 0x00, 0x02, 0x41, 0x00,
0x08, 0x00, 0x00, 0x20, 0x82, 0x00, 0x00, 0x00,
};
static const uint8_t boulder_dash_steel_mask[BOULDER_DASH_TILE_MASK_BYTES] = {
0xff, 0xff, 0x81, 0x81, 0x99, 0x99, 0x81, 0x81, 0x99, 0x99, 0x81, 0x81,
0x99, 0x99, 0x81, 0x81, 0x81, 0x81, 0x99, 0x99, 0x81, 0x81, 0x99, 0x99,
0x81, 0x81, 0x99, 0x99, 0x81, 0x81, 0xff, 0xff,
};
static const uint8_t boulder_dash_boulder_mask[BOULDER_DASH_TILE_MASK_BYTES] = {
0x03, 0xc0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe,
0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x03, 0xc0,
};
static const uint8_t boulder_dash_diamond_mask[BOULDER_DASH_TILE_MASK_BYTES] = {
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0,
0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0,
0x03, 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t boulder_dash_player_mask[BOULDER_DASH_TILE_MASK_BYTES] = {
0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, 0x1b, 0xd8, 0x1f, 0xf8, 0x0f, 0xf0,
0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x2d, 0xb4, 0x0c, 0x30,
0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e, 0x60, 0x06,
};
static const uint8_t boulder_dash_exit_mask[BOULDER_DASH_TILE_MASK_BYTES] = {
0x3f, 0xfc, 0x7f, 0xfe, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06,
0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06,
0x60, 0x06, 0x60, 0x06, 0x7f, 0xfe, 0x3f, 0xfc,
};
// Level 1 is deliberately a small teaching cave rather than a random tile test:
// dig from the upper-left start, collect all six diamonds, make a safe sideways
// boulder push, watch the supported boulder roll, then reach the lower-right exit.
// The later resource/level layer can replace this map without changing the rules.
static const boulder_dash_tile_t boulder_dash_level_one_cave[BOULDER_DASH_CAVE_ROWS]
[BOULDER_DASH_CAVE_COLUMNS] = {
{BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_PLAYER, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_EMPTY, BOULDER_DASH_DIAMOND, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_BOULDER, BOULDER_DASH_EMPTY, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIAMOND, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIAMOND, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_EMPTY, BOULDER_DASH_BOULDER, BOULDER_DASH_EMPTY, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_EMPTY, BOULDER_DASH_DIAMOND, BOULDER_DASH_EMPTY, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIAMOND, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_STEEL, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIRT, BOULDER_DASH_DIAMOND, BOULDER_DASH_DIRT, BOULDER_DASH_EXIT, BOULDER_DASH_STEEL},
{BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL, BOULDER_DASH_STEEL},
};
typedef enum {
BOULDER_DASH_PLAYING,
BOULDER_DASH_LEVEL_FLASH,
BOULDER_DASH_CRUSHED,
BOULDER_DASH_COMPLETE,
BOULDER_DASH_GAME_OVER,
} boulder_dash_state_t;
static boulder_dash_tile_t boulder_dash_cave[BOULDER_DASH_CAVE_ROWS]
[BOULDER_DASH_CAVE_COLUMNS];
static boulder_dash_state_t boulder_dash_state;
static int boulder_dash_player_column;
static int boulder_dash_player_row;
static uint8_t boulder_dash_diamonds_collected;
static uint8_t boulder_dash_diamonds_required;
static int32_t boulder_dash_score;
// Score and lives belong to the whole ten-level run, not one cave attempt.
static uint8_t boulder_dash_lives;
static uint32_t boulder_dash_last_move_at;
static uint32_t boulder_dash_last_gravity_at;
static uint32_t boulder_dash_now_ms;
static uint32_t boulder_dash_level_flash_started_at;
static bool boulder_dash_level_flash_light;
// Alternate the preferred roll side after each successful roll, preventing a
// symmetrical cave from always favouring its left-hand route.
static bool boulder_dash_roll_left_first;
static bool boulder_dash_running;
static uint8_t boulder_dash_level_index;
static uint32_t boulder_dash_level_random_state;
// One reusable PSRAM-backed transition owns the selected level-entry effect.
static screen_transition_t boulder_dash_level_transition;
// A reset builds the new cave first; drawBoulderDash captures that completed frame once.
static bool boulder_dash_level_transition_pending;
// True selects the shuffled 8 x 8 reveal; false selects alternating horizontal slices.
static bool boulder_dash_level_reveal_selected;
static uint32_t boulderDashNextLevelRandom(void)
{
// Tiny deterministic PRNG: its state begins from the level's fixed seed,
// so the generated layouts do not vary between separate plays.
boulder_dash_level_random_state = boulder_dash_level_random_state * 1664525U + 1013904223U;
return boulder_dash_level_random_state;
}
static bool boulderDashIsReservedLevelCell(int row, int column)
{
// Preserve an open, safe two-by-two area around the upper-left spawn and
// lower-right exit, independent of generated barrier/item placement.
return (row <= 2 && column <= 2) ||
(row >= BOULDER_DASH_CAVE_ROWS - 3 &&
column >= BOULDER_DASH_CAVE_COLUMNS - 3);
}
static bool boulderDashWalkableCaveIsConnected(void)
{
// Each proposed steel block is accepted only if every remaining non-steel
// tile can still be reached from the spawn. This prevents a generated wall
// pattern from sealing off part of the cave before gems are added.
bool visited[BOULDER_DASH_CAVE_ROWS][BOULDER_DASH_CAVE_COLUMNS] = {{false}};
uint8_t pending_cells[BOULDER_DASH_CAVE_ROWS * BOULDER_DASH_CAVE_COLUMNS];
uint16_t read_index = 0U;
uint16_t write_index = 0U;
pending_cells[write_index++] = BOULDER_DASH_CAVE_COLUMNS + 1U;
visited[1][1] = true;
while (read_index < write_index) {
const uint8_t cell = pending_cells[read_index++];
const int row = cell / BOULDER_DASH_CAVE_COLUMNS;
const int column = cell % BOULDER_DASH_CAVE_COLUMNS;
static const int row_offsets[4] = {-1, 1, 0, 0};
static const int column_offsets[4] = {0, 0, -1, 1};
for (int direction = 0; direction < 4; ++direction) {
const int next_row = row + row_offsets[direction];
const int next_column = column + column_offsets[direction];
if (next_row < 0 || next_row >= BOULDER_DASH_CAVE_ROWS ||
next_column < 0 || next_column >= BOULDER_DASH_CAVE_COLUMNS ||
visited[next_row][next_column] ||
boulder_dash_cave[next_row][next_column] == BOULDER_DASH_STEEL) {
continue;
}
visited[next_row][next_column] = true;
pending_cells[write_index++] =
(uint8_t)(next_row * BOULDER_DASH_CAVE_COLUMNS + next_column);
}
}
for (int row = 1; row < BOULDER_DASH_CAVE_ROWS - 1; ++row) {
for (int column = 1; column < BOULDER_DASH_CAVE_COLUMNS - 1; ++column) {
if (boulder_dash_cave[row][column] != BOULDER_DASH_STEEL &&
!visited[row][column]) {
return false;
}
}
}
return true;
}
static void placeProceduralLevelTiles(boulder_dash_tile_t tile, uint8_t count)
{
uint8_t placed = 0U;
uint16_t attempts = 0U;
while (placed < count && attempts++ < BOULDER_DASH_LEVEL_PLACEMENT_ATTEMPTS) {
const int row = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_ROWS - 2));
const int column = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_COLUMNS - 2));
if (boulderDashIsReservedLevelCell(row, column) ||
boulder_dash_cave[row][column] != BOULDER_DASH_DIRT) {
continue;
}
// New boulders begin on dirt rather than open space, so they do not
// drop the instant the level begins.
if (tile == BOULDER_DASH_BOULDER &&
boulder_dash_cave[row + 1][column] != BOULDER_DASH_DIRT) {
continue;
}
boulder_dash_cave[row][column] = tile;
++placed;
}
}
static void buildProceduralBoulderDashLevel(const boulder_dash_level_definition_t *level)
{
boulder_dash_level_random_state = level->seed;
for (int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
boulder_dash_cave[row][column] =
row == 0 || row == BOULDER_DASH_CAVE_ROWS - 1 ||
column == 0 || column == BOULDER_DASH_CAVE_COLUMNS - 1
? BOULDER_DASH_STEEL
: BOULDER_DASH_DIRT;
}
}
boulder_dash_cave[1][1] = BOULDER_DASH_PLAYER;
boulder_dash_cave[1][2] = BOULDER_DASH_EMPTY;
boulder_dash_cave[2][1] = BOULDER_DASH_EMPTY;
boulder_dash_cave[2][2] = BOULDER_DASH_EMPTY;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 2][BOULDER_DASH_CAVE_COLUMNS - 2] =
BOULDER_DASH_EXIT;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 2][BOULDER_DASH_CAVE_COLUMNS - 3] =
BOULDER_DASH_EMPTY;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 3][BOULDER_DASH_CAVE_COLUMNS - 2] =
BOULDER_DASH_EMPTY;
boulder_dash_cave[BOULDER_DASH_CAVE_ROWS - 3][BOULDER_DASH_CAVE_COLUMNS - 3] =
BOULDER_DASH_EMPTY;
uint8_t steel_placed = 0U;
uint16_t attempts = 0U;
while (steel_placed < level->steel_blocks &&
attempts++ < BOULDER_DASH_LEVEL_PLACEMENT_ATTEMPTS) {
const int row = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_ROWS - 2));
const int column = 1 + (int)(boulderDashNextLevelRandom() %
(BOULDER_DASH_CAVE_COLUMNS - 2));
if (boulderDashIsReservedLevelCell(row, column) ||
boulder_dash_cave[row][column] != BOULDER_DASH_DIRT) {
continue;
}
boulder_dash_cave[row][column] = BOULDER_DASH_STEEL;
if (boulderDashWalkableCaveIsConnected()) {
++steel_placed;
} else {
boulder_dash_cave[row][column] = BOULDER_DASH_DIRT;
}
}
placeProceduralLevelTiles(BOULDER_DASH_DIAMOND, level->diamonds);
placeProceduralLevelTiles(BOULDER_DASH_BOULDER, level->boulders);
}
static void resetBoulderDashCave(uint32_t now_ms, bool reset_run_score)
{
const boulder_dash_level_definition_t *const level =
&boulder_dash_levels[boulder_dash_level_index];
if (boulder_dash_level_index == 0U) {
memcpy(boulder_dash_cave, boulder_dash_level_one_cave, sizeof(boulder_dash_cave));
} else {
buildProceduralBoulderDashLevel(level);
}
boulder_dash_diamonds_collected = 0U;
boulder_dash_diamonds_required = 0U;
if (reset_run_score) boulder_dash_score = 0;
boulder_dash_roll_left_first = true;
// The player is stored as coordinates rather than a map tile so the tile
// beneath them remains available for boulders and other future interactions.
for (int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
if (boulder_dash_cave[row][column] == BOULDER_DASH_PLAYER) {
boulder_dash_player_column = column;
boulder_dash_player_row = row;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
} else if (boulder_dash_cave[row][column] == BOULDER_DASH_DIAMOND) {
++boulder_dash_diamonds_required;
}
}
}
boulder_dash_state = BOULDER_DASH_PLAYING;
boulder_dash_now_ms = now_ms;
boulder_dash_last_move_at = now_ms;
boulder_dash_last_gravity_at = now_ms;
// Choose independently for every initial cave, retry, next level, and new game.
boulder_dash_level_reveal_selected = (esp_random() & 1U) == 0U;
boulder_dash_level_transition_pending = true;
}
static void boulderDashCrushPlayer(void)
{
// A loss always consumes one of the run's lives. The level remains ready
// for an A/K restart while a life remains; the third loss ends the run.
if (boulder_dash_lives > 0U) --boulder_dash_lives;
boulder_dash_state = boulder_dash_lives == 0U
? BOULDER_DASH_GAME_OVER
: BOULDER_DASH_CRUSHED;
}
static void drawBoulderDashTile(uint16_t *frame, int x, int y, boulder_dash_tile_t tile,
bool exit_open)
{
switch (tile) {
case BOULDER_DASH_EMPTY:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK);
break;
case BOULDER_DASH_DIRT:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_SADDLEBROWN);
draw_bitmap_scaled(frame, x, y, boulder_dash_dirt_mask, 16, 16, 16, 16, COLOR_PERU);
break;
case BOULDER_DASH_STEEL:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_DARKSLATEGRAY);
draw_bitmap_scaled(frame, x, y, boulder_dash_steel_mask, 16, 16, 16, 16, COLOR_LIGHTGREY);
break;
case BOULDER_DASH_BOULDER:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK);
draw_bitmap_scaled(frame, x, y, boulder_dash_boulder_mask, 16, 16, 16, 16, COLOR_SLATEGRAY);
break;
case BOULDER_DASH_DIAMOND:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_DARKBLUE);
draw_bitmap_scaled(frame, x, y, boulder_dash_diamond_mask, 16, 16, 16, 16, COLOR_CYAN);
break;
case BOULDER_DASH_PLAYER:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE, COLOR_BLACK);
draw_bitmap_scaled(frame, x, y, boulder_dash_player_mask, 16, 16, 16, 16, COLOR_ORANGE);
break;
case BOULDER_DASH_EXIT:
fill_rect(frame, x, y, BOULDER_DASH_TILE_SIZE, BOULDER_DASH_TILE_SIZE,
exit_open ? COLOR_DARKGREEN : COLOR_DARKRED);
draw_bitmap_scaled(frame, x, y, boulder_dash_exit_mask, 16, 16, 16, 16,
exit_open ? COLOR_YELLOW : COLOR_LIGHTGREY);
break;
}
}
static bool boulderDashExitOpen(void)
{
return boulder_dash_diamonds_collected >= boulder_dash_diamonds_required;
}
static void tryMoveBoulderDashPlayer(int column_delta, int row_delta, bool hold_a_to_stay,
uint32_t now_ms)
{
if (boulder_dash_state != BOULDER_DASH_PLAYING) return;
const int next_column = boulder_dash_player_column + column_delta;
const int next_row = boulder_dash_player_row + row_delta;
if (next_column < 0 || next_column >= BOULDER_DASH_CAVE_COLUMNS ||
next_row < 0 || next_row >= BOULDER_DASH_CAVE_ROWS) return;
boulder_dash_tile_t *const destination = &boulder_dash_cave[next_row][next_column];
if (*destination == BOULDER_DASH_STEEL) return;
if (*destination == BOULDER_DASH_BOULDER) {
// Rocks can only be pushed sideways, and only into an already empty
// cell. Dirt, diamonds, exits, and vertical pushes remain blocked.
// Holding A makes this a careful push: the rock moves, but the player
// remains in place. Without A, the player follows into the old rock tile.
const int push_column = next_column + column_delta;
if (row_delta != 0 || push_column < 0 || push_column >= BOULDER_DASH_CAVE_COLUMNS ||
boulder_dash_cave[next_row][push_column] != BOULDER_DASH_EMPTY) return;
boulder_dash_cave[next_row][push_column] = BOULDER_DASH_BOULDER;
*destination = BOULDER_DASH_EMPTY;
if (!hold_a_to_stay) {
boulder_dash_player_column = next_column;
boulder_dash_player_row = next_row;
}
return;
}
if (*destination == BOULDER_DASH_EXIT) {
if (boulderDashExitOpen()) {
boulder_dash_state = BOULDER_DASH_LEVEL_FLASH;
boulder_dash_level_flash_started_at = now_ms;
boulder_dash_level_flash_light = true;
}
return;
}
// Dirt disappears as it is crossed; diamonds do too, while adding to the
// count needed to unlock the exit. Empty tiles cost nothing after their
// first digging pass.
if (*destination == BOULDER_DASH_DIRT) {
boulder_dash_score -= BOULDER_DASH_NEW_DIRT_PENALTY;
} else if (*destination == BOULDER_DASH_DIAMOND) {
++boulder_dash_diamonds_collected;
boulder_dash_score += BOULDER_DASH_DIAMOND_POINTS;
}
*destination = BOULDER_DASH_EMPTY;
boulder_dash_player_column = next_column;
boulder_dash_player_row = next_row;
}
static bool tryRollBoulder(int row, int column, int direction)
{
const int side_column = column + direction;
const int diagonal_row = row + 1;
if (side_column < 0 || side_column >= BOULDER_DASH_CAVE_COLUMNS) return false;
// A player beside the boulder blocks its path. A player in the diagonal
// destination is struck by the rolling boulder just like a falling one.
if (boulder_dash_player_row == row && boulder_dash_player_column == side_column) return false;
if (boulder_dash_cave[row][side_column] != BOULDER_DASH_EMPTY) return false;
if (boulder_dash_player_row == diagonal_row &&
boulder_dash_player_column == side_column) {
boulder_dash_cave[diagonal_row][side_column] = BOULDER_DASH_BOULDER;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
boulderDashCrushPlayer();
return true;
}
if (boulder_dash_cave[diagonal_row][side_column] != BOULDER_DASH_EMPTY) return false;
boulder_dash_cave[diagonal_row][side_column] = BOULDER_DASH_BOULDER;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
boulder_dash_roll_left_first = !boulder_dash_roll_left_first;
return true;
}
static void updateBoulderDashGravity(void)
{
// Bottom-up processing prevents a boulder from falling multiple cells in a
// single gravity tick after it enters a new empty cell.
for (int row = BOULDER_DASH_CAVE_ROWS - 2; row >= 0; --row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
if (boulder_dash_cave[row][column] != BOULDER_DASH_BOULDER) continue;
const int below_row = row + 1;
// A player directly underneath temporarily supports the boulder.
// This permits digging sideways beneath it; once the player moves
// clear, the empty cell below makes the boulder fall next tick.
// Diagonal rolling remains dangerous and is handled in tryRollBoulder.
if (boulder_dash_player_row == below_row &&
boulder_dash_player_column == column) continue;
const boulder_dash_tile_t below_tile = boulder_dash_cave[below_row][column];
if (below_tile == BOULDER_DASH_EMPTY) {
boulder_dash_cave[below_row][column] = BOULDER_DASH_BOULDER;
boulder_dash_cave[row][column] = BOULDER_DASH_EMPTY;
continue;
}
// Only round boulders and diamonds provide a surface a boulder can
// roll from. Dirt and steel deliberately hold it still.
if (below_tile != BOULDER_DASH_BOULDER && below_tile != BOULDER_DASH_DIAMOND) {
continue;
}
const int first_direction = boulder_dash_roll_left_first ? -1 : 1;
if (tryRollBoulder(row, column, first_direction) ||
tryRollBoulder(row, column, -first_direction)) {
if (boulder_dash_state != BOULDER_DASH_PLAYING) return;
}
}
}
}
void startBoulderDash(uint32_t now_ms)
{
// A stopped game normally released this already; keep a restarted module leak-free too.
screenTransitionDeinitialize(&boulder_dash_level_transition);
boulder_dash_running = true;
boulder_dash_level_index = 0U;
boulder_dash_lives = BOULDER_DASH_STARTING_LIVES;
resetBoulderDashCave(now_ms, true);
}
void stopBoulderDash(void)
{
// The shared effects retain PSRAM snapshots only while Boulder Dash is open.
screenTransitionDeinitialize(&boulder_dash_level_transition);
boulder_dash_level_transition_pending = false;
boulder_dash_running = false;
}
void boulderDashAction(bool reset_cave, uint32_t now_ms)
{
// During normal play A is held as the careful-push modifier. A/K resets
// after death; after completion it advances through all ten levels. The
// success flash is always allowed to finish before either action occurs.
if (boulder_dash_running && reset_cave &&
boulder_dash_state == BOULDER_DASH_CRUSHED) {
resetBoulderDashCave(now_ms, false);
} else if (boulder_dash_running && reset_cave &&
boulder_dash_state == BOULDER_DASH_COMPLETE) {
boulder_dash_level_index =
(uint8_t)((boulder_dash_level_index + 1U) % BOULDER_DASH_LEVEL_COUNT);
resetBoulderDashCave(now_ms, false);
} else if (boulder_dash_running && reset_cave &&
boulder_dash_state == BOULDER_DASH_GAME_OVER) {
boulder_dash_level_index = 0U;
boulder_dash_lives = BOULDER_DASH_STARTING_LIVES;
resetBoulderDashCave(now_ms, true);
}
}
void updateBoulderDash(int x_offset, int y_offset, bool valid, bool left, bool right,
bool hold_a_to_stay, uint32_t now_ms)
{
if (!boulder_dash_running) return;
boulder_dash_now_ms = now_ms;
// A level must remain visually and physically frozen until its entry effect finishes.
if (screenRevealActive(&boulder_dash_level_transition)) {
if (screenRevealComplete(&boulder_dash_level_transition)) {
screenRevealReset(&boulder_dash_level_transition);
}
return;
}
if (screenSliceActive(&boulder_dash_level_transition)) {
if (screenSliceComplete(&boulder_dash_level_transition)) {
screenSliceReset(&boulder_dash_level_transition);
}
return;
}
if (boulder_dash_level_transition_pending) return;
if (boulder_dash_state == BOULDER_DASH_LEVEL_FLASH) {
const uint32_t flash_elapsed_ms = now_ms - boulder_dash_level_flash_started_at;
boulder_dash_level_flash_light =
(flash_elapsed_ms / BOULDER_DASH_LEVEL_FLASH_INTERVAL_MS) % 2U == 0U;
if (flash_elapsed_ms >= BOULDER_DASH_LEVEL_FLASH_DURATION_MS) {
boulder_dash_state = BOULDER_DASH_COMPLETE;
}
return;
}
if (boulder_dash_state != BOULDER_DASH_PLAYING) return;
int column_delta = 0;
int row_delta = 0;
if (left != right) {
column_delta = left ? -1 : 1;
} else if (valid &&
(x_offset >= BOULDER_DASH_JOYSTICK_THRESHOLD ||
x_offset <= -BOULDER_DASH_JOYSTICK_THRESHOLD ||
y_offset >= BOULDER_DASH_JOYSTICK_THRESHOLD ||
y_offset <= -BOULDER_DASH_JOYSTICK_THRESHOLD)) {
const int horizontal_strength = x_offset < 0 ? -x_offset : x_offset;
const int vertical_strength = y_offset < 0 ? -y_offset : y_offset;
if (horizontal_strength >= vertical_strength) {
column_delta = x_offset < 0 ? -1 : 1;
} else {
// This project's calibrated joystick reports negative Y when pushed
// physically down, while cave rows increase toward the bottom.
row_delta = y_offset < 0 ? 1 : -1;
}
}
if ((column_delta != 0 || row_delta != 0) &&
now_ms - boulder_dash_last_move_at >= BOULDER_DASH_MOVE_REPEAT_MS) {
tryMoveBoulderDashPlayer(column_delta, row_delta, hold_a_to_stay, now_ms);
boulder_dash_last_move_at = now_ms;
}
if (now_ms - boulder_dash_last_gravity_at >= BOULDER_DASH_GRAVITY_MS) {
updateBoulderDashGravity();
boulder_dash_last_gravity_at = now_ms;
}
}
void drawBoulderDash(uint16_t *frame)
{
if (frame == NULL) return;
// Once started, the transition redraws its captured cave instead of live gameplay.
if (screenRevealActive(&boulder_dash_level_transition)) {
screenRevealDraw(&boulder_dash_level_transition, frame, boulder_dash_now_ms);
return;
}
if (screenSliceActive(&boulder_dash_level_transition)) {
screenSliceDraw(&boulder_dash_level_transition, frame, boulder_dash_now_ms);
return;
}
char score_text[18];
char diamond_text[18];
char level_text[24];
if (boulder_dash_state == BOULDER_DASH_LEVEL_FLASH) {
clear(frame, boulder_dash_level_flash_light ? COLOR_WHITE : COLOR_DARKBLUE);
return;
}
if (boulder_dash_state == BOULDER_DASH_COMPLETE) {
clear(frame, COLOR_DARKBLUE);
if (boulder_dash_level_index + 1U == BOULDER_DASH_LEVEL_COUNT) {
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 48,
"GAME COMPLETE", COLOR_LIGHTGREEN, 2);
} else {
(void)snprintf(level_text, sizeof(level_text), "LEVEL %u COMPLETE",
(unsigned)(boulder_dash_level_index + 1U));
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 48,
level_text, COLOR_LIGHTGREEN, 2);
}
(void)snprintf(score_text, sizeof(score_text), "SCORE %ld", (long)boulder_dash_score);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 82,
score_text, COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 116,
boulder_dash_level_index + 1U == BOULDER_DASH_LEVEL_COUNT
? "A/K LEVEL 1"
: "A/K NEXT LEVEL",
COLOR_YELLOW, 1);
return;
}
if (boulder_dash_state == BOULDER_DASH_GAME_OVER) {
clear(frame, COLOR_DARKBLUE);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 48,
"GAME OVER", COLOR_LIGHTCORAL, 2);
(void)snprintf(score_text, sizeof(score_text), "SCORE %ld", (long)boulder_dash_score);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 82,
score_text, COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 116,
"A/K NEW GAME", COLOR_YELLOW, 1);
return;
}
fill_rect(frame, 0, 0, LCD_WIDTH, BOULDER_DASH_STATUS_HEIGHT, COLOR_DARKBLUE);
(void)snprintf(level_text, sizeof(level_text), "LEVEL %u",
(unsigned)(boulder_dash_level_index + 1U));
drawPet2001TextTransparent(frame, 8, 4, level_text, COLOR_WHITE, 2);
if (boulder_dash_state == BOULDER_DASH_CRUSHED) {
drawPet2001TextTransparent(frame, 210, 3, "CRUSHED", COLOR_LIGHTCORAL, 1);
(void)snprintf(level_text, sizeof(level_text), "A RESET L %u",
(unsigned)boulder_dash_lives);
drawPet2001TextTransparent(frame, 210, 13, level_text, COLOR_YELLOW, 1);
} else {
(void)snprintf(score_text, sizeof(score_text), "S %ld", (long)boulder_dash_score);
(void)snprintf(diamond_text, sizeof(diamond_text), "D %u/%u",
(unsigned)boulder_dash_diamonds_collected,
(unsigned)boulder_dash_diamonds_required);
drawPet2001TextTransparent(frame, 210, 3, score_text, COLOR_CYAN, 1);
drawPet2001TextTransparent(frame, 260, 3, diamond_text, COLOR_CYAN, 1);
(void)snprintf(level_text, sizeof(level_text), "%s L%u",
boulderDashExitOpen() ? "EXIT OPEN" : "EXIT LOCK",
(unsigned)boulder_dash_lives);
drawPet2001TextTransparent(frame, 210, 13, level_text,
boulderDashExitOpen() ? COLOR_LIGHTGREEN : COLOR_LIGHTGREY, 1);
}
for (int row = 0; row < BOULDER_DASH_CAVE_ROWS; ++row) {
for (int column = 0; column < BOULDER_DASH_CAVE_COLUMNS; ++column) {
drawBoulderDashTile(frame, column * BOULDER_DASH_TILE_SIZE,
BOULDER_DASH_STATUS_HEIGHT + row * BOULDER_DASH_TILE_SIZE,
boulder_dash_cave[row][column], boulderDashExitOpen());
}
}
if (boulder_dash_state == BOULDER_DASH_PLAYING) {
const int player_x = boulder_dash_player_column * BOULDER_DASH_TILE_SIZE;
const int player_y = BOULDER_DASH_STATUS_HEIGHT +
boulder_dash_player_row * BOULDER_DASH_TILE_SIZE;
drawBoulderDashTile(frame, player_x, player_y, BOULDER_DASH_PLAYER,
boulderDashExitOpen());
}
if (boulder_dash_level_transition_pending) {
// The cave and PET status strip above are the completed destination captured by
// the existing global effect. If PSRAM is unavailable, leave this full frame visible.
const bool started = boulder_dash_level_reveal_selected
? screenRevealStart(&boulder_dash_level_transition, frame,
boulder_dash_now_ms, BOULDER_DASH_LEVEL_REVEAL_MS,
BOULDER_DASH_LEVEL_REVEAL_PERCENT)
: screenSliceStart(&boulder_dash_level_transition, frame,
boulder_dash_now_ms, BOULDER_DASH_LEVEL_SLICE_COUNT,
BOULDER_DASH_LEVEL_SLICE_MS);
boulder_dash_level_transition_pending = false;
if (started) {
if (boulder_dash_level_reveal_selected) {
screenRevealDraw(&boulder_dash_level_transition, frame,
boulder_dash_now_ms);
} else {
screenSliceDraw(&boulder_dash_level_transition, frame,
boulder_dash_now_ms);
}
}
}
}