Pig

Source: src/pig.c · 276 lines · 17 functions.

Screen captures

Views of this part running on the Mini Arcade.

Pig game screen on the Mini Arcade
Pig showing its dice, scores, and turn information.

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

setMessage() · source line 40
static void setMessage(const char *message)
  1. Executesnprintf(game_message, sizeof(game_message), "%s", message);
Read original function source
static void setMessage(const char *message)
{
    snprintf(game_message, sizeof(game_message), "%s", message);
}
loadScores() · source line 45
static void loadScores(void)

Calls into this documentation:

  1. Executebool found = false;
  2. Executescores = (pig_scores_t){ .version = 1U };
  3. Decision!game_storage_read(PIG_SCORE_NAMESPACE, PIG_SCORE_KEY, &scores, sizeof(scores), &found) || !found || scores.version != 1U
    True
    1. Executescores = (pig_scores_t){ .version = 1U };
    False

    Continue after this decision.

Read original function source
static void loadScores(void)
{
    bool found = false;
    scores = (pig_scores_t){ .version = 1U };
    if (!game_storage_read(PIG_SCORE_NAMESPACE, PIG_SCORE_KEY, &scores, sizeof(scores), &found) ||
        !found || scores.version != 1U) scores = (pig_scores_t){ .version = 1U };
}
saveScores() · source line 53
static void saveScores(void)

Calls into this documentation:

  1. Execute(void)game_storage_write(PIG_SCORE_NAMESPACE, PIG_SCORE_KEY, &scores, sizeof(scores));
Read original function source
static void saveScores(void)
{
    (void)game_storage_write(PIG_SCORE_NAMESPACE, PIG_SCORE_KEY, &scores, sizeof(scores));
}
finishGame() · source line 58
static void finishGame(pig_side_t winner, uint32_t now_ms)

Calls into this documentation:

  1. ExecuteaudioPlay(winner == PIG_PLAYER ? AUDIO_SFX_WAVE_CLEAR : AUDIO_SFX_GAME_OVER);
  2. Executestate = PIG_FINISHED;
  3. Executeturn_score = 0U;
  4. Executeauto_restart_started = now_ms;
  5. Executeauto_seconds_shown = (uint8_t)((aiRestartDelay() + 999U) / 1000U);
  6. Decisionauto_mode
    True
    1. ExecutesetMessage(winner == PIG_PLAYER ? "A WINS" : "B WINS");
    False
    1. Decisionwinner == PIG_PLAYER
      True
        1. Execute++scores.wins;
        2. Execute++scores.streak;
        3. ExecuteaddUserGoldWon(PIG_WIN_GOLD);
        4. ExecutesaveScores();
        5. ExecutesetMessage("YOU WIN +10 GOLD");
      False
        1. Execute++scores.losses;
        2. Executescores.streak = 0U;
        3. ExecutesaveScores();
        4. ExecutesetMessage("CPU WINS");
  7. Executeredraw_needed = true;
Read original function source
static void finishGame(pig_side_t winner, uint32_t now_ms)
{
    audioPlay(winner == PIG_PLAYER ? AUDIO_SFX_WAVE_CLEAR : AUDIO_SFX_GAME_OVER);
    state = PIG_FINISHED;
    turn_score = 0U;
    auto_restart_started = now_ms;
    auto_seconds_shown = (uint8_t)((aiRestartDelay() + 999U) / 1000U);

    if (auto_mode) setMessage(winner == PIG_PLAYER ? "A WINS" : "B WINS");
    else if (winner == PIG_PLAYER) {
        ++scores.wins;
        ++scores.streak;
        addUserGoldWon(PIG_WIN_GOLD);
        saveScores();
        setMessage("YOU WIN +10 GOLD");
    } else {
        ++scores.losses;
        scores.streak = 0U;
        saveScores();
        setMessage("CPU WINS");
    }
    redraw_needed = true;
}
scheduleComputer() · source line 82
static void scheduleComputer(uint32_t now_ms)
  1. Executestate_started = now_ms;
Read original function source
static void scheduleComputer(uint32_t now_ms) { state_started = now_ms; }
changeTurn() · source line 84
static void changeTurn(uint32_t now_ms)

Calls into this documentation:

  1. Executeturn_score = 0U;
  2. Executecurrent_side = current_side == PIG_PLAYER ? PIG_CPU : PIG_PLAYER;
  3. Executeselected_action = PIG_ROLL;
  4. ExecutesetMessage(auto_mode ? (current_side == PIG_PLAYER ? "A TURN" : "B TURN") : (current_side == PIG_PLAYER ? "YOUR TURN" : "CPU TURN"));
  5. Decisionauto_mode || current_side == PIG_CPU
    True
    1. ExecutescheduleComputer(now_ms);
    False

    Continue after this decision.

Read original function source
static void changeTurn(uint32_t now_ms)
{
    turn_score = 0U;
    current_side = current_side == PIG_PLAYER ? PIG_CPU : PIG_PLAYER;
    selected_action = PIG_ROLL;
    setMessage(auto_mode ? (current_side == PIG_PLAYER ? "A TURN" : "B TURN") :
                           (current_side == PIG_PLAYER ? "YOUR TURN" : "CPU TURN"));
    if (auto_mode || current_side == PIG_CPU) scheduleComputer(now_ms);
}
beginGame() · source line 94
static void beginGame(uint32_t now_ms)

