Battleships
Source: src/battleships.c · 429 lines · 34 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
inside() · source line 45
static bool inside(int row, int column)
- Exit this function
return row >= 0 && row < GRID_SIZE && column >= 0 && column < GRID_SIZE;
Read original function source
static bool inside(int row, int column) { return row >= 0 && row < GRID_SIZE && column >= 0 && column < GRID_SIZE; }say() · source line 46
static void say(const char *text)
- Execute
snprintf(message, sizeof(message), "%s", text);
Read original function source
static void say(const char *text) { snprintf(message, sizeof(message), "%s", text); }clear_board() · source line 49
static void clear_board(board_t *board)
- FOR loop
int row = 0; row < GRID_SIZE; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 0; column < GRID_SIZE; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
board->cells[row][column] = (cell_t){NO_SHIP, SHOT_UNKNOWN};
- FOR loop
int ship = 0; ship < SHIP_COUNT; ++shipRepeat body while the loop condition permits; then continue below.
- Execute
board->ships[ship] = (ship_t){ship_lengths[ship], 0, false};
Read original function source
static void clear_board(board_t *board)
{
for (int row = 0; row < GRID_SIZE; ++row)
for (int column = 0; column < GRID_SIZE; ++column)
board->cells[row][column] = (cell_t){NO_SHIP, SHOT_UNKNOWN};
for (int ship = 0; ship < SHIP_COUNT; ++ship)
board->ships[ship] = (ship_t){ship_lengths[ship], 0, false};
}clear_game() · source line 58
static void clear_game(void)
Calls into this documentation:
- Execute
clear_board(&player_board); - Execute
clear_board(&enemy_board); - Execute
player_ai.count = enemy_ai.count = 0; - Execute
selected_row = selected_column = 0; - Execute
placing_ship = SHIP_COUNT - 1; - Execute
horizontal = true; - Execute
player_turn = true; - Execute
reset_navigation();
Read original function source
static void clear_game(void)
{
clear_board(&player_board); clear_board(&enemy_board);
player_ai.count = enemy_ai.count = 0;
selected_row = selected_column = 0;
placing_ship = SHIP_COUNT - 1; horizontal = true; player_turn = true;
reset_navigation();
}choose_mode() · source line 67
static void choose_mode(void)
Calls into this documentation:
- Execute
game_state = STATE_CHOOSE_MODE; - Execute
auto_mode = false; - Execute
random_placement = false; - Execute
fleet_error = false; - Execute
say(""); - Execute
reset_navigation();
Read original function source
static void choose_mode(void)
{
game_state = STATE_CHOOSE_MODE; auto_mode = false; random_placement = false;
fleet_error = false; say(""); reset_navigation();
}can_place() · source line 73
static bool can_place(const board_t *board, int row, int column, uint8_t length, bool is_horizontal)
Calls into this documentation:
- Execute
const int end_row = row + (is_horizontal ? 0 : length - 1); - Execute
const int end_column = column + (is_horizontal ? length - 1 : 0); - Decision
!inside(row, column) || !inside(end_row, end_column)True- Exit this function
return false;
FalseContinue after this decision.
- FOR loop
int part = 0; part < length; ++partRepeat body while the loop condition permits; then continue below.
- Execute
const int ship_row = row + (is_horizontal ? 0 : part); - Execute
const int ship_column = column + (is_horizontal ? part : 0); - FOR loop
int row_step = -1; row_step <= 1; ++row_stepRepeat body while the loop condition permits; then continue below.
- FOR loop
int column_step = -1; column_step <= 1; ++column_stepRepeat body while the loop condition permits; then continue below.
- Execute
const int near_row = ship_row + row_step, near_column = ship_column + column_step; - Decision
inside(near_row, near_column) && board->cells[near_row][near_column].ship_index != NO_SHIPTrue- Exit this function
return false;
FalseContinue after this decision.
- Exit this function
return true;
Read original function source
static bool can_place(const board_t *board, int row, int column, uint8_t length, bool is_horizontal)
{
const int end_row = row + (is_horizontal ? 0 : length - 1);
const int end_column = column + (is_horizontal ? length - 1 : 0);
if (!inside(row, column) || !inside(end_row, end_column)) return false;
for (int part = 0; part < length; ++part) {
const int ship_row = row + (is_horizontal ? 0 : part);
const int ship_column = column + (is_horizontal ? part : 0);
for (int row_step = -1; row_step <= 1; ++row_step)
for (int column_step = -1; column_step <= 1; ++column_step) {
const int near_row = ship_row + row_step, near_column = ship_column + column_step;
if (inside(near_row, near_column) && board->cells[near_row][near_column].ship_index != NO_SHIP) return false;
}
}
return true;
}place_ship() · source line 90
static void place_ship(board_t *board, uint8_t ship_index, uint8_t row, uint8_t column, bool is_horizontal)
- Execute
ship_t *ship = &board->ships[ship_index]; - FOR loop
int part = 0; part < ship->length; ++partRepeat body while the loop condition permits; then continue below.
- Execute
board->cells[row + (is_horizontal ? 0 : part)][column + (is_horizontal ? part : 0)].ship_index = ship_index;
- Execute
ship->placed = true; - Execute
ship->hits = 0;
Read original function source
static void place_ship(board_t *board, uint8_t ship_index, uint8_t row, uint8_t column, bool is_horizontal)
{
ship_t *ship = &board->ships[ship_index];
for (int part = 0; part < ship->length; ++part)
board->cells[row + (is_horizontal ? 0 : part)][column + (is_horizontal ? part : 0)].ship_index = ship_index;
ship->placed = true; ship->hits = 0;
}place_random_fleet() · source line 98
static bool place_random_fleet(board_t *board)
Calls into this documentation:
- FOR loop
int fleet_try = 0; fleet_try < 100; ++fleet_tryRepeat body while the loop condition permits; then continue below.
- Execute
clear_board(board); - Execute
bool complete = true; - FOR loop
int ship = SHIP_COUNT - 1; ship >= 0; --shipRepeat body while the loop condition permits; then continue below.
- Execute
bool placed = false; - FOR loop
int try = 0; try < 250; ++tryRepeat body while the loop condition permits; then continue below.
- Execute
const bool is_horizontal = esp_random() % 2U == 0; - Execute
const uint8_t row = esp_random() % GRID_SIZE, column = esp_random() % GRID_SIZE; - Decision
can_place(board, row, column, board->ships[ship].length, is_horizontal)True- Execute
place_ship(board, ship, row, column, is_horizontal); - Execute
placed = true; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
!placedTrue- Execute
complete = false; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
completeTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool place_random_fleet(board_t *board)
{
for (int fleet_try = 0; fleet_try < 100; ++fleet_try) {
clear_board(board); bool complete = true;
for (int ship = SHIP_COUNT - 1; ship >= 0; --ship) {
bool placed = false;
for (int try = 0; try < 250; ++try) {
const bool is_horizontal = esp_random() % 2U == 0;
const uint8_t row = esp_random() % GRID_SIZE, column = esp_random() % GRID_SIZE;
if (can_place(board, row, column, board->ships[ship].length, is_horizontal)) {
place_ship(board, ship, row, column, is_horizontal); placed = true; break;
}
}
if (!placed) { complete = false; break; }
}
if (complete) return true;
}
return false;
}all_sunk() · source line 118
static bool all_sunk(const board_t *board)
- FOR loop
int ship = 0; ship < SHIP_COUNT; ++shipRepeat body while the loop condition permits; then continue below.
- Decision
!board->ships[ship].placed || board->ships[ship].hits < board->ships[ship].lengthTrue- Exit this function
return false;
FalseContinue after this decision.
- Exit this function
return true;
Read original function source
static bool all_sunk(const board_t *board)
{
for (int ship = 0; ship < SHIP_COUNT; ++ship)
if (!board->ships[ship].placed || board->ships[ship].hits < board->ships[ship].length) return false;
return true;
}fire_at() · source line 125
static shot_result_t fire_at(board_t *board, uint8_t row, uint8_t column)
- Execute
cell_t *cell = &board->cells[row][column]; - Decision
cell->shot != SHOT_UNKNOWNTrue- Exit this function
return RESULT_INVALID;
FalseContinue after this decision.
- Decision
cell->ship_index == NO_SHIPTrue- Execute
cell->shot = SHOT_MISS; - Exit this function
return RESULT_MISS;
FalseContinue after this decision.
- Execute
cell->shot = SHOT_HIT; - Execute
ship_t *ship = &board->ships[cell->ship_index]; - Execute
++ship->hits; - Exit this function
return ship->hits >= ship->length ? RESULT_SUNK : RESULT_HIT;
Read original function source
static shot_result_t fire_at(board_t *board, uint8_t row, uint8_t column)
{
cell_t *cell = &board->cells[row][column];
if (cell->shot != SHOT_UNKNOWN) return RESULT_INVALID;
if (cell->ship_index == NO_SHIP) { cell->shot = SHOT_MISS; return RESULT_MISS; }
cell->shot = SHOT_HIT;
ship_t *ship = &board->ships[cell->ship_index];
++ship->hits;
return ship->hits >= ship->length ? RESULT_SUNK : RESULT_HIT;
}move_cell() · source line 136
static void move_cell(int direction, bool unknown_only)
- Execute
int position = selected_row * GRID_SIZE + selected_column; - FOR loop
int count = 0; count < 100; ++countRepeat body while the loop condition permits; then continue below.
- Execute
position = (position + direction + 100) % 100; - Execute
const int row = position / GRID_SIZE, column = position % GRID_SIZE; - Decision
!unknown_only || enemy_board.cells[row][column].shot == SHOT_UNKNOWNTrue- Execute
selected_row = row; - Execute
selected_column = column; - Exit this function
return;
FalseContinue after this decision.
Read original function source
static void move_cell(int direction, bool unknown_only)
{
int position = selected_row * GRID_SIZE + selected_column;
for (int count = 0; count < 100; ++count) {
position = (position + direction + 100) % 100;
const int row = position / GRID_SIZE, column = position % GRID_SIZE;
if (!unknown_only || enemy_board.cells[row][column].shot == SHOT_UNKNOWN) {
selected_row = row; selected_column = column; return;
}
}
}queued() · source line 148
static bool queued(const ai_state_t *ai, uint8_t row, uint8_t column)
- FOR loop
int index = 0; index < ai->count; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
ai->rows[index] == row && ai->columns[index] == columnTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool queued(const ai_state_t *ai, uint8_t row, uint8_t column)
{
for (int index = 0; index < ai->count; ++index)
if (ai->rows[index] == row && ai->columns[index] == column) return true;
return false;
}queue_target() · source line 155
static void queue_target(ai_state_t *ai, const board_t *board, int row, int column)
Calls into this documentation:
- Decision
!inside(row, column) || board->cells[row][column].shot != SHOT_UNKNOWN || queued(ai, row, column) || ai->count >= 100True- Exit this function
return;
FalseContinue after this decision.
- Execute
ai->rows[ai->count] = row; - Execute
ai->columns[ai->count] = column; - Execute
++ai->count;
Read original function source
static void queue_target(ai_state_t *ai, const board_t *board, int row, int column)
{
if (!inside(row, column) || board->cells[row][column].shot != SHOT_UNKNOWN || queued(ai, row, column) || ai->count >= 100) return;
ai->rows[ai->count] = row; ai->columns[ai->count] = column; ++ai->count;
}remember_hit() · source line 161
static void remember_hit(ai_state_t *ai, const board_t *board, uint8_t row, uint8_t column, shot_result_t result)
Calls into this documentation:
- Decision
result == RESULT_SUNKTrue- Execute
ai->count = 0; - Exit this function
return;
FalseContinue after this decision.
- Decision
result != RESULT_HITTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
queue_target(ai, board, row - 1, column); - Execute
queue_target(ai, board, row + 1, column); - Execute
queue_target(ai, board, row, column - 1); - Execute
queue_target(ai, board, row, column + 1);
Read original function source
static void remember_hit(ai_state_t *ai, const board_t *board, uint8_t row, uint8_t column, shot_result_t result)
{
if (result == RESULT_SUNK) { ai->count = 0; return; }
if (result != RESULT_HIT) return;
queue_target(ai, board, row - 1, column); queue_target(ai, board, row + 1, column);
queue_target(ai, board, row, column - 1); queue_target(ai, board, row, column + 1);
}choose_ai_target() · source line 169
static bool choose_ai_target(ai_state_t *ai, const board_t *board, uint8_t *row, uint8_t *column)
- WHILE loop
ai->count > 0Repeat body while the loop condition permits; then continue below.
- Execute
--ai->count; - Execute
*row = ai->rows[ai->count]; - Execute
*column = ai->columns[ai->count]; - Decision
board->cells[*row][*column].shot == SHOT_UNKNOWNTrue- Exit this function
return true;
FalseContinue after this decision.
- Execute
unsigned available = 0; - FOR loop
int r = 0; r < GRID_SIZE; ++rRepeat body while the loop condition permits; then continue below.
- FOR loop
int c = 0; c < GRID_SIZE; ++cRepeat body while the loop condition permits; then continue below.
- Decision
board->cells[r][c].shot == SHOT_UNKNOWNTrue- Execute
++available;
FalseContinue after this decision.
- Decision
available == 0True- Exit this function
return false;
FalseContinue after this decision.
- Execute
unsigned selection = esp_random() % available; - FOR loop
int r = 0; r < GRID_SIZE; ++rRepeat body while the loop condition permits; then continue below.
- FOR loop
int c = 0; c < GRID_SIZE; ++cRepeat body while the loop condition permits; then continue below.
- Decision
board->cells[r][c].shot == SHOT_UNKNOWN && selection-- == 0True- Execute
*row = r; - Execute
*column = c; - Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool choose_ai_target(ai_state_t *ai, const board_t *board, uint8_t *row, uint8_t *column)
{
while (ai->count > 0) {
--ai->count; *row = ai->rows[ai->count]; *column = ai->columns[ai->count];
if (board->cells[*row][*column].shot == SHOT_UNKNOWN) return true;
}
unsigned available = 0;
for (int r = 0; r < GRID_SIZE; ++r) for (int c = 0; c < GRID_SIZE; ++c)
if (board->cells[r][c].shot == SHOT_UNKNOWN) ++available;
if (available == 0) return false;
unsigned selection = esp_random() % available;
for (int r = 0; r < GRID_SIZE; ++r) for (int c = 0; c < GRID_SIZE; ++c)
if (board->cells[r][c].shot == SHOT_UNKNOWN && selection-- == 0) { *row = r; *column = c; return true; }
return false;
}increment() · source line 185
static void increment(uint32_t *value)
- Decision
*value < UINT32_MAXTrue- Execute
++*value;
FalseContinue after this decision.
Read original function source
static void increment(uint32_t *value) { if (*value < UINT32_MAX) ++*value; }finish_game() · source line 187
static void finish_game(bool player_won)
Calls into this documentation:
- Execute
game_state = STATE_FINISHED; - Execute
state_started = now; - Execute
reset_navigation(); - Decision
auto_modeTrue- Execute
say(player_won ? "LEFT WINS" : "RIGHT WINS"); - Exit this function
return;
FalseContinue after this decision.
- Decision
player_wonTrue- Execute
increment(&scores.wins); - Execute
increment(&scores.streak); - Decision
!addUserGoldWon(BATTLESHIPS_WIN_GOLD)True- Execute
gold_save_failed = true;
FalseContinue after this decision.
- Execute
say("YOU WIN");
False- Execute
increment(&scores.losses); - Execute
scores.streak = 0; - Execute
say("ESP WINS");
- Decision
score_status == SCORES_OK || score_status == SCORES_NOT_FOUNDTrue- Execute
score_save_failed = !game_scores_save(GAME_BATTLESHIPS, &scores); - Decision
!score_save_failedTrue- Execute
score_status = SCORES_OK;
FalseContinue after this decision.
False- Execute
score_save_failed = true;
Read original function source
static void finish_game(bool player_won)
{
game_state = STATE_FINISHED; state_started = now; reset_navigation();
if (auto_mode) { say(player_won ? "LEFT WINS" : "RIGHT WINS"); return; }
if (player_won) { increment(&scores.wins); increment(&scores.streak); if (!addUserGoldWon(BATTLESHIPS_WIN_GOLD)) gold_save_failed = true; say("YOU WIN"); }
else { increment(&scores.losses); scores.streak = 0; say("ESP WINS"); }
if (score_status == SCORES_OK || score_status == SCORES_NOT_FOUND) {
score_save_failed = !game_scores_save(GAME_BATTLESHIPS, &scores);
if (!score_save_failed) score_status = SCORES_OK;
} else score_save_failed = true;
}finish_shot() · source line 199
static void finish_shot(const board_t *target, shot_result_t result, uint8_t row, uint8_t column, bool attacker_is_player)
Calls into this documentation:
- Execute
audioPlay(result == RESULT_MISS ? AUDIO_SFX_BOARD_PLACE : AUDIO_SFX_BOARD_CAPTURE); - Execute
const char *name = auto_mode ? (attacker_is_player ? "LEFT" : "RIGHT") : (attacker_is_player ? "YOU" : "ESP"); - Execute
const char *result_text = result == RESULT_SUNK ? "SUNK" : result == RESULT_HIT ? "HIT" : "MISS"; - Execute
snprintf(message, sizeof(message), "%s %s %c%u", name, result_text, 'A' + column, (unsigned)row + 1U); - Decision
all_sunk(target)True- Execute
finish_game(attacker_is_player); - Exit this function
return;
FalseContinue after this decision.
- Execute
player_turn = !attacker_is_player; - Execute
ai_due = now + aiSpeedDelay(ai_speed); - Execute
reset_navigation(); - Decision
player_turn && !auto_mode && enemy_board.cells[selected_row][selected_column].shot != SHOT_UNKNOWNTrue- Execute
move_cell(1, true);
FalseContinue after this decision.
Read original function source
static void finish_shot(const board_t *target, shot_result_t result, uint8_t row, uint8_t column, bool attacker_is_player)
{
audioPlay(result == RESULT_MISS ? AUDIO_SFX_BOARD_PLACE : AUDIO_SFX_BOARD_CAPTURE);
const char *name = auto_mode ? (attacker_is_player ? "LEFT" : "RIGHT") : (attacker_is_player ? "YOU" : "ESP");
const char *result_text = result == RESULT_SUNK ? "SUNK" : result == RESULT_HIT ? "HIT" : "MISS";
snprintf(message, sizeof(message), "%s %s %c%u", name, result_text, 'A' + column, (unsigned)row + 1U);
if (all_sunk(target)) { finish_game(attacker_is_player); return; }
player_turn = !attacker_is_player; ai_due = now + aiSpeedDelay(ai_speed); reset_navigation();
if (player_turn && !auto_mode && enemy_board.cells[selected_row][selected_column].shot != SHOT_UNKNOWN) move_cell(1, true);
}perform_ai_shot() · source line 210
static void perform_ai_shot(board_t *target, ai_state_t *ai, bool attacker_is_player)
Calls into this documentation:
- Execute
uint8_t row, column; - Decision
!choose_ai_target(ai, target, &row, &column)True- Exit this function
return;
FalseContinue after this decision.
- Execute
const shot_result_t result = fire_at(target, row, column); - Execute
remember_hit(ai, target, row, column, result); - Execute
selected_row = row; - Execute
selected_column = column; - Execute
finish_shot(target, result, row, column, attacker_is_player);
Read original function source
static void perform_ai_shot(board_t *target, ai_state_t *ai, bool attacker_is_player)
{
uint8_t row, column;
if (!choose_ai_target(ai, target, &row, &column)) return;
const shot_result_t result = fire_at(target, row, column);
remember_hit(ai, target, row, column, result); selected_row = row; selected_column = column;
finish_shot(target, result, row, column, attacker_is_player);
}start_combat() · source line 219
static void start_combat(void)
Calls into this documentation:
- Execute
player_ai.count = enemy_ai.count = 0; - Execute
selected_row = selected_column = 0; - Execute
player_turn = true; - Execute
game_state = STATE_PLAYING; - Execute
fleet_error = false; - Execute
say(auto_mode ? "AUTO BATTLE" : "YOUR SHOT"); - Execute
ai_due = now + aiSpeedDelay(ai_speed); - Execute
reset_navigation();
Read original function source
static void start_combat(void)
{
player_ai.count = enemy_ai.count = 0; selected_row = selected_column = 0; player_turn = true;
game_state = STATE_PLAYING; fleet_error = false; say(auto_mode ? "AUTO BATTLE" : "YOUR SHOT");
ai_due = now + aiSpeedDelay(ai_speed); reset_navigation();
}fleet_error_state() · source line 226
static void fleet_error_state(void)
Calls into this documentation:
- Execute
choose_mode(); - Execute
fleet_error = true; - Execute
say("FLEET ERROR - RETRY"); - Execute
ESP_LOGE("battleships", "Fleet placement failed");
Read original function source
static void fleet_error_state(void)
{
choose_mode(); fleet_error = true; say("FLEET ERROR - RETRY");
ESP_LOGE("battleships", "Fleet placement failed");
}prepare_random_game() · source line 232
static void prepare_random_game(void)
Calls into this documentation:
- Execute
clear_game(); - Decision
!place_random_fleet(&player_board) || !place_random_fleet(&enemy_board)True- Execute
fleet_error_state(); - Exit this function
return;
FalseContinue after this decision.
- Execute
start_combat();
Read original function source
static void prepare_random_game(void)
{
clear_game();
if (!place_random_fleet(&player_board) || !place_random_fleet(&enemy_board)) { fleet_error_state(); return; }
start_combat();
}start_manual_placement() · source line 239
static void start_manual_placement(void)
Calls into this documentation:
- Execute
clear_game(); - Execute
game_state = STATE_PLACING_POSITION; - Execute
say("SELECT START");
Read original function source
static void start_manual_placement(void)
{
clear_game(); game_state = STATE_PLACING_POSITION; say("SELECT START");
}confirm_placement() · source line 244
static void confirm_placement(void)
Calls into this documentation:
- Execute
const uint8_t length = player_board.ships[placing_ship].length; - Decision
!can_place(&player_board, selected_row, selected_column, length, horizontal)True- Execute
game_state = STATE_PLACING_POSITION; - Execute
say("NO FIT"); - Execute
reset_navigation(); - Exit this function
return;
FalseContinue after this decision.
- Execute
place_ship(&player_board, placing_ship, selected_row, selected_column, horizontal); - Execute
audioPlay(AUDIO_SFX_BOARD_PLACE); - Decision
placing_ship == 0True- Decision
!place_random_fleet(&enemy_board)True- Execute
fleet_error_state(); - Exit this function
return;
FalseContinue after this decision.
- Execute
start_combat(); - Exit this function
return;
FalseContinue after this decision.
- Execute
--placing_ship; - Execute
game_state = STATE_PLACING_POSITION; - Execute
say("SELECT START"); - Execute
reset_navigation();
Read original function source
static void confirm_placement(void)
{
const uint8_t length = player_board.ships[placing_ship].length;
if (!can_place(&player_board, selected_row, selected_column, length, horizontal)) {
game_state = STATE_PLACING_POSITION; say("NO FIT"); reset_navigation(); return;
}
place_ship(&player_board, placing_ship, selected_row, selected_column, horizontal);
audioPlay(AUDIO_SFX_BOARD_PLACE);
if (placing_ship == 0) {
if (!place_random_fleet(&enemy_board)) { fleet_error_state(); return; }
start_combat(); return;
}
--placing_ship; game_state = STATE_PLACING_POSITION; say("SELECT START"); reset_navigation();
}startBattleships() · source line 259
void startBattleships(uint32_t now_ms)
Calls into this documentation:
- Execute
now = state_started = now_ms; - Execute
running = true; - Execute
score_save_failed = gold_save_failed = false; - Execute
scores = (game_scores_t){0}; - Execute
score_status = game_scores_load(GAME_BATTLESHIPS, &scores); - Execute
ai_speed = AI_SPEED_DEFAULT_INDEX; - Execute
clear_game(); - Execute
choose_mode(); - Execute
game_state = STATE_SHOW_SCORES; - Execute
ESP_LOGI("battleships", "START: %s", game_scores_status_text(score_status));
Read original function source
void startBattleships(uint32_t now_ms)
{
now = state_started = now_ms; running = true; score_save_failed = gold_save_failed = false;
scores = (game_scores_t){0}; score_status = game_scores_load(GAME_BATTLESHIPS, &scores);
ai_speed = AI_SPEED_DEFAULT_INDEX; clear_game(); choose_mode(); game_state = STATE_SHOW_SCORES;
ESP_LOGI("battleships", "START: %s", game_scores_status_text(score_status));
}stopBattleships() · source line 267
void stopBattleships(void)
- Execute
running = false; - Execute
ESP_LOGI("battleships", "STOP");
Read original function source
void stopBattleships(void) { running = false; ESP_LOGI("battleships", "STOP"); }battleshipsAction() · source line 269
void battleshipsAction(bool select, bool speed, bool choose, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
now = now_ms; - Decision
chooseTrue- Execute
choose_mode(); - Exit this function
return;
FalseContinue after this decision.
- Decision
game_state == STATE_SHOW_SCORESTrue- Decision
selectTrue- Execute
choose_mode();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
game_state == STATE_CHOOSE_MODETrue- Decision
selectTrue- Execute
fleet_error = false; - Decision
auto_modeTrue- Execute
prepare_random_game();
False- Execute
random_placement = false; - Execute
game_state = STATE_CHOOSE_PLACEMENT; - Execute
reset_navigation();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
game_state == STATE_CHOOSE_PLACEMENTTrue- Decision
selectTrue- Decision
random_placementTrue- Execute
prepare_random_game();
False- Execute
start_manual_placement();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
auto_modeTrue- Decision
speedTrue- Execute
cycleAISpeed(&ai_speed); - Execute
ai_due = now + aiSpeedDelay(ai_speed);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
!selectTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
game_state == STATE_FINISHEDTrue- Execute
choose_mode();
False- Decision
game_state == STATE_PLACING_POSITIONTrue- Execute
game_state = STATE_PLACING_ORIENTATION; - Execute
say("CHOOSE H/V"); - Execute
reset_navigation();
False- Decision
game_state == STATE_PLACING_ORIENTATIONTrue- Execute
confirm_placement();
False- Decision
game_state == STATE_PLAYING && player_turnTrue- Execute
const shot_result_t result = fire_at(&enemy_board, selected_row, selected_column); - Decision
result != RESULT_INVALIDTrue- Execute
finish_shot(&enemy_board, result, selected_row, selected_column, true);
FalseContinue after this decision.
FalseContinue after this decision.
Read original function source
void battleshipsAction(bool select, bool speed, bool choose, uint32_t now_ms)
{
if (!running) return;
now = now_ms;
if (choose) { choose_mode(); return; }
if (game_state == STATE_SHOW_SCORES) { if (select) choose_mode(); return; }
if (game_state == STATE_CHOOSE_MODE) {
if (select) { fleet_error = false; if (auto_mode) prepare_random_game(); else { random_placement = false; game_state = STATE_CHOOSE_PLACEMENT; reset_navigation(); } }
return;
}
if (game_state == STATE_CHOOSE_PLACEMENT) { if (select) { if (random_placement) prepare_random_game(); else start_manual_placement(); } return; }
if (auto_mode) { if (speed) { cycleAISpeed(&ai_speed); ai_due = now + aiSpeedDelay(ai_speed); } return; }
if (!select) return;
if (game_state == STATE_FINISHED) choose_mode();
else if (game_state == STATE_PLACING_POSITION) { game_state = STATE_PLACING_ORIENTATION; say("CHOOSE H/V"); reset_navigation(); }
else if (game_state == STATE_PLACING_ORIENTATION) confirm_placement();
else if (game_state == STATE_PLAYING && player_turn) {
const shot_result_t result = fire_at(&enemy_board, selected_row, selected_column);
if (result != RESULT_INVALID) finish_shot(&enemy_board, result, selected_row, selected_column, true);
}
}updateBattleships() · source line 291
void updateBattleships(int x_offset, int y_offset, bool valid, bool left, bool right, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
now = now_ms; - Decision
game_state == STATE_SHOW_SCORESTrue- Decision
now - state_started >= SCORE_DISPLAY_MSTrue- Execute
choose_mode();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
game_state == STATE_FINISHEDTrue- Decision
auto_mode && now - state_started >= aiRestartDelay()True- Execute
prepare_random_game();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
game_state == STATE_PLAYING && (auto_mode || !player_turn)True- Decision
(int32_t)(now - ai_due) >= 0True- Decision
player_turnTrue- Execute
perform_ai_shot(&enemy_board, &player_ai, true);
False- Execute
perform_ai_shot(&player_board, &enemy_ai, false);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
!validTrue- Execute
stick_centred = false;
FalseContinue after this decision.
- Execute
const int ax = x_offset < 0 ? -x_offset : x_offset, ay = y_offset < 0 ? -y_offset : y_offset; - Decision
valid && ax <= 350 && ay <= 350True- Execute
stick_centred = true;
FalseContinue after this decision.
- Execute
int direction = 0; - Decision
left || rightTrue- Execute
direction = (int)right - (int)left; - Decision
direction == 0True- Execute
held_direction = 0; - Execute
repeating = false; - Exit this function
return;
FalseContinue after this decision.
False- Decision
!valid || (ax <= 350 && ay <= 350)True- Execute
held_direction = 0; - Execute
repeating = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
!stick_centredTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
ax >= ay && ax >= 600True- Execute
direction = x_offset > 0 ? 1 : -1;
False- Decision
ay >= 600True- Execute
direction = y_offset < 0 ? 1 : -1;
FalseContinue after this decision.
- Decision
direction == 0 || (held_direction == direction && now - last_move < (repeating ? 150U : 400U))True- Exit this function
return;
FalseContinue after this decision.
- Execute
repeating = held_direction == direction; - Execute
held_direction = direction; - Execute
last_move = now; - Decision
game_state == STATE_CHOOSE_MODETrue- Execute
auto_mode = !auto_mode;
False- Decision
game_state == STATE_CHOOSE_PLACEMENTTrue- Execute
random_placement = !random_placement;
False- Decision
game_state == STATE_PLACING_POSITIONTrue- Execute
move_cell(direction, false);
False- Decision
game_state == STATE_PLACING_ORIENTATIONTrue- Execute
horizontal = !horizontal;
False- Decision
game_state == STATE_PLAYINGTrue- Execute
move_cell(direction, true);
FalseContinue after this decision.
Read original function source
void updateBattleships(int x_offset, int y_offset, bool valid, bool left, bool right, uint32_t now_ms)
{
if (!running) return;
now = now_ms;
if (game_state == STATE_SHOW_SCORES) { if (now - state_started >= SCORE_DISPLAY_MS) choose_mode(); return; }
if (game_state == STATE_FINISHED) { if (auto_mode && now - state_started >= aiRestartDelay()) prepare_random_game(); return; }
if (game_state == STATE_PLAYING && (auto_mode || !player_turn)) {
if ((int32_t)(now - ai_due) >= 0) { if (player_turn) perform_ai_shot(&enemy_board, &player_ai, true); else perform_ai_shot(&player_board, &enemy_ai, false); }
return;
}
if (!valid) stick_centred = false;
const int ax = x_offset < 0 ? -x_offset : x_offset, ay = y_offset < 0 ? -y_offset : y_offset;
if (valid && ax <= 350 && ay <= 350) stick_centred = true;
int direction = 0;
if (left || right) { direction = (int)right - (int)left; if (direction == 0) { held_direction = 0; repeating = false; return; } }
else {
if (!valid || (ax <= 350 && ay <= 350)) { held_direction = 0; repeating = false; return; }
if (!stick_centred) return;
if (ax >= ay && ax >= 600) direction = x_offset > 0 ? 1 : -1;
else if (ay >= 600) direction = y_offset < 0 ? 1 : -1;
}
if (direction == 0 || (held_direction == direction && now - last_move < (repeating ? 150U : 400U))) return;
repeating = held_direction == direction; held_direction = direction; last_move = now;
if (game_state == STATE_CHOOSE_MODE) auto_mode = !auto_mode;
else if (game_state == STATE_CHOOSE_PLACEMENT) random_placement = !random_placement;
else if (game_state == STATE_PLACING_POSITION) move_cell(direction, false);
else if (game_state == STATE_PLACING_ORIENTATION) horizontal = !horizontal;
else if (game_state == STATE_PLAYING) move_cell(direction, true);
}storage_message() · source line 321
static const char *storage_message(void)
Calls into this documentation:
- Decision
gold_save_failed || !goldStorageHealthy()True- Exit this function
return "GOLD SAVE ERROR";
FalseContinue after this decision.
- Decision
score_save_failedTrue- Exit this function
return "SCORE SAVE ERROR";
FalseContinue after this decision.
- Decision
score_status != SCORES_OK && score_status != SCORES_NOT_FOUNDTrue- Exit this function
return game_scores_status_text(score_status);
FalseContinue after this decision.
- Decision
!aiTimingStorageHealthy()True- Exit this function
return "AI TIMING ERROR";
FalseContinue after this decision.
- Exit this function
return NULL;
Read original function source
static const char *storage_message(void)
{
if (gold_save_failed || !goldStorageHealthy()) return "GOLD SAVE ERROR";
if (score_save_failed) return "SCORE SAVE ERROR";
if (score_status != SCORES_OK && score_status != SCORES_NOT_FOUND) return game_scores_status_text(score_status);
if (!aiTimingStorageHealthy()) return "AI TIMING ERROR";
return NULL;
}draw_grid() · source line 330
static void draw_grid(uint16_t *frame, const board_t *board, int board_x, bool hide_ships, bool selected)
Calls into this documentation:
- FOR loop
int row = 0; row < GRID_SIZE; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 0; column < GRID_SIZE; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
const cell_t *cell = &board->cells[row][column]; - Execute
const int x = board_x + column * CELL_SIZE, y = BOARD_Y + row * CELL_SIZE; - Execute
uint16_t color = COLOR_BLACK; - Decision
!hide_ships && cell->ship_index != NO_SHIPTrue- Execute
color = COLOR_BLUE;
FalseContinue after this decision.
- Decision
cell->shot == SHOT_MISSTrue- Execute
color = COLOR_DIMGRAY;
False- Decision
cell->shot == SHOT_HITTrue- Execute
color = COLOR_RED;
FalseContinue after this decision.
- Execute
fill_rect(frame, x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2, color); - Decision
cell->shot == SHOT_MISSTrue- Execute
fill_circle(frame, x + 6, y + 6, 2, COLOR_CYAN);
False- Decision
cell->shot == SHOT_HITTrue- Execute
draw_line(frame, x + 3, y + 3, x + 8, y + 8, COLOR_YELLOW); - Execute
draw_line(frame, x + 8, y + 3, x + 3, y + 8, COLOR_YELLOW);
FalseContinue after this decision.
- Execute
draw_rect(frame, x, y, CELL_SIZE, CELL_SIZE, selected && row == selected_row && column == selected_column ? COLOR_YELLOW : COLOR_LIGHTGRAY);
Read original function source
static void draw_grid(uint16_t *frame, const board_t *board, int board_x, bool hide_ships, bool selected)
{
for (int row = 0; row < GRID_SIZE; ++row) for (int column = 0; column < GRID_SIZE; ++column) {
const cell_t *cell = &board->cells[row][column]; const int x = board_x + column * CELL_SIZE, y = BOARD_Y + row * CELL_SIZE;
uint16_t color = COLOR_BLACK;
if (!hide_ships && cell->ship_index != NO_SHIP) color = COLOR_BLUE;
if (cell->shot == SHOT_MISS) color = COLOR_DIMGRAY; else if (cell->shot == SHOT_HIT) color = COLOR_RED;
fill_rect(frame, x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2, color);
if (cell->shot == SHOT_MISS) fill_circle(frame, x + 6, y + 6, 2, COLOR_CYAN);
else if (cell->shot == SHOT_HIT) { draw_line(frame, x + 3, y + 3, x + 8, y + 8, COLOR_YELLOW); draw_line(frame, x + 8, y + 3, x + 3, y + 8, COLOR_YELLOW); }
draw_rect(frame, x, y, CELL_SIZE, CELL_SIZE, selected && row == selected_row && column == selected_column ? COLOR_YELLOW : COLOR_LIGHTGRAY);
}
}draw_choice() · source line 344
static void draw_choice(uint16_t *frame, const char *left_text, const char *right_text, bool right_selected)
Calls into this documentation:
- Execute
draw_rect(frame, 15, 55, 135, 40, right_selected ? COLOR_DIMGRAY : COLOR_YELLOW); - Execute
draw_rect(frame, 170, 55, 135, 40, right_selected ? COLOR_YELLOW : COLOR_DIMGRAY); - Execute
draw_text_centred(frame, 82, 68, left_text, 2, COLOR_WHITE); - Execute
draw_text_centred(frame, 237, 68, right_text, 2, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 111, "D/B/STICK CHOOSE A/K SELECT", 1, COLOR_WHITE);
Read original function source
static void draw_choice(uint16_t *frame, const char *left_text, const char *right_text, bool right_selected)
{
draw_rect(frame, 15, 55, 135, 40, right_selected ? COLOR_DIMGRAY : COLOR_YELLOW);
draw_rect(frame, 170, 55, 135, 40, right_selected ? COLOR_YELLOW : COLOR_DIMGRAY);
draw_text_centred(frame, 82, 68, left_text, 2, COLOR_WHITE); draw_text_centred(frame, 237, 68, right_text, 2, COLOR_WHITE);
draw_text_centred(frame, 160, 111, "D/B/STICK CHOOSE A/K SELECT", 1, COLOR_WHITE);
}draw_mode_choice() · source line 352
static void draw_mode_choice(uint16_t *frame, bool auto_selected)
Calls into this documentation:
- Execute
draw_text_centred(frame, 82, 68, "PLAY", 2, auto_selected ? COLOR_LIGHTGRAY : COLOR_YELLOW); - Execute
draw_text_centred(frame, 237, 68, "AUTO", 2, auto_selected ? COLOR_YELLOW : COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 160, 111, "D/B/STICK CHOOSE A/K SELECT", 1, COLOR_WHITE);
Read original function source
static void draw_mode_choice(uint16_t *frame, bool auto_selected)
{
draw_text_centred(frame, 82, 68, "PLAY", 2,
auto_selected ? COLOR_LIGHTGRAY : COLOR_YELLOW);
draw_text_centred(frame, 237, 68, "AUTO", 2,
auto_selected ? COLOR_YELLOW : COLOR_LIGHTGRAY);
draw_text_centred(frame, 160, 111, "D/B/STICK CHOOSE A/K SELECT", 1, COLOR_WHITE);
}drawBattleships() · source line 361
void drawBattleships(uint16_t *frame)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
char line[64]; - Execute
const char *status = storage_message(); - Decision
game_state == STATE_SHOW_SCORES || game_state == STATE_CHOOSE_MODE || game_state == STATE_CHOOSE_PLACEMENTTrue- Execute
draw_text_centred(frame, 160, 8, game_state == STATE_CHOOSE_PLACEMENT ? "PLACE FLEET" : "BATTLESHIPS", 3, COLOR_CYAN); - Decision
game_state == STATE_SHOW_SCORESTrue- Execute
snprintf(line, sizeof(line), "WINS %u", (unsigned)scores.wins); - Execute
draw_text_centred(frame, 160, 52, line, 2, COLOR_LIME); - Execute
snprintf(line, sizeof(line), "LOSSES %u", (unsigned)scores.losses); - Execute
draw_text_centred(frame, 160, 77, line, 2, COLOR_RED); - Execute
snprintf(line, sizeof(line), "STREAK %u", (unsigned)scores.streak); - Execute
draw_text_centred(frame, 160, 102, line, 2, COLOR_GOLD);
False- Decision
game_state == STATE_CHOOSE_MODETrue- Execute
draw_mode_choice(frame, auto_mode);
False- Execute
draw_choice(frame, "MANUAL", "RANDOM", random_placement);
- Execute
draw_text_centred(frame, 160, 133, fleet_error ? message : status != NULL ? status : game_scores_status_text(score_status), 1, COLOR_YELLOW); - Execute
draw_text_centred(frame, 160, 157, "C CHOOSE BOOT BACK", 1, COLOR_DIMGRAY); - Exit this function
return;
FalseContinue after this decision.
- Decision
game_state == STATE_PLACING_POSITION || game_state == STATE_PLACING_ORIENTATIONTrue- Execute
snprintf(line, sizeof(line), "SHIP %u", (unsigned)player_board.ships[placing_ship].length); - Execute
draw_text(frame, 4, 4, line, 1, COLOR_CYAN); - Execute
draw_grid(frame, &player_board, LEFT_BOARD_X, false, true); - Execute
snprintf(line, sizeof(line), "%c%u", 'A' + selected_column, (unsigned)selected_row + 1U); - Execute
draw_text_centred(frame, 220, 27, line, 3, COLOR_YELLOW); - Execute
draw_text_centred(frame, 187, 66, "H", 3, horizontal ? COLOR_YELLOW : COLOR_DIMGRAY); - Execute
draw_text_centred(frame, 253, 66, "V", 3, horizontal ? COLOR_DIMGRAY : COLOR_YELLOW); - Execute
draw_text_centred(frame, 220, 101, game_state == STATE_PLACING_ORIENTATION ? "D/B/STICK H/V" : "D/B/STICK CELL", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 220, 118, game_state == STATE_PLACING_ORIENTATION ? "A/K PLACE" : "A/K SELECT", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 145, message, 1, COLOR_YELLOW); - Execute
draw_text_centred(frame, 160, 160, status != NULL ? status : "C CHOOSE BOOT BACK", 1, COLOR_DIMGRAY); - Exit this function
return;
FalseContinue after this decision.
- Execute
draw_text_centred(frame, 62, 5, auto_mode ? "LEFT" : "YOU", 1, COLOR_CYAN); - Execute
draw_text_centred(frame, 258, 5, auto_mode ? "RIGHT" : "ESP", 1, COLOR_CYAN); - Execute
draw_grid(frame, &player_board, LEFT_BOARD_X, false, false); - Execute
draw_grid(frame, &enemy_board, RIGHT_BOARD_X, true, game_state == STATE_PLAYING && player_turn && !auto_mode); - Execute
drawGameGold(frame, 126, 2, 68, true); - Execute
draw_text_centred(frame, 160, 38, auto_mode ? (player_turn ? "LEFT" : "RIGHT") : (player_turn ? "YOUR" : "ESP"), 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 49, game_state == STATE_FINISHED ? "FINISHED" : "TURN", 1, COLOR_WHITE); - Execute
snprintf(line, sizeof(line), "%c%u", 'A' + selected_column, (unsigned)selected_row + 1U); - Execute
draw_text_centred(frame, 160, 66, line, 2, COLOR_CYAN); - Decision
auto_modeTrue- Execute
draw_text_centred(frame, 160, 89, aiSpeedShortText(ai_speed), 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 102, "E SPEED", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 115, "C STOP", 1, COLOR_WHITE); - Decision
game_state == STATE_FINISHEDTrue- Execute
const uint32_t delay = aiRestartDelay(), elapsed = now - state_started; - Execute
snprintf(line, sizeof(line), "NEXT %u", elapsed >= delay ? 0U : (unsigned)((delay - elapsed + 999U) / 1000U)); - Execute
draw_text_centred(frame, 160, 130, line, 1, COLOR_CYAN);
FalseContinue after this decision.
False- Execute
draw_text_centred(frame, 160, 89, "D/B/STICK", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 102, "PREV/NEXT", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 115, game_state == STATE_FINISHED ? "A/K NEW" : "A/K FIRE", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, 160, 130, "C CHOOSE", 1, COLOR_WHITE);
- Execute
draw_text_centred(frame, 160, 145, message, 1, COLOR_YELLOW); - Execute
draw_text_centred(frame, 160, 160, status != NULL ? status : "BOOT BACK HOLD BOOT HOME", 1, COLOR_DIMGRAY);
Read original function source
void drawBattleships(uint16_t *frame)
{
if (!running) return;
clear(frame, COLOR_BLACK);
char line[64];
const char *status = storage_message();
if (game_state == STATE_SHOW_SCORES || game_state == STATE_CHOOSE_MODE || game_state == STATE_CHOOSE_PLACEMENT) {
draw_text_centred(frame, 160, 8, game_state == STATE_CHOOSE_PLACEMENT ? "PLACE FLEET" : "BATTLESHIPS", 3, COLOR_CYAN);
if (game_state == STATE_SHOW_SCORES) {
snprintf(line, sizeof(line), "WINS %u", (unsigned)scores.wins); draw_text_centred(frame, 160, 52, line, 2, COLOR_LIME);
snprintf(line, sizeof(line), "LOSSES %u", (unsigned)scores.losses); draw_text_centred(frame, 160, 77, line, 2, COLOR_RED);
snprintf(line, sizeof(line), "STREAK %u", (unsigned)scores.streak); draw_text_centred(frame, 160, 102, line, 2, COLOR_GOLD);
} else if (game_state == STATE_CHOOSE_MODE) draw_mode_choice(frame, auto_mode);
else draw_choice(frame, "MANUAL", "RANDOM", random_placement);
draw_text_centred(frame, 160, 133, fleet_error ? message : status != NULL ? status : game_scores_status_text(score_status), 1, COLOR_YELLOW);
draw_text_centred(frame, 160, 157, "C CHOOSE BOOT BACK", 1, COLOR_DIMGRAY); return;
}
if (game_state == STATE_PLACING_POSITION || game_state == STATE_PLACING_ORIENTATION) {
snprintf(line, sizeof(line), "SHIP %u", (unsigned)player_board.ships[placing_ship].length); draw_text(frame, 4, 4, line, 1, COLOR_CYAN);
draw_grid(frame, &player_board, LEFT_BOARD_X, false, true);
snprintf(line, sizeof(line), "%c%u", 'A' + selected_column, (unsigned)selected_row + 1U); draw_text_centred(frame, 220, 27, line, 3, COLOR_YELLOW);
draw_text_centred(frame, 187, 66, "H", 3, horizontal ? COLOR_YELLOW : COLOR_DIMGRAY); draw_text_centred(frame, 253, 66, "V", 3, horizontal ? COLOR_DIMGRAY : COLOR_YELLOW);
draw_text_centred(frame, 220, 101, game_state == STATE_PLACING_ORIENTATION ? "D/B/STICK H/V" : "D/B/STICK CELL", 1, COLOR_WHITE);
draw_text_centred(frame, 220, 118, game_state == STATE_PLACING_ORIENTATION ? "A/K PLACE" : "A/K SELECT", 1, COLOR_WHITE);
draw_text_centred(frame, 160, 145, message, 1, COLOR_YELLOW); draw_text_centred(frame, 160, 160, status != NULL ? status : "C CHOOSE BOOT BACK", 1, COLOR_DIMGRAY); return;
}
draw_text_centred(frame, 62, 5, auto_mode ? "LEFT" : "YOU", 1, COLOR_CYAN); draw_text_centred(frame, 258, 5, auto_mode ? "RIGHT" : "ESP", 1, COLOR_CYAN);
draw_grid(frame, &player_board, LEFT_BOARD_X, false, false); draw_grid(frame, &enemy_board, RIGHT_BOARD_X, true, game_state == STATE_PLAYING && player_turn && !auto_mode);
drawGameGold(frame, 126, 2, 68, true);
draw_text_centred(frame, 160, 38, auto_mode ? (player_turn ? "LEFT" : "RIGHT") : (player_turn ? "YOUR" : "ESP"), 1, COLOR_WHITE);
draw_text_centred(frame, 160, 49, game_state == STATE_FINISHED ? "FINISHED" : "TURN", 1, COLOR_WHITE);
snprintf(line, sizeof(line), "%c%u", 'A' + selected_column, (unsigned)selected_row + 1U); draw_text_centred(frame, 160, 66, line, 2, COLOR_CYAN);
if (auto_mode) {
draw_text_centred(frame, 160, 89, aiSpeedShortText(ai_speed), 1, COLOR_WHITE); draw_text_centred(frame, 160, 102, "E SPEED", 1, COLOR_WHITE); draw_text_centred(frame, 160, 115, "C STOP", 1, COLOR_WHITE);
if (game_state == STATE_FINISHED) { const uint32_t delay = aiRestartDelay(), elapsed = now - state_started; snprintf(line, sizeof(line), "NEXT %u", elapsed >= delay ? 0U : (unsigned)((delay - elapsed + 999U) / 1000U)); draw_text_centred(frame, 160, 130, line, 1, COLOR_CYAN); }
} else {
draw_text_centred(frame, 160, 89, "D/B/STICK", 1, COLOR_WHITE); draw_text_centred(frame, 160, 102, "PREV/NEXT", 1, COLOR_WHITE);
draw_text_centred(frame, 160, 115, game_state == STATE_FINISHED ? "A/K NEW" : "A/K FIRE", 1, COLOR_WHITE); draw_text_centred(frame, 160, 130, "C CHOOSE", 1, COLOR_WHITE);
}
draw_text_centred(frame, 160, 145, message, 1, COLOR_YELLOW); draw_text_centred(frame, 160, 160, status != NULL ? status : "BOOT BACK HOLD BOOT HOME", 1, COLOR_DIMGRAY);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "battleships.h"
#include "audio.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "game_scores.h"
#include "gold.h"
#include "ai_speed.h"
#include "esp_random.h"
#include "esp_log.h"
#include <stdio.h>
#define SCORE_DISPLAY_MS 2000U
#define BATTLESHIPS_WIN_GOLD 10U
#define GRID_SIZE 10
#define SHIP_COUNT 11
#define NO_SHIP 0xffU
#define CELL_SIZE 12
#define LEFT_BOARD_X 2
#define RIGHT_BOARD_X 198
#define BOARD_Y 20
static const uint8_t ship_lengths[SHIP_COUNT] = {2,2,2,3,3,3,4,4,5,5,7};
typedef enum { SHOT_UNKNOWN, SHOT_MISS, SHOT_HIT } shot_state_t;
typedef enum { RESULT_INVALID, RESULT_MISS, RESULT_HIT, RESULT_SUNK } shot_result_t;
typedef enum { STATE_SHOW_SCORES, STATE_CHOOSE_MODE, STATE_CHOOSE_PLACEMENT,
STATE_PLACING_POSITION, STATE_PLACING_ORIENTATION, STATE_PLAYING,
STATE_FINISHED } game_state_t;
typedef struct { uint8_t ship_index; shot_state_t shot; } cell_t;
typedef struct { uint8_t length, hits; bool placed; } ship_t;
typedef struct { cell_t cells[GRID_SIZE][GRID_SIZE]; ship_t ships[SHIP_COUNT]; } board_t;
typedef struct { uint8_t rows[100], columns[100], count; } ai_state_t;
static board_t player_board, enemy_board;
static ai_state_t player_ai, enemy_ai;
static game_scores_t scores;
static game_score_result_t score_status;
static game_state_t game_state;
static uint8_t selected_row, selected_column, placing_ship, ai_speed;
static uint32_t now, state_started, ai_due, last_move;
static bool running, auto_mode, random_placement, horizontal, player_turn;
static bool stick_centred, repeating, score_save_failed, gold_save_failed, fleet_error;
static int held_direction;
static char message[48];
static bool inside(int row, int column) { return row >= 0 && row < GRID_SIZE && column >= 0 && column < GRID_SIZE; }
static void say(const char *text) { snprintf(message, sizeof(message), "%s", text); }
static void reset_navigation(void) { stick_centred = false; held_direction = 0; repeating = false; }
static void clear_board(board_t *board)
{
for (int row = 0; row < GRID_SIZE; ++row)
for (int column = 0; column < GRID_SIZE; ++column)
board->cells[row][column] = (cell_t){NO_SHIP, SHOT_UNKNOWN};
for (int ship = 0; ship < SHIP_COUNT; ++ship)
board->ships[ship] = (ship_t){ship_lengths[ship], 0, false};
}
static void clear_game(void)
{
clear_board(&player_board); clear_board(&enemy_board);
player_ai.count = enemy_ai.count = 0;
selected_row = selected_column = 0;
placing_ship = SHIP_COUNT - 1; horizontal = true; player_turn = true;
reset_navigation();
}
static void choose_mode(void)
{
game_state = STATE_CHOOSE_MODE; auto_mode = false; random_placement = false;
fleet_error = false; say(""); reset_navigation();
}
static bool can_place(const board_t *board, int row, int column, uint8_t length, bool is_horizontal)
{
const int end_row = row + (is_horizontal ? 0 : length - 1);
const int end_column = column + (is_horizontal ? length - 1 : 0);
if (!inside(row, column) || !inside(end_row, end_column)) return false;
for (int part = 0; part < length; ++part) {
const int ship_row = row + (is_horizontal ? 0 : part);
const int ship_column = column + (is_horizontal ? part : 0);
for (int row_step = -1; row_step <= 1; ++row_step)
for (int column_step = -1; column_step <= 1; ++column_step) {
const int near_row = ship_row + row_step, near_column = ship_column + column_step;
if (inside(near_row, near_column) && board->cells[near_row][near_column].ship_index != NO_SHIP) return false;
}
}
return true;
}
static void place_ship(board_t *board, uint8_t ship_index, uint8_t row, uint8_t column, bool is_horizontal)
{
ship_t *ship = &board->ships[ship_index];
for (int part = 0; part < ship->length; ++part)
board->cells[row + (is_horizontal ? 0 : part)][column + (is_horizontal ? part : 0)].ship_index = ship_index;
ship->placed = true; ship->hits = 0;
}
static bool place_random_fleet(board_t *board)
{
for (int fleet_try = 0; fleet_try < 100; ++fleet_try) {
clear_board(board); bool complete = true;
for (int ship = SHIP_COUNT - 1; ship >= 0; --ship) {
bool placed = false;
for (int try = 0; try < 250; ++try) {
const bool is_horizontal = esp_random() % 2U == 0;
const uint8_t row = esp_random() % GRID_SIZE, column = esp_random() % GRID_SIZE;
if (can_place(board, row, column, board->ships[ship].length, is_horizontal)) {
place_ship(board, ship, row, column, is_horizontal); placed = true; break;
}
}
if (!placed) { complete = false; break; }
}
if (complete) return true;
}
return false;
}
static bool all_sunk(const board_t *board)
{
for (int ship = 0; ship < SHIP_COUNT; ++ship)
if (!board->ships[ship].placed || board->ships[ship].hits < board->ships[ship].length) return false;
return true;
}
static shot_result_t fire_at(board_t *board, uint8_t row, uint8_t column)
{
cell_t *cell = &board->cells[row][column];
if (cell->shot != SHOT_UNKNOWN) return RESULT_INVALID;
if (cell->ship_index == NO_SHIP) { cell->shot = SHOT_MISS; return RESULT_MISS; }
cell->shot = SHOT_HIT;
ship_t *ship = &board->ships[cell->ship_index];
++ship->hits;
return ship->hits >= ship->length ? RESULT_SUNK : RESULT_HIT;
}
static void move_cell(int direction, bool unknown_only)
{
int position = selected_row * GRID_SIZE + selected_column;
for (int count = 0; count < 100; ++count) {
position = (position + direction + 100) % 100;
const int row = position / GRID_SIZE, column = position % GRID_SIZE;
if (!unknown_only || enemy_board.cells[row][column].shot == SHOT_UNKNOWN) {
selected_row = row; selected_column = column; return;
}
}
}
static bool queued(const ai_state_t *ai, uint8_t row, uint8_t column)
{
for (int index = 0; index < ai->count; ++index)
if (ai->rows[index] == row && ai->columns[index] == column) return true;
return false;
}
static void queue_target(ai_state_t *ai, const board_t *board, int row, int column)
{
if (!inside(row, column) || board->cells[row][column].shot != SHOT_UNKNOWN || queued(ai, row, column) || ai->count >= 100) return;
ai->rows[ai->count] = row; ai->columns[ai->count] = column; ++ai->count;
}
static void remember_hit(ai_state_t *ai, const board_t *board, uint8_t row, uint8_t column, shot_result_t result)
{
if (result == RESULT_SUNK) { ai->count = 0; return; }
if (result != RESULT_HIT) return;
queue_target(ai, board, row - 1, column); queue_target(ai, board, row + 1, column);
queue_target(ai, board, row, column - 1); queue_target(ai, board, row, column + 1);
}
static bool choose_ai_target(ai_state_t *ai, const board_t *board, uint8_t *row, uint8_t *column)
{
while (ai->count > 0) {
--ai->count; *row = ai->rows[ai->count]; *column = ai->columns[ai->count];
if (board->cells[*row][*column].shot == SHOT_UNKNOWN) return true;
}
unsigned available = 0;
for (int r = 0; r < GRID_SIZE; ++r) for (int c = 0; c < GRID_SIZE; ++c)
if (board->cells[r][c].shot == SHOT_UNKNOWN) ++available;
if (available == 0) return false;
unsigned selection = esp_random() % available;
for (int r = 0; r < GRID_SIZE; ++r) for (int c = 0; c < GRID_SIZE; ++c)
if (board->cells[r][c].shot == SHOT_UNKNOWN && selection-- == 0) { *row = r; *column = c; return true; }
return false;
}
static void increment(uint32_t *value) { if (*value < UINT32_MAX) ++*value; }
static void finish_game(bool player_won)
{
game_state = STATE_FINISHED; state_started = now; reset_navigation();
if (auto_mode) { say(player_won ? "LEFT WINS" : "RIGHT WINS"); return; }
if (player_won) { increment(&scores.wins); increment(&scores.streak); if (!addUserGoldWon(BATTLESHIPS_WIN_GOLD)) gold_save_failed = true; say("YOU WIN"); }
else { increment(&scores.losses); scores.streak = 0; say("ESP WINS"); }
if (score_status == SCORES_OK || score_status == SCORES_NOT_FOUND) {
score_save_failed = !game_scores_save(GAME_BATTLESHIPS, &scores);
if (!score_save_failed) score_status = SCORES_OK;
} else score_save_failed = true;
}
static void finish_shot(const board_t *target, shot_result_t result, uint8_t row, uint8_t column, bool attacker_is_player)
{
audioPlay(result == RESULT_MISS ? AUDIO_SFX_BOARD_PLACE : AUDIO_SFX_BOARD_CAPTURE);
const char *name = auto_mode ? (attacker_is_player ? "LEFT" : "RIGHT") : (attacker_is_player ? "YOU" : "ESP");
const char *result_text = result == RESULT_SUNK ? "SUNK" : result == RESULT_HIT ? "HIT" : "MISS";
snprintf(message, sizeof(message), "%s %s %c%u", name, result_text, 'A' + column, (unsigned)row + 1U);
if (all_sunk(target)) { finish_game(attacker_is_player); return; }
player_turn = !attacker_is_player; ai_due = now + aiSpeedDelay(ai_speed); reset_navigation();
if (player_turn && !auto_mode && enemy_board.cells[selected_row][selected_column].shot != SHOT_UNKNOWN) move_cell(1, true);
}
static void perform_ai_shot(board_t *target, ai_state_t *ai, bool attacker_is_player)
{
uint8_t row, column;
if (!choose_ai_target(ai, target, &row, &column)) return;
const shot_result_t result = fire_at(target, row, column);
remember_hit(ai, target, row, column, result); selected_row = row; selected_column = column;
finish_shot(target, result, row, column, attacker_is_player);
}
static void start_combat(void)
{
player_ai.count = enemy_ai.count = 0; selected_row = selected_column = 0; player_turn = true;
game_state = STATE_PLAYING; fleet_error = false; say(auto_mode ? "AUTO BATTLE" : "YOUR SHOT");
ai_due = now + aiSpeedDelay(ai_speed); reset_navigation();
}
static void fleet_error_state(void)
{
choose_mode(); fleet_error = true; say("FLEET ERROR - RETRY");
ESP_LOGE("battleships", "Fleet placement failed");
}
static void prepare_random_game(void)
{
clear_game();
if (!place_random_fleet(&player_board) || !place_random_fleet(&enemy_board)) { fleet_error_state(); return; }
start_combat();
}
static void start_manual_placement(void)
{
clear_game(); game_state = STATE_PLACING_POSITION; say("SELECT START");
}
static void confirm_placement(void)
{
const uint8_t length = player_board.ships[placing_ship].length;
if (!can_place(&player_board, selected_row, selected_column, length, horizontal)) {
game_state = STATE_PLACING_POSITION; say("NO FIT"); reset_navigation(); return;
}
place_ship(&player_board, placing_ship, selected_row, selected_column, horizontal);
audioPlay(AUDIO_SFX_BOARD_PLACE);
if (placing_ship == 0) {
if (!place_random_fleet(&enemy_board)) { fleet_error_state(); return; }
start_combat(); return;
}
--placing_ship; game_state = STATE_PLACING_POSITION; say("SELECT START"); reset_navigation();
}
void startBattleships(uint32_t now_ms)
{
now = state_started = now_ms; running = true; score_save_failed = gold_save_failed = false;
scores = (game_scores_t){0}; score_status = game_scores_load(GAME_BATTLESHIPS, &scores);
ai_speed = AI_SPEED_DEFAULT_INDEX; clear_game(); choose_mode(); game_state = STATE_SHOW_SCORES;
ESP_LOGI("battleships", "START: %s", game_scores_status_text(score_status));
}
void stopBattleships(void) { running = false; ESP_LOGI("battleships", "STOP"); }
void battleshipsAction(bool select, bool speed, bool choose, uint32_t now_ms)
{
if (!running) return;
now = now_ms;
if (choose) { choose_mode(); return; }
if (game_state == STATE_SHOW_SCORES) { if (select) choose_mode(); return; }
if (game_state == STATE_CHOOSE_MODE) {
if (select) { fleet_error = false; if (auto_mode) prepare_random_game(); else { random_placement = false; game_state = STATE_CHOOSE_PLACEMENT; reset_navigation(); } }
return;
}
if (game_state == STATE_CHOOSE_PLACEMENT) { if (select) { if (random_placement) prepare_random_game(); else start_manual_placement(); } return; }
if (auto_mode) { if (speed) { cycleAISpeed(&ai_speed); ai_due = now + aiSpeedDelay(ai_speed); } return; }
if (!select) return;
if (game_state == STATE_FINISHED) choose_mode();
else if (game_state == STATE_PLACING_POSITION) { game_state = STATE_PLACING_ORIENTATION; say("CHOOSE H/V"); reset_navigation(); }
else if (game_state == STATE_PLACING_ORIENTATION) confirm_placement();
else if (game_state == STATE_PLAYING && player_turn) {
const shot_result_t result = fire_at(&enemy_board, selected_row, selected_column);
if (result != RESULT_INVALID) finish_shot(&enemy_board, result, selected_row, selected_column, true);
}
}
void updateBattleships(int x_offset, int y_offset, bool valid, bool left, bool right, uint32_t now_ms)
{
if (!running) return;
now = now_ms;
if (game_state == STATE_SHOW_SCORES) { if (now - state_started >= SCORE_DISPLAY_MS) choose_mode(); return; }
if (game_state == STATE_FINISHED) { if (auto_mode && now - state_started >= aiRestartDelay()) prepare_random_game(); return; }
if (game_state == STATE_PLAYING && (auto_mode || !player_turn)) {
if ((int32_t)(now - ai_due) >= 0) { if (player_turn) perform_ai_shot(&enemy_board, &player_ai, true); else perform_ai_shot(&player_board, &enemy_ai, false); }
return;
}
if (!valid) stick_centred = false;
const int ax = x_offset < 0 ? -x_offset : x_offset, ay = y_offset < 0 ? -y_offset : y_offset;
if (valid && ax <= 350 && ay <= 350) stick_centred = true;
int direction = 0;
if (left || right) { direction = (int)right - (int)left; if (direction == 0) { held_direction = 0; repeating = false; return; } }
else {
if (!valid || (ax <= 350 && ay <= 350)) { held_direction = 0; repeating = false; return; }
if (!stick_centred) return;
if (ax >= ay && ax >= 600) direction = x_offset > 0 ? 1 : -1;
else if (ay >= 600) direction = y_offset < 0 ? 1 : -1;
}
if (direction == 0 || (held_direction == direction && now - last_move < (repeating ? 150U : 400U))) return;
repeating = held_direction == direction; held_direction = direction; last_move = now;
if (game_state == STATE_CHOOSE_MODE) auto_mode = !auto_mode;
else if (game_state == STATE_CHOOSE_PLACEMENT) random_placement = !random_placement;
else if (game_state == STATE_PLACING_POSITION) move_cell(direction, false);
else if (game_state == STATE_PLACING_ORIENTATION) horizontal = !horizontal;
else if (game_state == STATE_PLAYING) move_cell(direction, true);
}
static const char *storage_message(void)
{
if (gold_save_failed || !goldStorageHealthy()) return "GOLD SAVE ERROR";
if (score_save_failed) return "SCORE SAVE ERROR";
if (score_status != SCORES_OK && score_status != SCORES_NOT_FOUND) return game_scores_status_text(score_status);
if (!aiTimingStorageHealthy()) return "AI TIMING ERROR";
return NULL;
}
static void draw_grid(uint16_t *frame, const board_t *board, int board_x, bool hide_ships, bool selected)
{
for (int row = 0; row < GRID_SIZE; ++row) for (int column = 0; column < GRID_SIZE; ++column) {
const cell_t *cell = &board->cells[row][column]; const int x = board_x + column * CELL_SIZE, y = BOARD_Y + row * CELL_SIZE;
uint16_t color = COLOR_BLACK;
if (!hide_ships && cell->ship_index != NO_SHIP) color = COLOR_BLUE;
if (cell->shot == SHOT_MISS) color = COLOR_DIMGRAY; else if (cell->shot == SHOT_HIT) color = COLOR_RED;
fill_rect(frame, x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2, color);
if (cell->shot == SHOT_MISS) fill_circle(frame, x + 6, y + 6, 2, COLOR_CYAN);
else if (cell->shot == SHOT_HIT) { draw_line(frame, x + 3, y + 3, x + 8, y + 8, COLOR_YELLOW); draw_line(frame, x + 8, y + 3, x + 3, y + 8, COLOR_YELLOW); }
draw_rect(frame, x, y, CELL_SIZE, CELL_SIZE, selected && row == selected_row && column == selected_column ? COLOR_YELLOW : COLOR_LIGHTGRAY);
}
}
static void draw_choice(uint16_t *frame, const char *left_text, const char *right_text, bool right_selected)
{
draw_rect(frame, 15, 55, 135, 40, right_selected ? COLOR_DIMGRAY : COLOR_YELLOW);
draw_rect(frame, 170, 55, 135, 40, right_selected ? COLOR_YELLOW : COLOR_DIMGRAY);
drawPet2001TextCentredTransparent(frame, 82, 68, left_text, COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, 237, 68, right_text, COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, 160, 111, "D/B/STICK CHOOSE A/K SELECT", COLOR_WHITE, 1);
}
static void draw_mode_choice(uint16_t *frame, bool auto_selected)
{
drawPet2001TextCentredTransparent(frame, 82, 68, "PLAY",
auto_selected ? COLOR_LIGHTGRAY : COLOR_YELLOW, 2);
drawPet2001TextCentredTransparent(frame, 237, 68, "AUTO",
auto_selected ? COLOR_YELLOW : COLOR_LIGHTGRAY, 2);
drawPet2001TextCentredTransparent(frame, 160, 111, "D/B/STICK CHOOSE A/K SELECT", COLOR_WHITE, 1);
}
void drawBattleships(uint16_t *frame)
{
if (!running) return;
clear(frame, COLOR_BLACK);
char line[64];
const char *status = storage_message();
if (game_state == STATE_SHOW_SCORES || game_state == STATE_CHOOSE_MODE || game_state == STATE_CHOOSE_PLACEMENT) {
drawPet2001TextCentredTransparent(frame, 160, 8,
game_state == STATE_CHOOSE_PLACEMENT ? "PLACE FLEET" : "BATTLESHIPS",
COLOR_CYAN, 3);
if (game_state == STATE_SHOW_SCORES) {
snprintf(line, sizeof(line), "WINS %u", (unsigned)scores.wins);
drawPet2001TextCentredTransparent(frame, 160, 52, line, COLOR_LIME, 2);
snprintf(line, sizeof(line), "LOSSES %u", (unsigned)scores.losses);
drawPet2001TextCentredTransparent(frame, 160, 77, line, COLOR_RED, 2);
snprintf(line, sizeof(line), "STREAK %u", (unsigned)scores.streak);
drawPet2001TextCentredTransparent(frame, 160, 102, line, COLOR_GOLD, 2);
} else if (game_state == STATE_CHOOSE_MODE) draw_mode_choice(frame, auto_mode);
else draw_choice(frame, "MANUAL", "RANDOM", random_placement);
drawPet2001TextCentredTransparent(frame, 160, 133,
fleet_error ? message : status != NULL ? status : game_scores_status_text(score_status),
COLOR_YELLOW, 1);
drawPet2001TextCentredTransparent(frame, 160, 157, "C CHOOSE BOOT BACK", COLOR_DIMGRAY, 1); return;
}
if (game_state == STATE_PLACING_POSITION || game_state == STATE_PLACING_ORIENTATION) {
snprintf(line, sizeof(line), "SHIP %u", (unsigned)player_board.ships[placing_ship].length);
drawPet2001TextTransparent(frame, 4, 4, line, COLOR_CYAN, 1);
draw_grid(frame, &player_board, LEFT_BOARD_X, false, true);
snprintf(line, sizeof(line), "%c%u", 'A' + selected_column, (unsigned)selected_row + 1U);
drawPet2001TextCentredTransparent(frame, 220, 27, line, COLOR_YELLOW, 3);
drawPet2001TextCentredTransparent(frame, 187, 66, "H", horizontal ? COLOR_YELLOW : COLOR_DIMGRAY, 3);
drawPet2001TextCentredTransparent(frame, 253, 66, "V", horizontal ? COLOR_DIMGRAY : COLOR_YELLOW, 3);
drawPet2001TextCentredTransparent(frame, 220, 101,
game_state == STATE_PLACING_ORIENTATION ? "D/B/STICK H/V" : "D/B/STICK CELL",
COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 220, 118,
game_state == STATE_PLACING_ORIENTATION ? "A/K PLACE" : "A/K SELECT",
COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 145, message, COLOR_YELLOW, 1);
drawPet2001TextCentredTransparent(frame, 160, 160,
status != NULL ? status : "C CHOOSE BOOT BACK", COLOR_DIMGRAY, 1); return;
}
drawPet2001TextCentredTransparent(frame, 62, 5, auto_mode ? "LEFT" : "YOU", COLOR_CYAN, 1);
drawPet2001TextCentredTransparent(frame, 258, 5, auto_mode ? "RIGHT" : "ESP", COLOR_CYAN, 1);
draw_grid(frame, &player_board, LEFT_BOARD_X, false, false); draw_grid(frame, &enemy_board, RIGHT_BOARD_X, true, game_state == STATE_PLAYING && player_turn && !auto_mode);
drawGameGold(frame, 126, 2, 68, true);
drawPet2001TextCentredTransparent(frame, 160, 38,
auto_mode ? (player_turn ? "LEFT" : "RIGHT") : (player_turn ? "YOUR" : "ESP"),
COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 49, game_state == STATE_FINISHED ? "FINISHED" : "TURN", COLOR_WHITE, 1);
snprintf(line, sizeof(line), "%c%u", 'A' + selected_column, (unsigned)selected_row + 1U);
drawPet2001TextCentredTransparent(frame, 160, 66, line, COLOR_CYAN, 2);
if (auto_mode) {
drawPet2001TextCentredTransparent(frame, 160, 89, aiSpeedShortText(ai_speed), COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 102, "E SPEED", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 115, "C STOP", COLOR_WHITE, 1);
if (game_state == STATE_FINISHED) { const uint32_t delay = aiRestartDelay(), elapsed = now - state_started; snprintf(line, sizeof(line), "NEXT %u", elapsed >= delay ? 0U : (unsigned)((delay - elapsed + 999U) / 1000U)); drawPet2001TextCentredTransparent(frame, 160, 130, line, COLOR_CYAN, 1); }
} else {
drawPet2001TextCentredTransparent(frame, 160, 89, "D/B/STICK", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 102, "PREV/NEXT", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 115, game_state == STATE_FINISHED ? "A/K NEW" : "A/K FIRE", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 130, "C CHOOSE", COLOR_WHITE, 1);
}
drawPet2001TextCentredTransparent(frame, 160, 145, message, COLOR_YELLOW, 1);
drawPet2001TextCentredTransparent(frame, 160, 160,
status != NULL ? status : "BOOT BACK HOLD BOOT HOME", COLOR_DIMGRAY, 1);
}