Connect Four

Source: src/connect_four.c · 270 lines · 20 functions.

Screen captures

Views of this part running on the Mini Arcade.

Connect 4 game board on the Mini Arcade
Connect 4 showing the board during play.
Connect 4 at a later game state on the Mini Arcade
A second Connect 4 state with more pieces in play.

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

other() · source line 30
static uint8_t other(uint8_t piece)
  1. Exit this functionreturn piece == RED_PIECE ? YELLOW_PIECE : RED_PIECE;
Read original function source
static uint8_t other(uint8_t piece) { return piece == RED_PIECE ? YELLOW_PIECE : RED_PIECE; }
piece_colour() · source line 31
static uint16_t piece_colour(uint8_t piece)
  1. Exit this functionreturn piece == RED_PIECE ? COLOR_RED : COLOR_YELLOW;
Read original function source
static uint16_t piece_colour(uint8_t piece) { return piece == RED_PIECE ? COLOR_RED : COLOR_YELLOW; }
available() · source line 32
static bool available(uint8_t column)
  1. Exit this functionreturn column < COLUMNS && board[0][column] == EMPTY;
Read original function source
static bool available(uint8_t column) { return column < COLUMNS && board[0][column] == EMPTY; }
drop() · source line 34
static int drop(uint8_t column, uint8_t piece)

Calls into this documentation:

  1. Decision!available(column)
    True
    1. Exit this functionreturn -1;
    False

    Continue after this decision.

  2. FOR loopint row = ROWS - 1; row >= 0; --row

    Repeat body while the loop condition permits; then continue below.

    1. Decisionboard[row][column] == EMPTY
      True
        1. Executeboard[row][column] = piece;
        2. Exit this functionreturn row;
      False

      Continue after this decision.

  3. Exit this functionreturn -1;
Read original function source
static int drop(uint8_t column, uint8_t piece)
{
    if (!available(column)) return -1;
    for (int row = ROWS - 1; row >= 0; --row) if (board[row][column] == EMPTY) {
        board[row][column] = piece;
        return row;
    }
    return -1;
}
line_count() · source line 44
static unsigned line_count(int row, int column, int dr, int dc, uint8_t piece)
  1. Executeunsigned count = 0;
  2. FOR looprow += dr, column += dc; row >= 0 && row < ROWS && column >= 0 && column < COLUMNS && board[row][column] == piece; row += dr, column += dc

    Repeat body while the loop condition permits; then continue below.

    1. Execute++count;
  3. Exit this functionreturn count;
Read original function source
static unsigned line_count(int row, int column, int dr, int dc, uint8_t piece)
{
    unsigned count = 0;
    for (row += dr, column += dc; row >= 0 && row < ROWS && column >= 0 && column < COLUMNS &&
         board[row][column] == piece; row += dr, column += dc) ++count;
    return count;
}
wins() · source line 52
static bool wins(int row, int column, uint8_t piece)

Calls into this documentation:

  1. Executestatic const int directions[4][2] = {{0, 1}, {1, 0}, {1, 1}, {1, -1}};
  2. FOR loopunsigned i = 0; i < 4; ++i

    Repeat body while the loop condition permits; then continue below.

      1. Decision1U + line_count(row, column, directions[i][0], directions[i][1], piece) + line_count(row, column, -directions[i][0], -directions[i][1], piece) >= 4U
        True
        1. Exit this functionreturn true;
        False

        Continue after this decision.

  3. Exit this functionreturn false;
Read original function source
static bool wins(int row, int column, uint8_t piece)
{
    static const int directions[4][2] = {{0, 1}, {1, 0}, {1, 1}, {1, -1}};
    for (unsigned i = 0; i < 4; ++i) {
        if (1U + line_count(row, column, directions[i][0], directions[i][1], piece) +
            line_count(row, column, -directions[i][0], -directions[i][1], piece) >= 4U) return true;
    }
    return false;
}
move_wins() · source line 62
static bool move_wins(uint8_t column, uint8_t piece)

Calls into this documentation:

  1. Executeconst int row = drop(column, piece);
  2. Decisionrow < 0
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  3. Executeconst bool result = wins(row, column, piece);
  4. Executeboard[row][column] = EMPTY;
  5. Exit this functionreturn result;
Read original function source
static bool move_wins(uint8_t column, uint8_t piece)
{
    const int row = drop(column, piece);
    if (row < 0) return false;
    const bool result = wins(row, column, piece);
    board[row][column] = EMPTY;
    return result;
}
full() · source line 71
static bool full(void)

Calls into this documentation:

  1. FOR loopuint8_t column = 0; column < COLUMNS; ++column

    Repeat body while the loop condition permits; then continue below.

    1. Decisionavailable(column)
      True
      1. Exit this functionreturn false;
      False

      Continue after this decision.

  2. Exit this functionreturn true;
Read original function source
static bool full(void)
{
    for (uint8_t column = 0; column < COLUMNS; ++column) if (available(column)) return false;
    return true;
}
first_available() · source line 77
static uint8_t first_available(void)

Calls into this documentation:

  1. Executestatic const uint8_t order[COLUMNS] = {3, 2, 4, 1, 5, 0, 6};
  2. FOR loopunsigned i = 0; i < COLUMNS; ++i

    Repeat body while the loop condition permits; then continue below.

    1. Decisionavailable(order[i])
      True
      1. Exit this functionreturn order[i];
      False

      Continue after this decision.

  3. Exit this functionreturn 0;
Read original function source
static uint8_t first_available(void)
{
    static const uint8_t order[COLUMNS] = {3, 2, 4, 1, 5, 0, 6};
    for (unsigned i = 0; i < COLUMNS; ++i) if (available(order[i])) return order[i];
    return 0;
}
computer_column() · source line 84
static uint8_t computer_column(void)

