Connect Four
Source: src/connect_four.c · 270 lines · 20 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
other() · source line 30
static uint8_t other(uint8_t piece)
- Exit this function
return 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)
- Exit this function
return 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)
- Exit this function
return 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:
- Decision
!available(column)True- Exit this function
return -1;
FalseContinue after this decision.
- FOR loop
int row = ROWS - 1; row >= 0; --rowRepeat body while the loop condition permits; then continue below.
- Decision
board[row][column] == EMPTYTrue- Execute
board[row][column] = piece; - Exit this function
return row;
FalseContinue after this decision.
- Exit this function
return -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)
- Execute
unsigned count = 0; - FOR loop
row += dr, column += dc; row >= 0 && row < ROWS && column >= 0 && column < COLUMNS && board[row][column] == piece; row += dr, column += dcRepeat body while the loop condition permits; then continue below.
- Execute
++count;
- Exit this function
return 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:
- Execute
static const int directions[4][2] = {{0, 1}, {1, 0}, {1, 1}, {1, -1}}; - FOR loop
unsigned i = 0; i < 4; ++iRepeat body while the loop condition permits; then continue below.
- Decision
1U + line_count(row, column, directions[i][0], directions[i][1], piece) + line_count(row, column, -directions[i][0], -directions[i][1], piece) >= 4UTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return 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:
- Execute
const int row = drop(column, piece); - Decision
row < 0True- Exit this function
return false;
FalseContinue after this decision.
- Execute
const bool result = wins(row, column, piece); - Execute
board[row][column] = EMPTY; - Exit this function
return 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:
- FOR loop
uint8_t column = 0; column < COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
available(column)True- Exit this function
return false;
FalseContinue after this decision.
- Exit this function
return 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:
- Execute
static const uint8_t order[COLUMNS] = {3, 2, 4, 1, 5, 0, 6}; - FOR loop
unsigned i = 0; i < COLUMNS; ++iRepeat body while the loop condition permits; then continue below.
- Decision
available(order[i])True- Exit this function
return order[i];
FalseContinue after this decision.
- Exit this function
return 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:
- Execute
uint8_t choices[COLUMNS], count = 0; - FOR loop
uint8_t column = 0; column < COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
available(column)True- Execute
choices[count++] = column;
FalseContinue after this decision.
- FOR loop
uint8_t i = 0; i < count; ++iRepeat body while the loop condition permits; then continue below.
- Decision
move_wins(choices[i], current_piece)True- Exit this function
return choices[i];
FalseContinue after this decision.
- Execute
const uint8_t opponent = other(current_piece); - FOR loop
uint8_t i = 0; i < count; ++iRepeat body while the loop condition permits; then continue below.
- Decision
move_wins(choices[i], opponent)True- Exit this function
return choices[i];
FalseContinue after this decision.
- Decision
(esp_random() % 100U) < 20UTrue- Exit this function
return choices[esp_random() % count];
FalseContinue after this decision.
- Execute
uint8_t best = choices[0]; - Execute
int best_score = -10000; - FOR loop
uint8_t i = 0; i < count; ++iRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t column = choices[i]; - Execute
const int row = drop(column, current_piece); - Execute
int score = (int)(esp_random() % 8U) + (3 - (column > 3 ? column - 3 : 3 - column)) * 5; - FOR loop
uint8_t reply = 0; reply < COLUMNS; ++replyRepeat body while the loop condition permits; then continue below.
- Decision
available(reply) && move_wins(reply, opponent)True- Execute
score -= 80;
FalseContinue after this decision.
- Execute
score += (int)line_count(row, column, 0, 1, current_piece) * 3; - Execute
score += (int)line_count(row, column, 0, -1, current_piece) * 3; - Execute
score += (int)line_count(row, column, 1, 0, current_piece) * 3; - Execute
board[row][column] = EMPTY; - Decision
score > best_scoreTrue- Execute
best_score = score; - Execute
best = column;
FalseContinue after this decision.
- Exit this function
return 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)
- FOR loop
unsigned row = 0; row < ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0; column < COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
board[row][column] = EMPTY;
- Execute
current_piece = RED_PIECE; - Execute
selected_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:
- Execute
state = FINISHED; - Execute
state_started = now; - Decision
auto_modeTrue- Decision
winner == gold_piece && !addAIGold(WIN_GOLD)True- Execute
gold_save_failed = true;
FalseContinue after this decision.
- Execute
message = winner == EMPTY ? "DRAW" : winner == RED_PIECE ? "RED WINS" : "YELLOW WINS"; - Exit this function
return;
FalseContinue after this decision.
- Decision
winner == human_pieceTrue- Execute
++scores.wins; - Execute
++scores.streak; - Decision
!addUserGoldWon(WIN_GOLD)True- Execute
gold_save_failed = true;
FalseContinue after this decision.
- Execute
message = "YOU WIN";
False- Decision
winner == EMPTYTrue- Execute
++scores.draws; - Execute
scores.streak = 0; - Execute
message = "DRAW";
False- Execute
++scores.losses; - Execute
scores.streak = 0; - Execute
message = "CPU WINS";
- Execute
score_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:
- Execute
const int row = drop(column, current_piece); - Decision
row < 0True- Exit this function
return;
FalseContinue after this decision.
- Execute
audioPlay(AUDIO_SFX_BOARD_PLACE); - Decision
wins(row, column, current_piece)True- Execute
finish(current_piece); - Exit this function
return;
FalseContinue after this decision.
- Decision
full()True- Execute
finish(EMPTY); - Exit this function
return;
FalseContinue after this decision.
- Execute
current_piece = other(current_piece); - Execute
selected_column = first_available(); - Execute
message = auto_mode || current_piece != human_piece ? "CPU TURN" : "YOUR TURN"; - Execute
computer_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:
- Execute
reset_board(); - Execute
state = PLAYING; - Decision
auto_modeTrue- Execute
gold_piece = (esp_random() & 1U) == 0U ? RED_PIECE : YELLOW_PIECE;
FalseContinue after this decision.
- Execute
message = auto_mode || human_piece != RED_PIECE ? "CPU TURN" : "YOUR TURN"; - Execute
computer_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:
- Decision
state == CHOOSE_PIECETrue- Execute
int choice = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1; - Execute
choice = (choice + direction + 3) % 3; - Execute
auto_mode = choice == 2; - Execute
human_piece = choice == 1 ? YELLOW_PIECE : RED_PIECE; - Exit this function
return;
FalseContinue after this decision.
- Decision
state != PLAYING || auto_mode || current_piece != human_pieceTrue- Exit this function
return;
FalseContinue after this decision.
- FOR loop
unsigned i = 1; i <= COLUMNS; ++iRepeat body while the loop condition permits; then continue below.
- Execute
const int next = (int)selected_column + direction * (int)i; - Execute
const uint8_t column = (uint8_t)((next % COLUMNS + COLUMNS) % COLUMNS); - Decision
available(column)True- Execute
selected_column = column; - Exit this function
return;
FalseContinue 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:
- Execute
now = now_ms; - Execute
running = true; - Execute
auto_mode = false; - Execute
human_piece = RED_PIECE; - Execute
ai_speed = AI_SPEED_DEFAULT_INDEX; - Execute
score_save_failed = gold_save_failed = false; - Execute
scores = (game_scores_t){0}; - Execute
score_status = game_scores_load(GAME_CONNECT_FOUR, &scores); - Execute
reset_board(); - Execute
state = SHOW_SCORES; - Execute
state_started = now_ms; - Execute
message = ""; - Execute
stick_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)
- Execute
running = 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:
- Execute
now = now_ms; - Decision
!running || state == SHOW_SCORESTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
chooseTrue- Execute
state = CHOOSE_PIECE; - Execute
auto_mode = false; - Execute
human_piece = RED_PIECE; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == CHOOSE_PIECETrue- Decision
selectTrue- Execute
begin_game();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
auto_mode && speedTrue- Execute
cycleAISpeed(&ai_speed); - Execute
computer_due = now + aiSpeedDelay(ai_speed); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == FINISHED && selectTrue- Execute
state = CHOOSE_PIECE; - Execute
auto_mode = false; - Execute
human_piece = RED_PIECE; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == PLAYING && !auto_mode && current_piece == human_piece && selectTrue- Execute
play(selected_column);
FalseContinue 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:
- Execute
(void)y_offset; - Execute
now = now_ms; - Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == SHOW_SCORES && now - state_started >= SCORE_SHOW_MSTrue- Execute
state = CHOOSE_PIECE; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == FINISHED && auto_modeTrue- Decision
now - state_started >= aiRestartDelay()True- Execute
begin_game();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == PLAYING && (auto_mode || current_piece != human_piece) && (int32_t)(now - computer_due) >= 0True- Execute
play(computer_column());
FalseContinue after this decision.
- Execute
int direction = left == right ? 0 : (left ? -1 : 1); - Decision
direction != 0 && now - last_nav >= 170UTrue- Execute
move_selection(direction); - Execute
last_nav = now;
FalseContinue after this decision.
- Decision
!valid || (x_offset > -350 && x_offset < 350)True- Execute
stick_centred = true;
FalseContinue after this decision.
- Decision
valid && stick_centred && (x_offset <= -650 || x_offset >= 650)True- Execute
move_selection(x_offset < 0 ? -1 : 1); - Execute
stick_centred = false;
FalseContinue 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:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
char text[48]; - Decision
state == SHOW_SCORESTrue- Execute
draw_text_centred(frame, 160, 8, "CONNECT FOUR", 3, COLOR_CYAN); - Execute
snprintf(text, sizeof(text), "WINS %u", (unsigned)scores.wins); - Execute
draw_text(frame, 35, 58, text, 1, COLOR_GREEN); - Execute
snprintf(text, sizeof(text), "LOSSES %u", (unsigned)scores.losses); - Execute
draw_text(frame, 170, 58, text, 1, COLOR_RED); - Execute
snprintf(text, sizeof(text), "DRAWS %u", (unsigned)scores.draws); - Execute
draw_text(frame, 35, 92, text, 1, COLOR_YELLOW); - Execute
snprintf(text, sizeof(text), "STREAK %u", (unsigned)scores.streak); - Execute
draw_text(frame, 170, 92, text, 1, COLOR_ORANGE); - Execute
draw_text_centred(frame, 160, 145, game_scores_status_text(score_status), 1, COLOR_LIGHTGRAY); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == CHOOSE_PIECETrue- Execute
const int selected = auto_mode ? 2 : human_piece == RED_PIECE ? 0 : 1; - Execute
draw_text_centred(frame, 160, 8, "CONNECT FOUR", 3, COLOR_CYAN); - Execute
fill_circle(frame, 75, 76, 20, COLOR_RED); - Execute
draw_ring(frame, 75, 76, 20, COLOR_WHITE); - Execute
fill_circle(frame, 160, 76, 20, COLOR_YELLOW); - Execute
draw_ring(frame, 160, 76, 20, COLOR_WHITE); - Execute
draw_text_centred(frame, 75, 106, "RED", 1, COLOR_RED); - Execute
draw_text_centred(frame, 160, 106, "YELLOW", 1, COLOR_YELLOW); - Execute
draw_text_centred(frame, 255, 76, "AUTO", 1, COLOR_WHITE); - Execute
draw_rect(frame, selected == 0 ? 48 : selected == 1 ? 133 : 220, selected == 2 ? 54 : 49, selected == 2 ? 70 : 54, selected == 2 ? 38 : 54, COLOR_YELLOW); - Execute
draw_text_centred(frame, 160, 145, "D/B/STICK CHOOSE A/K START", 1, COLOR_LIGHTGRAY); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == PLAYING && !auto_mode && current_piece == human_pieceTrue- Execute
const int x = BOARD_X + selected_column * CELL + CELL / 2; - Execute
draw_line(frame, x - 6, 16, x, 25, COLOR_YELLOW); - Execute
draw_line(frame, x + 6, 16, x, 25, COLOR_YELLOW); - Execute
draw_line(frame, x - 6, 16, x + 6, 16, COLOR_YELLOW);
FalseContinue after this decision.
- Execute
fill_rect(frame, BOARD_X, BOARD_Y, COLUMNS * CELL, ROWS * CELL, COLOR_BLUE); - FOR loop
unsigned row = 0; row < ROWS; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0; column < COLUMNS; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
const int x = BOARD_X + column * CELL + CELL / 2, y = BOARD_Y + row * CELL + CELL / 2; - Execute
fill_circle(frame, x, y, 8, board[row][column] == EMPTY ? COLOR_BLACK : piece_colour(board[row][column])); - Execute
draw_ring(frame, x, y, 8, COLOR_WHITE);
- Execute
draw_text(frame, 170, 8, "TURN", 1, COLOR_CYAN); - Execute
draw_text(frame, 170, 20, current_piece == RED_PIECE ? "RED" : "YELLOW", 1, piece_colour(current_piece)); - Execute
drawGameGold(frame, 166, 37, 148, true); - Execute
draw_text_centred(frame, 242, 75, message, 1, COLOR_YELLOW); - Decision
auto_modeTrue- Execute
snprintf(text, sizeof(text), "AI %s", aiSpeedShortText(ai_speed)); - Execute
draw_text_centred(frame, 242, 104, text, 1, COLOR_CYAN); - Execute
draw_text_centred(frame, 242, 124, "E SPEED F PAUSE", 1, COLOR_LIGHTGRAY);
False- Decision
state == PLAYINGTrue- Execute
draw_text_centred(frame, 242, 124, "D/B MOVE A/K DROP", 1, COLOR_LIGHTGRAY);
False- Execute
draw_text_centred(frame, 242, 124, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
- Execute
draw_text_centred(frame, 242, 145, "C CHOOSE F PAUSE", 1, COLOR_LIGHTGRAY); - Decision
score_save_failed || gold_save_failedTrue- Execute
draw_text_centred(frame, 242, 104, "SAVE ERROR", 1, COLOR_RED);
FalseContinue 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);
}