Calls into this documentation:

  1. Executeplayer_score = 0U;
  2. Executecpu_score = 0U;
  3. Executeturn_score = 0U;
  4. Executedie_value = 0U;
  5. Executecurrent_side = PIG_PLAYER;
  6. Executeselected_action = PIG_ROLL;
  7. Executestate = PIG_PLAYING;
  8. ExecutesetMessage(auto_mode ? "A STARTS" : "YOUR TURN");
  9. Decisionauto_mode
    True
    1. ExecutescheduleComputer(now_ms);
    False

    Continue after this decision.

  10. Executeredraw_needed = true;
Read original function source
static void beginGame(uint32_t now_ms)
{
    player_score = 0U;
    cpu_score = 0U;
    turn_score = 0U;
    die_value = 0U;
    current_side = PIG_PLAYER;
    selected_action = PIG_ROLL;
    state = PIG_PLAYING;
    setMessage(auto_mode ? "A STARTS" : "YOUR TURN");
    if (auto_mode) scheduleComputer(now_ms);
    redraw_needed = true;
}
rollCurrentTurn() · source line 108
static void rollCurrentTurn(uint32_t now_ms)

Calls into this documentation:

  1. Executedie_value = (uint8_t)(esp_random() % 6U) + 1U;
  2. ExecuteaudioPlay(AUDIO_SFX_DICE_ROLL);
  3. Decisiondie_value == 1U
    True
      1. ExecutechangeTurn(now_ms);
      2. ExecutesetMessage("PIG! TURN LOST");
    False
      1. Executeturn_score += die_value;
      2. Executesnprintf(game_message, sizeof(game_message), "ROLLED %u", (unsigned)die_value);
      3. Decisionauto_mode || current_side == PIG_CPU
        True
        1. ExecutescheduleComputer(now_ms);
        False

        Continue after this decision.

  4. Executeredraw_needed = true;
Read original function source
static void rollCurrentTurn(uint32_t now_ms)
{
    die_value = (uint8_t)(esp_random() % 6U) + 1U;
    audioPlay(AUDIO_SFX_DICE_ROLL);
    if (die_value == 1U) {
        changeTurn(now_ms);
        setMessage("PIG! TURN LOST");
    } else {
        turn_score += die_value;
        snprintf(game_message, sizeof(game_message), "ROLLED %u", (unsigned)die_value);
        if (auto_mode || current_side == PIG_CPU) scheduleComputer(now_ms);
    }
    redraw_needed = true;
}
holdCurrentTurn() · source line 123
static void holdCurrentTurn(uint32_t now_ms)