Calls into this documentation:

  1. Executeuint8_t choices[COLUMNS], count = 0;
  2. FOR loopuint8_t column = 0; column < COLUMNS; ++column

    Repeat body while the loop condition permits; then continue below.

    1. Decisionavailable(column)
      True
      1. Executechoices[count++] = column;
      False

      Continue after this decision.

  3. FOR loopuint8_t i = 0; i < count; ++i

    Repeat body while the loop condition permits; then continue below.

    1. Decisionmove_wins(choices[i], current_piece)
      True
      1. Exit this functionreturn choices[i];
      False

      Continue after this decision.

  4. Executeconst uint8_t opponent = other(current_piece);
  5. FOR loopuint8_t i = 0; i < count; ++i

    Repeat body while the loop condition permits; then continue below.

    1. Decisionmove_wins(choices[i], opponent)
      True
      1. Exit this functionreturn choices[i];
      False

      Continue after this decision.

  6. Decision(esp_random() % 100U) < 20U
    True
    1. Exit this functionreturn choices[esp_random() % count];
    False

    Continue after this decision.

  7. Executeuint8_t best = choices[0];
  8. Executeint best_score = -10000;
  9. FOR loopuint8_t i = 0; i < count; ++i

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst uint8_t column = choices[i];
      2. Executeconst int row = drop(column, current_piece);
      3. Executeint score = (int)(esp_random() % 8U) + (3 - (column > 3 ? column - 3 : 3 - column)) * 5;
      4. FOR loopuint8_t reply = 0; reply < COLUMNS; ++reply

        Repeat body while the loop condition permits; then continue below.

        1. Decisionavailable(reply) && move_wins(reply, opponent)
          True
          1. Executescore -= 80;
          False

          Continue after this decision.

      5. Executescore += (int)line_count(row, column, 0, 1, current_piece) * 3;
      6. Executescore += (int)line_count(row, column, 0, -1, current_piece) * 3;
      7. Executescore += (int)line_count(row, column, 1, 0, current_piece) * 3;
      8. Executeboard[row][column] = EMPTY;
      9. Decisionscore > best_score
        True
          1. Executebest_score = score;
          2. Executebest = column;
        False

        Continue after this decision.

  10. Exit this functionreturn best;
Read original function source
static uint8_t computer_column(void)
{
    uint8_t choices[COLUMNS], count = 0;
    for (uint8_t column = 0; column < COLUMNS; ++column) if (available(column)) choices[count++] = column;
    for (uint8_t i = 0; i < count; ++i) if (move_wins(choices[i], current_piece)) return choices[i];
    const uint8_t opponent = other(current_piece);
    for (uint8_t i = 0; i < count; ++i) if (move_wins(choices[i], opponent)) return choices[i];
    if ((esp_random() % 100U) < 20U) return choices[esp_random() % count];
    uint8_t best = choices[0];
    int best_score = -10000;
    for (uint8_t i = 0; i < count; ++i) {
        const uint8_t column = choices[i];
        const int row = drop(column, current_piece);
        int score = (int)(esp_random() % 8U) + (3 - (column > 3 ? column - 3 : 3 - column)) * 5;
        for (uint8_t reply = 0; reply < COLUMNS; ++reply) if (available(reply) && move_wins(reply, opponent)) score -= 80;
        score += (int)line_count(row, column, 0, 1, current_piece) * 3;
        score += (int)line_count(row, column, 0, -1, current_piece) * 3;
        score += (int)line_count(row, column, 1, 0, current_piece) * 3;
        board[row][column] = EMPTY;
        if (score > best_score) { best_score = score; best = column; }
    }
    return best;
}
reset_board() · source line 108
static void reset_board(void)
  1. FOR loopunsigned row = 0; row < ROWS; ++row

    Repeat body while the loop condition permits; then continue below.

    1. FOR loopunsigned column = 0; column < COLUMNS; ++column

      Repeat body while the loop condition permits; then continue below.

      1. Executeboard[row][column] = EMPTY;
  2. Executecurrent_piece = RED_PIECE;
  3. Executeselected_column = 3;
Read original function source
static void reset_board(void)
{
    for (unsigned row = 0; row < ROWS; ++row) for (unsigned column = 0; column < COLUMNS; ++column) board[row][column] = EMPTY;
    current_piece = RED_PIECE;
    selected_column = 3;
}
finish() · source line 115
static void finish(uint8_t winner)

Calls into this documentation:

  1. Executestate = FINISHED;
  2. Executestate_started = now;
  3. Decisionauto_mode
    True
      1. Decisionwinner == gold_piece && !addAIGold(WIN_GOLD)
        True
        1. Executegold_save_failed = true;
        False

        Continue after this decision.

      2. Executemessage = winner == EMPTY ? "DRAW" : winner == RED_PIECE ? "RED WINS" : "YELLOW WINS";
      3. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisionwinner == human_piece
    True
      1. Execute++scores.wins;
      2. Execute++scores.streak;
      3. Decision!addUserGoldWon(WIN_GOLD)
        True
        1. Executegold_save_failed = true;
        False

        Continue after this decision.

      4. Executemessage = "YOU WIN";
    False
    1. Decisionwinner == EMPTY
      True
        1. Execute++scores.draws;
        2. Executescores.streak = 0;
        3. Executemessage = "DRAW";
      False
        1. Execute++scores.losses;
        2. Executescores.streak = 0;
        3. Executemessage = "CPU WINS";
  5. Executescore_save_failed = !game_scores_save(GAME_CONNECT_FOUR, &scores);
Read original function source
static void finish(uint8_t winner)
{
    state = FINISHED;
    state_started = now;
    if (auto_mode) {
        if (winner == gold_piece && !addAIGold(WIN_GOLD)) gold_save_failed = true;
        message = winner == EMPTY ? "DRAW" : winner == RED_PIECE ? "RED WINS" : "YELLOW WINS";
        return;
    }
    if (winner == human_piece) {
        ++scores.wins; ++scores.streak;
        if (!addUserGoldWon(WIN_GOLD)) gold_save_failed = true;
        message = "YOU WIN";
    } else if (winner == EMPTY) {
        ++scores.draws; scores.streak = 0; message = "DRAW";
    } else {
        ++scores.losses; scores.streak = 0; message = "CPU WINS";
    }
    score_save_failed = !game_scores_save(GAME_CONNECT_FOUR, &scores);
}
play() · source line 136
static void play(uint8_t column)

Calls into this documentation:

  1. Executeconst int row = drop(column, current_piece);
  2. Decisionrow < 0
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. ExecuteaudioPlay(AUDIO_SFX_BOARD_PLACE);
  4. Decisionwins(row, column, current_piece)
    True
      1. Executefinish(current_piece);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionfull()
    True
      1. Executefinish(EMPTY);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Executecurrent_piece = other(current_piece);
  7. Executeselected_column = first_available();
  8. Executemessage = auto_mode || current_piece != human_piece ? "CPU TURN" : "YOUR TURN";
  9. Executecomputer_due = now + aiSpeedDelay(ai_speed);
Read original function source
static void play(uint8_t column)
{
    const int row = drop(column, current_piece);
    if (row < 0) return;
    audioPlay(AUDIO_SFX_BOARD_PLACE);
    if (wins(row, column, current_piece)) { finish(current_piece); return; }
    if (full()) { finish(EMPTY); return; }
    current_piece = other(current_piece);
    selected_column = first_available();
    message = auto_mode || current_piece != human_piece ? "CPU TURN" : "YOUR TURN";
    computer_due = now + aiSpeedDelay(ai_speed);
}
begin_game() · source line 149
static void begin_game(void)

Calls into this documentation:

  1. Executereset_board();
  2. Executestate = PLAYING;
  3. Decisionauto_mode
    True
    1. Executegold_piece = (esp_random() & 1U) == 0U ? RED_PIECE : YELLOW_PIECE;
    False

    Continue after this decision.

  4. Executemessage = auto_mode || human_piece != RED_PIECE ? "CPU TURN" : "YOUR TURN";
  5. Executecomputer_due = now + aiSpeedDelay(ai_speed);
Read original function source
static void begin_game(void)
{
    reset_board();
    state = PLAYING;
    if (auto_mode) gold_piece = (esp_random() & 1U) == 0U ? RED_PIECE : YELLOW_PIECE;
    message = auto_mode || human_piece != RED_PIECE ? "CPU TURN" : "YOUR TURN";
    computer_due = now + aiSpeedDelay(ai_speed);
}
move_selection() · source line 158
static void move_selection(int direction)

Calls into this documentation:

  1. Decisionstate == CHOOSE_PIECE
    True
      1. Executeint choice = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1;
      2. Executechoice = (choice + direction + 3) % 3;
      3. Executeauto_mode = choice == 2;
      4. Executehuman_piece = choice == 1 ? YELLOW_PIECE : RED_PIECE;
      5. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionstate != PLAYING || auto_mode || current_piece != human_piece
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. FOR loopunsigned i = 1; i <= COLUMNS; ++i

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst int next = (int)selected_column + direction * (int)i;
      2. Executeconst uint8_t column = (uint8_t)((next % COLUMNS + COLUMNS) % COLUMNS);
      3. Decisionavailable(column)
        True
          1. Executeselected_column = column;
          2. Exit this functionreturn;
        False

        Continue after this decision.

Read original function source
static void move_selection(int direction)
{
    if (state == CHOOSE_PIECE) {
        int choice = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1;
        choice = (choice + direction + 3) % 3;
        auto_mode = choice == 2;
        human_piece = choice == 1 ? YELLOW_PIECE : RED_PIECE;
        return;
    }
    if (state != PLAYING || auto_mode || current_piece != human_piece) return;
    for (unsigned i = 1; i <= COLUMNS; ++i) {
        const int next = (int)selected_column + direction * (int)i;
        const uint8_t column = (uint8_t)((next % COLUMNS + COLUMNS) % COLUMNS);
        if (available(column)) { selected_column = column; return; }
    }
}
startConnectFour() · source line 175
void startConnectFour(uint32_t now_ms)

Calls into this documentation:

  1. Executenow = now_ms;
  2. Executerunning = true;
  3. Executeauto_mode = false;
  4. Executehuman_piece = RED_PIECE;
  5. Executeai_speed = AI_SPEED_DEFAULT_INDEX;
  6. Executescore_save_failed = gold_save_failed = false;
  7. Executescores = (game_scores_t){0};
  8. Executescore_status = game_scores_load(GAME_CONNECT_FOUR, &scores);
  9. Executereset_board();
  10. Executestate = SHOW_SCORES;
  11. Executestate_started = now_ms;
  12. Executemessage = "";
  13. Executestick_centred = false;
Read original function source
void startConnectFour(uint32_t now_ms)
{
    now = now_ms; running = true; auto_mode = false; human_piece = RED_PIECE; ai_speed = AI_SPEED_DEFAULT_INDEX;
    score_save_failed = gold_save_failed = false; scores = (game_scores_t){0};
    score_status = game_scores_load(GAME_CONNECT_FOUR, &scores);
    reset_board(); state = SHOW_SCORES; state_started = now_ms; message = ""; stick_centred = false;
}
stopConnectFour() · source line 182
void stopConnectFour(void)
  1. Executerunning = false;