Calls into this documentation:

  1. ExecuteaudioPlay(AUDIO_SFX_DICE_HOLD);
  2. Executeuint16_t *score = current_side == PIG_PLAYER ? &player_score : &cpu_score;
  3. Execute*score += turn_score;
  4. Executeconst pig_side_t holding_side = current_side;
  5. Executeturn_score = 0U;
  6. Decision*score >= PIG_TARGET_SCORE
    True
      1. ExecutefinishGame(holding_side, now_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  7. ExecutechangeTurn(now_ms);
  8. ExecutesetMessage("SCORE BANKED");
  9. Executeredraw_needed = true;
Read original function source
static void holdCurrentTurn(uint32_t now_ms)
{
    audioPlay(AUDIO_SFX_DICE_HOLD);
    uint16_t *score = current_side == PIG_PLAYER ? &player_score : &cpu_score;
    *score += turn_score;
    const pig_side_t holding_side = current_side;
    turn_score = 0U;
    if (*score >= PIG_TARGET_SCORE) {
        finishGame(holding_side, now_ms);
        return;
    }
    changeTurn(now_ms);
    setMessage("SCORE BANKED");
    redraw_needed = true;
}
computerShouldHold() · source line 139
static bool computerShouldHold(void)
  1. Executeconst uint16_t own = current_side == PIG_PLAYER ? player_score : cpu_score;
  2. Executeconst uint16_t other = current_side == PIG_PLAYER ? cpu_score : player_score;
  3. Decisionown + turn_score >= PIG_TARGET_SCORE
    True
    1. Exit this functionreturn true;
    False

    Continue after this decision.

  4. Executeuint16_t hold_at = 20U;
  5. Decisionown > other + 25U
    True
    1. Executehold_at = 15U;
    False
    1. Decisionother >= 85U || other > own + 25U
      True
      1. Executehold_at = 25U;
      False

      Continue after this decision.

  6. Exit this functionreturn turn_score >= hold_at;
Read original function source
static bool computerShouldHold(void)
{
    const uint16_t own = current_side == PIG_PLAYER ? player_score : cpu_score;
    const uint16_t other = current_side == PIG_PLAYER ? cpu_score : player_score;
    if (own + turn_score >= PIG_TARGET_SCORE) return true;
    uint16_t hold_at = 20U;
    if (own > other + 25U) hold_at = 15U;
    else if (other >= 85U || other > own + 25U) hold_at = 25U;
    return turn_score >= hold_at;
}
startPig() · source line 150
void startPig(uint32_t now_ms)

Calls into this documentation:

  1. ExecuteloadScores();
  2. Executerunning = true;
  3. Executeauto_mode = false;
  4. Executeai_speed = AI_SPEED_DEFAULT_INDEX;
  5. ExecutemodeSelectionReset(&mode_input);
  6. Executestate = PIG_SCORES;
  7. Executestate_started = now_ms;
  8. Executeredraw_needed = true;
Read original function source
void startPig(uint32_t now_ms)
{
    loadScores();
    running = true;
    auto_mode = false;
    ai_speed = AI_SPEED_DEFAULT_INDEX;
    modeSelectionReset(&mode_input);
    state = PIG_SCORES;
    state_started = now_ms;
    redraw_needed = true;
}
stopPig() · source line 162
void stopPig(void)
  1. Executerunning = false;
Read original function source
void stopPig(void) { running = false; }
pigAction() · source line 164
void pigAction(bool next, bool speed, bool select, uint32_t now_ms)

Calls into this documentation:

  1. Decision!running
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionstate == PIG_MODE
    True
      1. Decisionnext
        True
        1. ExecutebeginGame(now_ms);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionauto_mode
    True
      1. Decisionspeed
        True
          1. ExecutecycleAISpeed(&ai_speed);
          2. ExecutescheduleComputer(now_ms);
          3. Executeredraw_needed = true;
        False

        Continue after this decision.

      2. Decisionselect
        True
          1. Executeauto_mode = false;
          2. Executestate = PIG_MODE;
          3. ExecutemodeSelectionReset(&mode_input);
          4. Executeredraw_needed = true;
        False

        Continue after this decision.

      3. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisionstate == PIG_FINISHED && select
    True
      1. ExecutebeginGame(now_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionstate == PIG_PLAYING && current_side == PIG_PLAYER
    True
      1. Decisionnext
        True
          1. Executeselected_action = selected_action == PIG_ROLL ? PIG_HOLD : PIG_ROLL;
          2. Executeredraw_needed = true;
        False

        Continue after this decision.

      2. Decisionselect
        True
          1. Decisionselected_action == PIG_ROLL
            True
            1. ExecuterollCurrentTurn(now_ms);
            False
            1. ExecuteholdCurrentTurn(now_ms);
        False

        Continue after this decision.

    False

    Continue after this decision.

Read original function source
void pigAction(bool next, bool speed, bool select, uint32_t now_ms)
{
    if (!running) return;
    if (state == PIG_MODE) {
        if (next) beginGame(now_ms);
        return;
    }
    if (auto_mode) {
        if (speed) { cycleAISpeed(&ai_speed); scheduleComputer(now_ms); redraw_needed = true; }
        if (select) { auto_mode = false; state = PIG_MODE; modeSelectionReset(&mode_input); redraw_needed = true; }
        return;
    }
    if (state == PIG_FINISHED && select) { beginGame(now_ms); return; }
    if (state == PIG_PLAYING && current_side == PIG_PLAYER) {
        if (next) { selected_action = selected_action == PIG_ROLL ? PIG_HOLD : PIG_ROLL; redraw_needed = true; }
        if (select) {
            if (selected_action == PIG_ROLL) rollCurrentTurn(now_ms);
            else holdCurrentTurn(now_ms);
        }
    }
}
updatePig() · source line 186
void updatePig(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
               uint32_t now_ms)

Calls into this documentation:

  1. Decision!running
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionstate == PIG_SCORES && now_ms - state_started >= PIG_SHOW_SCORES_MS
    True
      1. Executestate = PIG_MODE;
      2. Executeredraw_needed = true;
      3. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionstate == PIG_MODE
    True
      1. DecisionmodeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right, now_ms)
        True
          1. Executeauto_mode = !auto_mode;
          2. Executeredraw_needed = true;
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisionstate == PIG_FINISHED && auto_mode
    True
      1. Executeconst uint32_t delay = aiRestartDelay();
      2. Executeconst uint32_t elapsed = now_ms - auto_restart_started;
      3. Decisionelapsed >= delay
        True
          1. ExecutebeginGame(now_ms);
          2. Exit this functionreturn;
        False

        Continue after this decision.

      4. Executeconst uint8_t seconds_left = (uint8_t)((delay - elapsed + 999U) / 1000U);
      5. Decisionseconds_left != auto_seconds_shown
        True
          1. Executeauto_seconds_shown = seconds_left;
          2. Executeredraw_needed = true;
        False

        Continue after this decision.

      6. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionstate == PIG_PLAYING && (auto_mode || current_side == PIG_CPU) && now_ms - state_started >= aiSpeedDelay(ai_speed)
    True
      1. DecisioncomputerShouldHold()
        True
        1. ExecuteholdCurrentTurn(now_ms);
        False
        1. ExecuterollCurrentTurn(now_ms);
    False

    Continue after this decision.

Read original function source
void updatePig(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
               uint32_t now_ms)
{
    if (!running) return;
    if (state == PIG_SCORES && now_ms - state_started >= PIG_SHOW_SCORES_MS) {
        state = PIG_MODE;
        redraw_needed = true;
        return;
    }
    if (state == PIG_MODE) {
        if (modeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right, now_ms)) {
            auto_mode = !auto_mode;
            redraw_needed = true;
        }
        return;
    }
    if (state == PIG_FINISHED && auto_mode) {
        const uint32_t delay = aiRestartDelay();
        const uint32_t elapsed = now_ms - auto_restart_started;
        if (elapsed >= delay) { beginGame(now_ms); return; }
        const uint8_t seconds_left = (uint8_t)((delay - elapsed + 999U) / 1000U);
        if (seconds_left != auto_seconds_shown) { auto_seconds_shown = seconds_left; redraw_needed = true; }
        return;
    }
    if (state == PIG_PLAYING && (auto_mode || current_side == PIG_CPU) &&
        now_ms - state_started >= aiSpeedDelay(ai_speed)) {
        if (computerShouldHold()) holdCurrentTurn(now_ms);
        else rollCurrentTurn(now_ms);
    }
}
drawPip() · source line 217
static void drawPip(uint16_t *frame, int x, int y)