Read original function source
void stopConnectFour(void) { running = false; }
connectFourAction() · source line 184
void connectFourAction(bool select, bool speed, bool choose, uint32_t now_ms)

Calls into this documentation:

  1. Executenow = now_ms;
  2. Decision!running || state == SHOW_SCORES
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionchoose
    True
      1. Executestate = CHOOSE_PIECE;
      2. Executeauto_mode = false;
      3. Executehuman_piece = RED_PIECE;
      4. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisionstate == CHOOSE_PIECE
    True
      1. Decisionselect
        True
        1. Executebegin_game();
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionauto_mode && speed
    True
      1. ExecutecycleAISpeed(&ai_speed);
      2. Executecomputer_due = now + aiSpeedDelay(ai_speed);
      3. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionstate == FINISHED && select
    True
      1. Executestate = CHOOSE_PIECE;
      2. Executeauto_mode = false;
      3. Executehuman_piece = RED_PIECE;
      4. Exit this functionreturn;
    False

    Continue after this decision.

  7. Decisionstate == PLAYING && !auto_mode && current_piece == human_piece && select
    True
    1. Executeplay(selected_column);
    False

    Continue after this decision.

Read original function source
void connectFourAction(bool select, bool speed, bool choose, uint32_t now_ms)
{
    now = now_ms;
    if (!running || state == SHOW_SCORES) return;
    if (choose) { state = CHOOSE_PIECE; auto_mode = false; human_piece = RED_PIECE; return; }
    if (state == CHOOSE_PIECE) { if (select) begin_game(); return; }
    if (auto_mode && speed) { cycleAISpeed(&ai_speed); computer_due = now + aiSpeedDelay(ai_speed); return; }
    if (state == FINISHED && select) { state = CHOOSE_PIECE; auto_mode = false; human_piece = RED_PIECE; return; }
    if (state == PLAYING && !auto_mode && current_piece == human_piece && select) play(selected_column);
}
updateConnectFour() · source line 195
void updateConnectFour(int x_offset, int y_offset, bool valid, bool left, bool right, uint32_t now_ms)

Calls into this documentation:

  1. Execute(void)y_offset;
  2. Executenow = now_ms;
  3. Decision!running
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisionstate == SHOW_SCORES && now - state_started >= SCORE_SHOW_MS
    True
      1. Executestate = CHOOSE_PIECE;
      2. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionstate == FINISHED && auto_mode
    True
      1. Decisionnow - state_started >= aiRestartDelay()
        True
        1. Executebegin_game();
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionstate == PLAYING && (auto_mode || current_piece != human_piece) && (int32_t)(now - computer_due) >= 0
    True
    1. Executeplay(computer_column());
    False

    Continue after this decision.

  7. Executeint direction = left == right ? 0 : (left ? -1 : 1);
  8. Decisiondirection != 0 && now - last_nav >= 170U
    True
      1. Executemove_selection(direction);
      2. Executelast_nav = now;
    False

    Continue after this decision.

  9. Decision!valid || (x_offset > -350 && x_offset < 350)
    True
    1. Executestick_centred = true;
    False

    Continue after this decision.

  10. Decisionvalid && stick_centred && (x_offset <= -650 || x_offset >= 650)
    True
      1. Executemove_selection(x_offset < 0 ? -1 : 1);
      2. Executestick_centred = false;
    False

    Continue after this decision.

Read original function source
void updateConnectFour(int x_offset, int y_offset, bool valid, bool left, bool right, uint32_t now_ms)
{
    (void)y_offset; now = now_ms;
    if (!running) return;
    if (state == SHOW_SCORES && now - state_started >= SCORE_SHOW_MS) { state = CHOOSE_PIECE; return; }
    if (state == FINISHED && auto_mode) {
        if (now - state_started >= aiRestartDelay()) begin_game();
        return;
    }
    if (state == PLAYING && (auto_mode || current_piece != human_piece) && (int32_t)(now - computer_due) >= 0) play(computer_column());
    int direction = left == right ? 0 : (left ? -1 : 1);
    if (direction != 0 && now - last_nav >= 170U) { move_selection(direction); last_nav = now; }
    if (!valid || (x_offset > -350 && x_offset < 350)) stick_centred = true;
    if (valid && stick_centred && (x_offset <= -650 || x_offset >= 650)) {
        move_selection(x_offset < 0 ? -1 : 1); stick_centred = false;
    }
}
drawConnectFour() · source line 213
void drawConnectFour(uint16_t *frame)