Calls into this documentation:

  1. Executefill_circle(frame, x, y, 4, COLOR_WHITE);
Read original function source
static void drawPip(uint16_t *frame, int x, int y) { fill_circle(frame, x, y, 4, COLOR_WHITE); }
drawDie() · source line 219
static void drawDie(uint16_t *frame)

Calls into this documentation:

  1. Executeconst int x = 126, y = 48, size = 68;
  2. Executefill_rect(frame, x, y, size, size, COLOR_DARKGREY);
  3. Executedraw_rect(frame, x, y, size, size, COLOR_WHITE);
  4. Decisiondie_value == 0U
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisiondie_value == 1U || die_value == 3U || die_value == 5U
    True
    1. ExecutedrawPip(frame, x + 34, y + 34);
    False

    Continue after this decision.

  6. Decisiondie_value >= 2U
    True
      1. ExecutedrawPip(frame, x + 17, y + 17);
      2. ExecutedrawPip(frame, x + 51, y + 51);
    False

    Continue after this decision.

  7. Decisiondie_value >= 4U
    True
      1. ExecutedrawPip(frame, x + 51, y + 17);
      2. ExecutedrawPip(frame, x + 17, y + 51);
    False

    Continue after this decision.

  8. Decisiondie_value == 6U
    True
      1. ExecutedrawPip(frame, x + 17, y + 34);
      2. ExecutedrawPip(frame, x + 51, y + 34);
    False

    Continue after this decision.

Read original function source
static void drawDie(uint16_t *frame)
{
    const int x = 126, y = 48, size = 68;
    fill_rect(frame, x, y, size, size, COLOR_DARKGREY);
    draw_rect(frame, x, y, size, size, COLOR_WHITE);
    if (die_value == 0U) return;
    if (die_value == 1U || die_value == 3U || die_value == 5U) drawPip(frame, x + 34, y + 34);
    if (die_value >= 2U) { drawPip(frame, x + 17, y + 17); drawPip(frame, x + 51, y + 51); }
    if (die_value >= 4U) { drawPip(frame, x + 51, y + 17); drawPip(frame, x + 17, y + 51); }
    if (die_value == 6U) { drawPip(frame, x + 17, y + 34); drawPip(frame, x + 51, y + 34); }
}
drawPig() · source line 231
void drawPig(uint16_t *frame)