Calls into this documentation:

  1. Decision!running || frame == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeclear(frame, COLOR_BLACK);
  3. Executechar text[48];
  4. Decisionstate == SHOW_SCORES
    True
      1. Executedraw_text_centred(frame, 160, 8, "CONNECT FOUR", 3, COLOR_CYAN);
      2. Executesnprintf(text, sizeof(text), "WINS %u", (unsigned)scores.wins);
      3. Executedraw_text(frame, 35, 58, text, 1, COLOR_GREEN);
      4. Executesnprintf(text, sizeof(text), "LOSSES %u", (unsigned)scores.losses);
      5. Executedraw_text(frame, 170, 58, text, 1, COLOR_RED);
      6. Executesnprintf(text, sizeof(text), "DRAWS %u", (unsigned)scores.draws);
      7. Executedraw_text(frame, 35, 92, text, 1, COLOR_YELLOW);
      8. Executesnprintf(text, sizeof(text), "STREAK %u", (unsigned)scores.streak);
      9. Executedraw_text(frame, 170, 92, text, 1, COLOR_ORANGE);
      10. Executedraw_text_centred(frame, 160, 145, game_scores_status_text(score_status), 1, COLOR_LIGHTGRAY);
      11. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionstate == CHOOSE_PIECE
    True
      1. Executeconst int selected = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1;
      2. Executedraw_text_centred(frame, 160, 8, "CONNECT FOUR", 3, COLOR_CYAN);
      3. Executefill_circle(frame, 75, 76, 20, COLOR_RED);
      4. Executedraw_ring(frame, 75, 76, 20, COLOR_WHITE);
      5. Executefill_circle(frame, 160, 76, 20, COLOR_YELLOW);
      6. Executedraw_ring(frame, 160, 76, 20, COLOR_WHITE);
      7. Executedraw_text_centred(frame, 75, 106, "RED", 1, COLOR_RED);
      8. Executedraw_text_centred(frame, 160, 106, "YELLOW", 1, COLOR_YELLOW);
      9. Executedraw_text_centred(frame, 255, 76, "AUTO", 1, COLOR_WHITE);
      10. Executedraw_rect(frame, selected == 0 ? 48 : selected == 1 ? 133 : 220, selected == 2 ? 54 : 49, selected == 2 ? 70 : 54, selected == 2 ? 38 : 54, COLOR_YELLOW);
      11. Executedraw_text_centred(frame, 160, 145, "D/B/STICK CHOOSE A/K START", 1, COLOR_LIGHTGRAY);
      12. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionstate == PLAYING && !auto_mode && current_piece == human_piece
    True
      1. Executeconst int x = BOARD_X + selected_column * CELL + CELL / 2;
      2. Executedraw_line(frame, x - 6, 16, x, 25, COLOR_YELLOW);
      3. Executedraw_line(frame, x + 6, 16, x, 25, COLOR_YELLOW);
      4. Executedraw_line(frame, x - 6, 16, x + 6, 16, COLOR_YELLOW);
    False

    Continue after this decision.

  7. Executefill_rect(frame, BOARD_X, BOARD_Y, COLUMNS * CELL, ROWS * CELL, COLOR_BLUE);
  8. FOR loopunsigned row = 0; row < ROWS; ++row

    Repeat body while the loop condition permits; then continue below.

    1. FOR loopunsigned column = 0; column < COLUMNS; ++column

      Repeat body while the loop condition permits; then continue below.

        1. Executeconst int x = BOARD_X + column * CELL + CELL / 2, y = BOARD_Y + row * CELL + CELL / 2;
        2. Executefill_circle(frame, x, y, 8, board[row][column] == EMPTY ? COLOR_BLACK : piece_colour(board[row][column]));
        3. Executedraw_ring(frame, x, y, 8, COLOR_WHITE);
  9. Executedraw_text(frame, 170, 8, "TURN", 1, COLOR_CYAN);
  10. Executedraw_text(frame, 170, 20, current_piece == RED_PIECE ? "RED" : "YELLOW", 1, piece_colour(current_piece));
  11. ExecutedrawGameGold(frame, 166, 37, 148, true);
  12. Executedraw_text_centred(frame, 242, 75, message, 1, COLOR_YELLOW);
  13. Decisionauto_mode
    True
      1. Executesnprintf(text, sizeof(text), "AI %s", aiSpeedShortText(ai_speed));
      2. Executedraw_text_centred(frame, 242, 104, text, 1, COLOR_CYAN);
      3. Executedraw_text_centred(frame, 242, 124, "E SPEED F PAUSE", 1, COLOR_LIGHTGRAY);
    False
    1. Decisionstate == PLAYING
      True
      1. Executedraw_text_centred(frame, 242, 124, "D/B MOVE A/K DROP", 1, COLOR_LIGHTGRAY);
      False
      1. Executedraw_text_centred(frame, 242, 124, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
  14. Executedraw_text_centred(frame, 242, 145, "C CHOOSE F PAUSE", 1, COLOR_LIGHTGRAY);
  15. Decisionscore_save_failed || gold_save_failed
    True
    1. Executedraw_text_centred(frame, 242, 104, "SAVE ERROR", 1, COLOR_RED);
    False

    Continue after this decision.

Read original function source
void drawConnectFour(uint16_t *frame)
{
    if (!running || frame == NULL) return;
    clear(frame, COLOR_BLACK);
    char text[48];
    if (state == SHOW_SCORES) {
        draw_text_centred(frame, 160, 8, "CONNECT FOUR", 3, COLOR_CYAN);
        snprintf(text, sizeof(text), "WINS %u", (unsigned)scores.wins); draw_text(frame, 35, 58, text, 1, COLOR_GREEN);
        snprintf(text, sizeof(text), "LOSSES %u", (unsigned)scores.losses); draw_text(frame, 170, 58, text, 1, COLOR_RED);
        snprintf(text, sizeof(text), "DRAWS %u", (unsigned)scores.draws); draw_text(frame, 35, 92, text, 1, COLOR_YELLOW);
        snprintf(text, sizeof(text), "STREAK %u", (unsigned)scores.streak); draw_text(frame, 170, 92, text, 1, COLOR_ORANGE);
        draw_text_centred(frame, 160, 145, game_scores_status_text(score_status), 1, COLOR_LIGHTGRAY);
        return;
    }
    if (state == CHOOSE_PIECE) {
        const int selected = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1;
        draw_text_centred(frame, 160, 8, "CONNECT FOUR", 3, COLOR_CYAN);
        fill_circle(frame, 75, 76, 20, COLOR_RED); draw_ring(frame, 75, 76, 20, COLOR_WHITE);
        fill_circle(frame, 160, 76, 20, COLOR_YELLOW); draw_ring(frame, 160, 76, 20, COLOR_WHITE);
        draw_text_centred(frame, 75, 106, "RED", 1, COLOR_RED); draw_text_centred(frame, 160, 106, "YELLOW", 1, COLOR_YELLOW);
        draw_text_centred(frame, 255, 76, "AUTO", 1, COLOR_WHITE);
        draw_rect(frame, selected == 0 ? 48 : selected == 1 ? 133 : 220, selected == 2 ? 54 : 49, selected == 2 ? 70 : 54, selected == 2 ? 38 : 54, COLOR_YELLOW);
        draw_text_centred(frame, 160, 145, "D/B/STICK CHOOSE  A/K START", 1, COLOR_LIGHTGRAY);
        return;
    }
    if (state == PLAYING && !auto_mode && current_piece == human_piece) {
        const int x = BOARD_X + selected_column * CELL + CELL / 2;
        draw_line(frame, x - 6, 16, x, 25, COLOR_YELLOW); draw_line(frame, x + 6, 16, x, 25, COLOR_YELLOW); draw_line(frame, x - 6, 16, x + 6, 16, COLOR_YELLOW);
    }
    fill_rect(frame, BOARD_X, BOARD_Y, COLUMNS * CELL, ROWS * CELL, COLOR_BLUE);
    for (unsigned row = 0; row < ROWS; ++row) for (unsigned column = 0; column < COLUMNS; ++column) {
        const int x = BOARD_X + column * CELL + CELL / 2, y = BOARD_Y + row * CELL + CELL / 2;
        fill_circle(frame, x, y, 8, board[row][column] == EMPTY ? COLOR_BLACK : piece_colour(board[row][column]));
        draw_ring(frame, x, y, 8, COLOR_WHITE);
    }
    draw_text(frame, 170, 8, "TURN", 1, COLOR_CYAN);
    draw_text(frame, 170, 20, current_piece == RED_PIECE ? "RED" : "YELLOW", 1, piece_colour(current_piece));
    drawGameGold(frame, 166, 37, 148, true);
    draw_text_centred(frame, 242, 75, message, 1, COLOR_YELLOW);
    if (auto_mode) {
        snprintf(text, sizeof(text), "AI %s", aiSpeedShortText(ai_speed)); draw_text_centred(frame, 242, 104, text, 1, COLOR_CYAN);
        draw_text_centred(frame, 242, 124, "E SPEED  F PAUSE", 1, COLOR_LIGHTGRAY);
    } else if (state == PLAYING) draw_text_centred(frame, 242, 124, "D/B MOVE  A/K DROP", 1, COLOR_LIGHTGRAY);
    else draw_text_centred(frame, 242, 124, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
    draw_text_centred(frame, 242, 145, "C CHOOSE  F PAUSE", 1, COLOR_LIGHTGRAY);
    if (score_save_failed || gold_save_failed) draw_text_centred(frame, 242, 104, "SAVE ERROR", 1, COLOR_RED);
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "connect_four.h"
#include "audio.h"
#include "ai_speed.h"
#include "esp_random.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "game_scores.h"
#include "gold.h"
#include <stdio.h>

#define ROWS 6U
#define COLUMNS 7U
#define CELL 22
#define BOARD_X 4
#define BOARD_Y 28
#define WIN_GOLD 35U
#define SCORE_SHOW_MS 2000U

enum { EMPTY, RED_PIECE, YELLOW_PIECE };
typedef enum { SHOW_SCORES, CHOOSE_PIECE, PLAYING, FINISHED } game_state_t;

static uint8_t board[ROWS][COLUMNS];
static game_scores_t scores;
static game_score_result_t score_status;
static game_state_t state;
static uint8_t human_piece, current_piece, gold_piece, selected_column, ai_speed;
static uint32_t now, state_started, computer_due, last_nav;
static bool running, auto_mode, stick_centred, score_save_failed, gold_save_failed;
static const char *message;

static uint8_t other(uint8_t piece) { return piece == RED_PIECE ? YELLOW_PIECE : RED_PIECE; }
static uint16_t piece_colour(uint8_t piece) { return piece == RED_PIECE ? COLOR_RED : COLOR_YELLOW; }
static bool available(uint8_t column) { return column < COLUMNS && board[0][column] == EMPTY; }

static int drop(uint8_t column, uint8_t piece)
{
    if (!available(column)) return -1;
    for (int row = ROWS - 1; row >= 0; --row) if (board[row][column] == EMPTY) {
        board[row][column] = piece;
        return row;
    }
    return -1;
}

static unsigned line_count(int row, int column, int dr, int dc, uint8_t piece)
{
    unsigned count = 0;
    for (row += dr, column += dc; row >= 0 && row < ROWS && column >= 0 && column < COLUMNS &&
         board[row][column] == piece; row += dr, column += dc) ++count;
    return count;
}

static bool wins(int row, int column, uint8_t piece)
{
    static const int directions[4][2] = {{0, 1}, {1, 0}, {1, 1}, {1, -1}};
    for (unsigned i = 0; i < 4; ++i) {
        if (1U + line_count(row, column, directions[i][0], directions[i][1], piece) +
            line_count(row, column, -directions[i][0], -directions[i][1], piece) >= 4U) return true;
    }
    return false;
}

static bool move_wins(uint8_t column, uint8_t piece)
{
    const int row = drop(column, piece);
    if (row < 0) return false;
    const bool result = wins(row, column, piece);
    board[row][column] = EMPTY;
    return result;
}

static bool full(void)
{
    for (uint8_t column = 0; column < COLUMNS; ++column) if (available(column)) return false;
    return true;
}

static uint8_t first_available(void)
{
    static const uint8_t order[COLUMNS] = {3, 2, 4, 1, 5, 0, 6};
    for (unsigned i = 0; i < COLUMNS; ++i) if (available(order[i])) return order[i];
    return 0;
}

static uint8_t computer_column(void)
{
    uint8_t choices[COLUMNS], count = 0;
    for (uint8_t column = 0; column < COLUMNS; ++column) if (available(column)) choices[count++] = column;
    for (uint8_t i = 0; i < count; ++i) if (move_wins(choices[i], current_piece)) return choices[i];
    const uint8_t opponent = other(current_piece);
    for (uint8_t i = 0; i < count; ++i) if (move_wins(choices[i], opponent)) return choices[i];
    if ((esp_random() % 100U) < 20U) return choices[esp_random() % count];
    uint8_t best = choices[0];
    int best_score = -10000;
    for (uint8_t i = 0; i < count; ++i) {
        const uint8_t column = choices[i];
        const int row = drop(column, current_piece);
        int score = (int)(esp_random() % 8U) + (3 - (column > 3 ? column - 3 : 3 - column)) * 5;
        for (uint8_t reply = 0; reply < COLUMNS; ++reply) if (available(reply) && move_wins(reply, opponent)) score -= 80;
        score += (int)line_count(row, column, 0, 1, current_piece) * 3;
        score += (int)line_count(row, column, 0, -1, current_piece) * 3;
        score += (int)line_count(row, column, 1, 0, current_piece) * 3;
        board[row][column] = EMPTY;
        if (score > best_score) { best_score = score; best = column; }
    }
    return best;
}

static void reset_board(void)
{
    for (unsigned row = 0; row < ROWS; ++row) for (unsigned column = 0; column < COLUMNS; ++column) board[row][column] = EMPTY;
    current_piece = RED_PIECE;
    selected_column = 3;
}

static void finish(uint8_t winner)
{
    state = FINISHED;
    state_started = now;
    if (auto_mode) {
        if (winner == gold_piece && !addAIGold(WIN_GOLD)) gold_save_failed = true;
        message = winner == EMPTY ? "DRAW" : winner == RED_PIECE ? "RED WINS" : "YELLOW WINS";
        return;
    }
    if (winner == human_piece) {
        ++scores.wins; ++scores.streak;
        if (!addUserGoldWon(WIN_GOLD)) gold_save_failed = true;
        message = "YOU WIN";
    } else if (winner == EMPTY) {
        ++scores.draws; scores.streak = 0; message = "DRAW";
    } else {
        ++scores.losses; scores.streak = 0; message = "CPU WINS";
    }
    score_save_failed = !game_scores_save(GAME_CONNECT_FOUR, &scores);
}

static void play(uint8_t column)
{
    const int row = drop(column, current_piece);
    if (row < 0) return;
    audioPlay(AUDIO_SFX_BOARD_PLACE);
    if (wins(row, column, current_piece)) { finish(current_piece); return; }
    if (full()) { finish(EMPTY); return; }
    current_piece = other(current_piece);
    selected_column = first_available();
    message = auto_mode || current_piece != human_piece ? "CPU TURN" : "YOUR TURN";
    computer_due = now + aiSpeedDelay(ai_speed);
}

static void begin_game(void)
{
    reset_board();
    state = PLAYING;
    if (auto_mode) gold_piece = (esp_random() & 1U) == 0U ? RED_PIECE : YELLOW_PIECE;
    message = auto_mode || human_piece != RED_PIECE ? "CPU TURN" : "YOUR TURN";
    computer_due = now + aiSpeedDelay(ai_speed);
}

static void move_selection(int direction)
{
    if (state == CHOOSE_PIECE) {
        int choice = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1;
        choice = (choice + direction + 3) % 3;
        auto_mode = choice == 2;
        human_piece = choice == 1 ? YELLOW_PIECE : RED_PIECE;
        return;
    }
    if (state != PLAYING || auto_mode || current_piece != human_piece) return;
    for (unsigned i = 1; i <= COLUMNS; ++i) {
        const int next = (int)selected_column + direction * (int)i;
        const uint8_t column = (uint8_t)((next % COLUMNS + COLUMNS) % COLUMNS);
        if (available(column)) { selected_column = column; return; }
    }
}

void startConnectFour(uint32_t now_ms)
{
    now = now_ms; running = true; auto_mode = false; human_piece = RED_PIECE; ai_speed = AI_SPEED_DEFAULT_INDEX;
    score_save_failed = gold_save_failed = false; scores = (game_scores_t){0};
    score_status = game_scores_load(GAME_CONNECT_FOUR, &scores);
    reset_board(); state = SHOW_SCORES; state_started = now_ms; message = ""; stick_centred = false;
}
void stopConnectFour(void) { running = false; }

void connectFourAction(bool select, bool speed, bool choose, uint32_t now_ms)
{
    now = now_ms;
    if (!running || state == SHOW_SCORES) return;
    if (choose) { state = CHOOSE_PIECE; auto_mode = false; human_piece = RED_PIECE; return; }
    if (state == CHOOSE_PIECE) { if (select) begin_game(); return; }
    if (auto_mode && speed) { cycleAISpeed(&ai_speed); computer_due = now + aiSpeedDelay(ai_speed); return; }
    if (state == FINISHED && select) { state = CHOOSE_PIECE; auto_mode = false; human_piece = RED_PIECE; return; }
    if (state == PLAYING && !auto_mode && current_piece == human_piece && select) play(selected_column);
}

void updateConnectFour(int x_offset, int y_offset, bool valid, bool left, bool right, uint32_t now_ms)
{
    (void)y_offset; now = now_ms;
    if (!running) return;
    if (state == SHOW_SCORES && now - state_started >= SCORE_SHOW_MS) { state = CHOOSE_PIECE; return; }
    if (state == FINISHED && auto_mode) {
        if (now - state_started >= aiRestartDelay()) begin_game();
        return;
    }
    if (state == PLAYING && (auto_mode || current_piece != human_piece) && (int32_t)(now - computer_due) >= 0) play(computer_column());
    int direction = left == right ? 0 : (left ? -1 : 1);
    if (direction != 0 && now - last_nav >= 170U) { move_selection(direction); last_nav = now; }
    if (!valid || (x_offset > -350 && x_offset < 350)) stick_centred = true;
    if (valid && stick_centred && (x_offset <= -650 || x_offset >= 650)) {
        move_selection(x_offset < 0 ? -1 : 1); stick_centred = false;
    }
}

void drawConnectFour(uint16_t *frame)
{
    if (!running || frame == NULL) return;
    clear(frame, COLOR_BLACK);
    char text[48];
    if (state == SHOW_SCORES) {
        drawPet2001TextCentredTransparent(frame, 160, 8, "CONNECT FOUR", COLOR_CYAN, 3);
        snprintf(text, sizeof(text), "WINS %u", (unsigned)scores.wins);
        drawPet2001TextTransparent(frame, 35, 58, text, COLOR_GREEN, 1);
        snprintf(text, sizeof(text), "LOSSES %u", (unsigned)scores.losses);
        drawPet2001TextTransparent(frame, 170, 58, text, COLOR_RED, 1);
        snprintf(text, sizeof(text), "DRAWS %u", (unsigned)scores.draws);
        drawPet2001TextTransparent(frame, 35, 92, text, COLOR_YELLOW, 1);
        snprintf(text, sizeof(text), "STREAK %u", (unsigned)scores.streak);
        drawPet2001TextTransparent(frame, 170, 92, text, COLOR_ORANGE, 1);
        drawPet2001TextCentredTransparent(frame, 160, 145, game_scores_status_text(score_status), COLOR_LIGHTGRAY, 1);
        return;
    }
    if (state == CHOOSE_PIECE) {
        const int selected = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1;
        drawPet2001TextCentredTransparent(frame, 160, 8, "CONNECT FOUR", COLOR_CYAN, 3);
        fill_circle(frame, 75, 76, 20, COLOR_RED); draw_ring(frame, 75, 76, 20, COLOR_WHITE);
        fill_circle(frame, 160, 76, 20, COLOR_YELLOW); draw_ring(frame, 160, 76, 20, COLOR_WHITE);
        drawPet2001TextCentredTransparent(frame, 75, 106, "RED", COLOR_RED, 1);
        drawPet2001TextCentredTransparent(frame, 160, 106, "YELLOW", COLOR_YELLOW, 1);
        drawPet2001TextCentredTransparent(frame, 255, 76, "AUTO", COLOR_WHITE, 1);
        draw_rect(frame, selected == 0 ? 48 : selected == 1 ? 133 : 220, selected == 2 ? 54 : 49, selected == 2 ? 70 : 54, selected == 2 ? 38 : 54, COLOR_YELLOW);
        drawPet2001TextCentredTransparent(frame, 160, 145, "D/B/STICK CHOOSE  A/K START", COLOR_LIGHTGRAY, 1);
        return;
    }
    if (state == PLAYING && !auto_mode && current_piece == human_piece) {
        const int x = BOARD_X + selected_column * CELL + CELL / 2;
        draw_line(frame, x - 6, 16, x, 25, COLOR_YELLOW); draw_line(frame, x + 6, 16, x, 25, COLOR_YELLOW); draw_line(frame, x - 6, 16, x + 6, 16, COLOR_YELLOW);
    }
    fill_rect(frame, BOARD_X, BOARD_Y, COLUMNS * CELL, ROWS * CELL, COLOR_BLUE);
    for (unsigned row = 0; row < ROWS; ++row) for (unsigned column = 0; column < COLUMNS; ++column) {
        const int x = BOARD_X + column * CELL + CELL / 2, y = BOARD_Y + row * CELL + CELL / 2;
        fill_circle(frame, x, y, 8, board[row][column] == EMPTY ? COLOR_BLACK : piece_colour(board[row][column]));
        draw_ring(frame, x, y, 8, COLOR_WHITE);
    }
    drawPet2001TextTransparent(frame, 170, 8, "TURN", COLOR_CYAN, 1);
    drawPet2001TextTransparent(frame, 170, 20, current_piece == RED_PIECE ? "RED" : "YELLOW", piece_colour(current_piece), 1);
    drawGameGold(frame, 166, 37, 148, true);
    drawPet2001TextCentredTransparent(frame, 242, 75, message, COLOR_YELLOW, 1);
    if (auto_mode) {
        snprintf(text, sizeof(text), "AI %s", aiSpeedShortText(ai_speed));
        drawPet2001TextCentredTransparent(frame, 242, 104, text, COLOR_CYAN, 1);
        drawPet2001TextCentredTransparent(frame, 242, 124, "E SPEED  F PAUSE", COLOR_LIGHTGRAY, 1);
    } else if (state == PLAYING) {
        drawPet2001TextCentredTransparent(frame, 242, 124, "D/B MOVE  A/K DROP", COLOR_LIGHTGRAY, 1);
    } else {
        drawPet2001TextCentredTransparent(frame, 242, 124, "A/K NEW GAME", COLOR_LIGHTGRAY, 1);
    }
    drawPet2001TextCentredTransparent(frame, 242, 145, "C CHOOSE  F PAUSE", COLOR_LIGHTGRAY, 1);
    if (score_save_failed || gold_save_failed)
        drawPet2001TextCentredTransparent(frame, 242, 104, "SAVE ERROR", COLOR_RED, 1);
}