Calls into this documentation:

  1. Decision!running || !redraw_needed
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeredraw_needed = false;
  3. Executeclear(frame, COLOR_BLACK);
  4. Executechar text[40];
  5. Decisionstate == PIG_SCORES
    True
      1. Executedraw_text_centred(frame, 160, 8, "PIG", 4, COLOR_CYAN);
      2. Executedraw_text_centred(frame, 160, 45, "GAME RECORD", 2, COLOR_LIGHTGRAY);
      3. Executesnprintf(text, sizeof(text), "WINS %u LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses);
      4. Executedraw_text_centred(frame, 160, 78, text, 2, COLOR_WHITE);
      5. Executesnprintf(text, sizeof(text), "STREAK %u", (unsigned)scores.streak);
      6. Executedraw_text_centred(frame, 160, 108, text, 2, COLOR_GOLD);
      7. Executedraw_text_centred(frame, 160, 140, "FIRST TO 100", 1, COLOR_LIGHTGRAY);
      8. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionstate == PIG_MODE
    True
      1. Executedraw_text_centred(frame, 160, 8, "PIG", 4, COLOR_CYAN);
      2. Executedraw_text_centred(frame, 160, 52, "SELECT MODE", 2, COLOR_LIGHTGRAY);
      3. Executedraw_text_centred(frame, 100, 84, "PLAY", 2, auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW);
      4. Executedraw_text_centred(frame, 220, 84, "AUTO", 2, auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY);
      5. Executedraw_text_centred(frame, 160, 115, "WIN 10 GOLD", 2, COLOR_GOLD);
      6. Executedraw_text_centred(frame, 160, 145, "D/B/STICK CHANGE A/K START", 1, COLOR_LIGHTGRAY);
      7. Exit this functionreturn;
    False

    Continue after this decision.

  7. Executedraw_text(frame, 6, 3, "PIG", 3, COLOR_CYAN);
  8. Executedraw_text(frame, 65, 12, "FIRST TO 100", 1, COLOR_LIGHTGRAY);
  9. ExecutedrawGameGold(frame, 260, 2, 55, true);
  10. Executedraw_text_centred(frame, 50, 40, auto_mode ? "A" : "YOU", 2, current_side == PIG_PLAYER ? COLOR_YELLOW : COLOR_CYAN);
  11. Executedraw_text_centred(frame, 270, 40, auto_mode ? "B" : "CPU", 2, current_side == PIG_CPU ? COLOR_YELLOW : COLOR_CYAN);
  12. Executesnprintf(text, sizeof(text), "%u", (unsigned)player_score);
  13. Executedraw_text_centred(frame, 50, 68, text, 4, COLOR_WHITE);
  14. Executesnprintf(text, sizeof(text), "%u", (unsigned)cpu_score);
  15. Executedraw_text_centred(frame, 270, 68, text, 4, COLOR_WHITE);
  16. ExecutedrawDie(frame);
  17. Executesnprintf(text, sizeof(text), "TURN %u", (unsigned)turn_score);
  18. Executedraw_text_centred(frame, 160, 119, text, 2, COLOR_CYAN);
  19. Executedraw_text_centred(frame, 160, 137, game_message, 1, COLOR_WHITE);
  20. Decisionstate == PIG_PLAYING && !auto_mode && current_side == PIG_PLAYER
    True
      1. Executedraw_text_centred(frame, 80, 155, "ROLL", 2, selected_action == PIG_ROLL ? COLOR_YELLOW : COLOR_WHITE);
      2. Executedraw_text_centred(frame, 240, 155, "HOLD", 2, selected_action == PIG_HOLD ? COLOR_YELLOW : COLOR_WHITE);
    False
    1. Decisionauto_mode
      True
        1. Decisionstate == PIG_FINISHED
          True
          1. Executesnprintf(text, sizeof(text), "NEXT GAME %u E SPEED C STOP", (unsigned)auto_seconds_shown);
          False
          1. Executesnprintf(text, sizeof(text), "E SPEED %s C STOP", aiSpeedShortText(ai_speed));
        2. Executedraw_text_centred(frame, 160, 155, text, 1, COLOR_LIGHTGRAY);
      False
      1. Decisionstate == PIG_FINISHED
        True
        1. Executedraw_text_centred(frame, 160, 155, "C NEW GAME", 1, COLOR_LIGHTGRAY);
        False
        1. Executedraw_text_centred(frame, 160, 155, "CPU THINKING", 1, COLOR_LIGHTGRAY);
Read original function source
void drawPig(uint16_t *frame)
{
    if (!running || !redraw_needed) return;
    redraw_needed = false;
    clear(frame, COLOR_BLACK);
    char text[40];
    if (state == PIG_SCORES) {
        draw_text_centred(frame, 160, 8, "PIG", 4, COLOR_CYAN);
        draw_text_centred(frame, 160, 45, "GAME RECORD", 2, COLOR_LIGHTGRAY);
        snprintf(text, sizeof(text), "WINS %u    LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses);
        draw_text_centred(frame, 160, 78, text, 2, COLOR_WHITE);
        snprintf(text, sizeof(text), "STREAK %u", (unsigned)scores.streak);
        draw_text_centred(frame, 160, 108, text, 2, COLOR_GOLD);
        draw_text_centred(frame, 160, 140, "FIRST TO 100", 1, COLOR_LIGHTGRAY);
        return;
    }
    if (state == PIG_MODE) {
        draw_text_centred(frame, 160, 8, "PIG", 4, COLOR_CYAN);
        draw_text_centred(frame, 160, 52, "SELECT MODE", 2, COLOR_LIGHTGRAY);
        draw_text_centred(frame, 100, 84, "PLAY", 2, auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW);
        draw_text_centred(frame, 220, 84, "AUTO", 2, auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY);
        draw_text_centred(frame, 160, 115, "WIN 10 GOLD", 2, COLOR_GOLD);
        draw_text_centred(frame, 160, 145, "D/B/STICK CHANGE  A/K START", 1, COLOR_LIGHTGRAY);
        return;
    }
    draw_text(frame, 6, 3, "PIG", 3, COLOR_CYAN);
    draw_text(frame, 65, 12, "FIRST TO 100", 1, COLOR_LIGHTGRAY);
    drawGameGold(frame, 260, 2, 55, true);
    draw_text_centred(frame, 50, 40, auto_mode ? "A" : "YOU", 2, current_side == PIG_PLAYER ? COLOR_YELLOW : COLOR_CYAN);
    draw_text_centred(frame, 270, 40, auto_mode ? "B" : "CPU", 2, current_side == PIG_CPU ? COLOR_YELLOW : COLOR_CYAN);
    snprintf(text, sizeof(text), "%u", (unsigned)player_score); draw_text_centred(frame, 50, 68, text, 4, COLOR_WHITE);
    snprintf(text, sizeof(text), "%u", (unsigned)cpu_score); draw_text_centred(frame, 270, 68, text, 4, COLOR_WHITE);
    drawDie(frame);
    snprintf(text, sizeof(text), "TURN %u", (unsigned)turn_score); draw_text_centred(frame, 160, 119, text, 2, COLOR_CYAN);
    draw_text_centred(frame, 160, 137, game_message, 1, COLOR_WHITE);
    if (state == PIG_PLAYING && !auto_mode && current_side == PIG_PLAYER) {
        draw_text_centred(frame, 80, 155, "ROLL", 2, selected_action == PIG_ROLL ? COLOR_YELLOW : COLOR_WHITE);
        draw_text_centred(frame, 240, 155, "HOLD", 2, selected_action == PIG_HOLD ? COLOR_YELLOW : COLOR_WHITE);
    } else if (auto_mode) {
        if (state == PIG_FINISHED) snprintf(text, sizeof(text), "NEXT GAME %u  E SPEED  C STOP", (unsigned)auto_seconds_shown);
        else snprintf(text, sizeof(text), "E SPEED %s  C STOP", aiSpeedShortText(ai_speed));
        draw_text_centred(frame, 160, 155, text, 1, COLOR_LIGHTGRAY);
    } else if (state == PIG_FINISHED) draw_text_centred(frame, 160, 155, "C NEW GAME", 1, COLOR_LIGHTGRAY);
    else draw_text_centred(frame, 160, 155, "CPU THINKING", 1, COLOR_LIGHTGRAY);
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "pig.h"

#include "ai_speed.h"
#include "audio.h"
#include "game_graphics.h"
#include "game_storage.h"
#include "gold.h"
#include "mode_selection.h"
#include "PET2001_font.h"
#include "esp_random.h"
#include <stdio.h>

#define PIG_TARGET_SCORE 100U
#define PIG_SHOW_SCORES_MS 2000U
#define PIG_WIN_GOLD 10U
#define PIG_SCORE_NAMESPACE "arcade_results"
#define PIG_SCORE_KEY "pig"

typedef enum { PIG_SCORES, PIG_MODE, PIG_PLAYING, PIG_FINISHED } pig_state_t;
typedef enum { PIG_PLAYER, PIG_CPU } pig_side_t;
typedef enum { PIG_ROLL, PIG_HOLD } pig_action_t;

typedef struct {
    uint32_t version;
    uint32_t wins;
    uint32_t losses;
    uint32_t streak;
} pig_scores_t;

static pig_scores_t scores;
static pig_state_t state;
static pig_side_t current_side;
static pig_action_t selected_action;
static uint16_t player_score, cpu_score, turn_score;
static uint8_t die_value, ai_speed, auto_seconds_shown;
static uint32_t state_started, auto_restart_started;
static bool running, auto_mode, redraw_needed;
static mode_selection_input_t mode_input;
static char game_message[32];

static void setMessage(const char *message)
{
    snprintf(game_message, sizeof(game_message), "%s", message);
}

static void loadScores(void)
{
    bool found = false;
    scores = (pig_scores_t){ .version = 1U };
    if (!game_storage_read(PIG_SCORE_NAMESPACE, PIG_SCORE_KEY, &scores, sizeof(scores), &found) ||
        !found || scores.version != 1U) scores = (pig_scores_t){ .version = 1U };
}

static void saveScores(void)
{
    (void)game_storage_write(PIG_SCORE_NAMESPACE, PIG_SCORE_KEY, &scores, sizeof(scores));
}

static void finishGame(pig_side_t winner, uint32_t now_ms)
{
    audioPlay(winner == PIG_PLAYER ? AUDIO_SFX_WAVE_CLEAR : AUDIO_SFX_GAME_OVER);
    state = PIG_FINISHED;
    turn_score = 0U;
    auto_restart_started = now_ms;
    auto_seconds_shown = (uint8_t)((aiRestartDelay() + 999U) / 1000U);

    if (auto_mode) setMessage(winner == PIG_PLAYER ? "A WINS" : "B WINS");
    else if (winner == PIG_PLAYER) {
        ++scores.wins;
        ++scores.streak;
        addUserGoldWon(PIG_WIN_GOLD);
        saveScores();
        setMessage("YOU WIN +10 GOLD");
    } else {
        ++scores.losses;
        scores.streak = 0U;
        saveScores();
        setMessage("CPU WINS");
    }
    redraw_needed = true;
}

static void scheduleComputer(uint32_t now_ms) { state_started = now_ms; }

static void changeTurn(uint32_t now_ms)
{
    turn_score = 0U;
    current_side = current_side == PIG_PLAYER ? PIG_CPU : PIG_PLAYER;
    selected_action = PIG_ROLL;
    setMessage(auto_mode ? (current_side == PIG_PLAYER ? "A TURN" : "B TURN") :
                           (current_side == PIG_PLAYER ? "YOUR TURN" : "CPU TURN"));
    if (auto_mode || current_side == PIG_CPU) scheduleComputer(now_ms);
}

static void beginGame(uint32_t now_ms)
{
    player_score = 0U;
    cpu_score = 0U;
    turn_score = 0U;
    die_value = 0U;
    current_side = PIG_PLAYER;
    selected_action = PIG_ROLL;
    state = PIG_PLAYING;
    setMessage(auto_mode ? "A STARTS" : "YOUR TURN");
    if (auto_mode) scheduleComputer(now_ms);
    redraw_needed = true;
}

static void rollCurrentTurn(uint32_t now_ms)
{
    die_value = (uint8_t)(esp_random() % 6U) + 1U;
    audioPlay(AUDIO_SFX_DICE_ROLL);
    if (die_value == 1U) {
        changeTurn(now_ms);
        setMessage("PIG! TURN LOST");
    } else {
        turn_score += die_value;
        snprintf(game_message, sizeof(game_message), "ROLLED %u", (unsigned)die_value);
        if (auto_mode || current_side == PIG_CPU) scheduleComputer(now_ms);
    }
    redraw_needed = true;
}

static void holdCurrentTurn(uint32_t now_ms)
{
    audioPlay(AUDIO_SFX_DICE_HOLD);
    uint16_t *score = current_side == PIG_PLAYER ? &player_score : &cpu_score;
    *score += turn_score;
    const pig_side_t holding_side = current_side;
    turn_score = 0U;
    if (*score >= PIG_TARGET_SCORE) {
        finishGame(holding_side, now_ms);
        return;
    }
    changeTurn(now_ms);
    setMessage("SCORE BANKED");
    redraw_needed = true;
}

static bool computerShouldHold(void)
{
    const uint16_t own = current_side == PIG_PLAYER ? player_score : cpu_score;
    const uint16_t other = current_side == PIG_PLAYER ? cpu_score : player_score;
    if (own + turn_score >= PIG_TARGET_SCORE) return true;
    uint16_t hold_at = 20U;
    if (own > other + 25U) hold_at = 15U;
    else if (other >= 85U || other > own + 25U) hold_at = 25U;
    return turn_score >= hold_at;
}

void startPig(uint32_t now_ms)
{
    loadScores();
    running = true;
    auto_mode = false;
    ai_speed = AI_SPEED_DEFAULT_INDEX;
    modeSelectionReset(&mode_input);
    state = PIG_SCORES;
    state_started = now_ms;
    redraw_needed = true;
}

void stopPig(void) { running = false; }

void pigAction(bool next, bool speed, bool select, uint32_t now_ms)
{
    if (!running) return;
    if (state == PIG_MODE) {
        if (next) beginGame(now_ms);
        return;
    }
    if (auto_mode) {
        if (speed) { cycleAISpeed(&ai_speed); scheduleComputer(now_ms); redraw_needed = true; }
        if (select) { auto_mode = false; state = PIG_MODE; modeSelectionReset(&mode_input); redraw_needed = true; }
        return;
    }
    if (state == PIG_FINISHED && select) { beginGame(now_ms); return; }
    if (state == PIG_PLAYING && current_side == PIG_PLAYER) {
        if (next) { selected_action = selected_action == PIG_ROLL ? PIG_HOLD : PIG_ROLL; redraw_needed = true; }
        if (select) {
            if (selected_action == PIG_ROLL) rollCurrentTurn(now_ms);
            else holdCurrentTurn(now_ms);
        }
    }
}

void updatePig(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
               uint32_t now_ms)
{
    if (!running) return;
    if (state == PIG_SCORES && now_ms - state_started >= PIG_SHOW_SCORES_MS) {
        state = PIG_MODE;
        redraw_needed = true;
        return;
    }
    if (state == PIG_MODE) {
        if (modeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right, now_ms)) {
            auto_mode = !auto_mode;
            redraw_needed = true;
        }
        return;
    }
    if (state == PIG_FINISHED && auto_mode) {
        const uint32_t delay = aiRestartDelay();
        const uint32_t elapsed = now_ms - auto_restart_started;
        if (elapsed >= delay) { beginGame(now_ms); return; }
        const uint8_t seconds_left = (uint8_t)((delay - elapsed + 999U) / 1000U);
        if (seconds_left != auto_seconds_shown) { auto_seconds_shown = seconds_left; redraw_needed = true; }
        return;
    }
    if (state == PIG_PLAYING && (auto_mode || current_side == PIG_CPU) &&
        now_ms - state_started >= aiSpeedDelay(ai_speed)) {
        if (computerShouldHold()) holdCurrentTurn(now_ms);
        else rollCurrentTurn(now_ms);
    }
}

static void drawPip(uint16_t *frame, int x, int y) { fill_circle(frame, x, y, 4, COLOR_WHITE); }

static void drawDie(uint16_t *frame)
{
    const int x = 126, y = 48, size = 68;
    fill_rect(frame, x, y, size, size, COLOR_DARKGREY);
    draw_rect(frame, x, y, size, size, COLOR_WHITE);
    if (die_value == 0U) return;
    if (die_value == 1U || die_value == 3U || die_value == 5U) drawPip(frame, x + 34, y + 34);
    if (die_value >= 2U) { drawPip(frame, x + 17, y + 17); drawPip(frame, x + 51, y + 51); }
    if (die_value >= 4U) { drawPip(frame, x + 51, y + 17); drawPip(frame, x + 17, y + 51); }
    if (die_value == 6U) { drawPip(frame, x + 17, y + 34); drawPip(frame, x + 51, y + 34); }
}

void drawPig(uint16_t *frame)
{
    if (!running || !redraw_needed) return;
    redraw_needed = false;
    clear(frame, COLOR_BLACK);
    char text[40];
    if (state == PIG_SCORES) {
        drawPet2001TextCentredTransparent(frame, 160, 8, "PIG", COLOR_CYAN, 4);
        drawPet2001TextCentredTransparent(frame, 160, 45, "GAME RECORD", COLOR_LIGHTGRAY, 2);
        snprintf(text, sizeof(text), "WINS %u    LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses);
        drawPet2001TextCentredTransparent(frame, 160, 78, text, COLOR_WHITE, 1);
        snprintf(text, sizeof(text), "STREAK %u", (unsigned)scores.streak);
        drawPet2001TextCentredTransparent(frame, 160, 108, text, COLOR_GOLD, 1);
        drawPet2001TextCentredTransparent(frame, 160, 140, "FIRST TO 100", COLOR_LIGHTGRAY, 1);
        return;
    }
    if (state == PIG_MODE) {
        drawPet2001TextCentredTransparent(frame, 160, 8, "PIG", COLOR_CYAN, 4);
        drawPet2001TextCentredTransparent(frame, 160, 52, "SELECT MODE", COLOR_LIGHTGRAY, 2);
        drawPet2001TextCentredTransparent(frame, 100, 84, "PLAY", auto_mode ? COLOR_LIGHTGRAY : COLOR_YELLOW, 2);
        drawPet2001TextCentredTransparent(frame, 220, 84, "AUTO", auto_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY, 2);
        drawPet2001TextCentredTransparent(frame, 160, 115, "WIN 10 GOLD", COLOR_GOLD, 2);
        drawPet2001TextCentredTransparent(frame, 160, 145, "D/B/STICK CHANGE  A/K START", COLOR_LIGHTGRAY, 1);
        return;
    }
    drawPet2001TextTransparent(frame, 6, 3, "PIG", COLOR_CYAN, 3);
    drawPet2001TextTransparent(frame, 84, 12, "FIRST TO 100", COLOR_LIGHTGRAY, 1);
    drawGameGold(frame, 260, 2, 55, true);
    drawPet2001TextCentredTransparent(frame, 50, 40, auto_mode ? "A" : "YOU", current_side == PIG_PLAYER ? COLOR_YELLOW : COLOR_CYAN, 2);
    drawPet2001TextCentredTransparent(frame, 270, 40, auto_mode ? "B" : "CPU", current_side == PIG_CPU ? COLOR_YELLOW : COLOR_CYAN, 2);
    snprintf(text, sizeof(text), "%u", (unsigned)player_score); drawPet2001TextCentredTransparent(frame, 50, 68, text, COLOR_WHITE, 4);
    snprintf(text, sizeof(text), "%u", (unsigned)cpu_score); drawPet2001TextCentredTransparent(frame, 270, 68, text, COLOR_WHITE, 4);
    drawDie(frame);
    snprintf(text, sizeof(text), "TURN %u", (unsigned)turn_score); drawPet2001TextCentredTransparent(frame, 160, 119, text, COLOR_CYAN, 2);
    drawPet2001TextCentredTransparent(frame, 160, 137, game_message, COLOR_WHITE, 1);
    if (state == PIG_PLAYING && !auto_mode && current_side == PIG_PLAYER) {
        drawPet2001TextCentredTransparent(frame, 80, 155, "ROLL", selected_action == PIG_ROLL ? COLOR_YELLOW : COLOR_WHITE, 2);
        drawPet2001TextCentredTransparent(frame, 240, 155, "HOLD", selected_action == PIG_HOLD ? COLOR_YELLOW : COLOR_WHITE, 2);
    } else if (auto_mode) {
        if (state == PIG_FINISHED) snprintf(text, sizeof(text), "NEXT GAME %u  E SPEED  C STOP", (unsigned)auto_seconds_shown);
        else snprintf(text, sizeof(text), "E SPEED %s  C STOP", aiSpeedShortText(ai_speed));
        drawPet2001TextCentredTransparent(frame, 160, 155, text, COLOR_LIGHTGRAY, 1);
    } else if (state == PIG_FINISHED) drawPet2001TextCentredTransparent(frame, 160, 155, "C NEW GAME", COLOR_LIGHTGRAY, 1);
    else drawPet2001TextCentredTransparent(frame, 160, 155, "CPU THINKING", COLOR_LIGHTGRAY, 1);
}