Blackjack

Source: src/blackjack.c · 300 lines · 19 functions.

Screen captures

Views of this part running on the Mini Arcade.

Blackjack game screen on the Mini Arcade
Blackjack showing the card table and current hand.

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

cardValue() · source line 47
static uint8_t cardValue(card_t card)
  1. Exit this functionreturn card.rank == 1U ? 11U : card.rank >= 10U ? 10U : card.rank;
Read original function source
static uint8_t cardValue(card_t card)
{
    return card.rank == 1U ? 11U : card.rank >= 10U ? 10U : card.rank;
}
handValue() · source line 52
static uint8_t handValue(const hand_t *hand, uint8_t visible_count)

Calls into this documentation:

  1. Decisionvisible_count == 0U || visible_count > hand->count
    True
    1. Executevisible_count = hand->count;
    False

    Continue after this decision.

  2. Executeuint8_t total = 0U, aces = 0U;
  3. FOR loopuint8_t index = 0U; index < visible_count; ++index

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

      1. Executetotal += cardValue(hand->cards[index]);
      2. Decisionhand->cards[index].rank == 1U
        True
        1. Execute++aces;
        False

        Continue after this decision.

  4. WHILE looptotal > 21U && aces > 0U

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

      1. Executetotal -= 10U;
      2. Execute--aces;
  5. Exit this functionreturn total;
Read original function source
static uint8_t handValue(const hand_t *hand, uint8_t visible_count)
{
    if (visible_count == 0U || visible_count > hand->count) visible_count = hand->count;
    uint8_t total = 0U, aces = 0U;
    for (uint8_t index = 0U; index < visible_count; ++index) {
        total += cardValue(hand->cards[index]);
        if (hand->cards[index].rank == 1U) ++aces;
    }
    while (total > 21U && aces > 0U) { total -= 10U; --aces; }
    return total;
}
usableAce() · source line 64
static bool usableAce(const hand_t *hand)

Calls into this documentation:

  1. Executeuint8_t total = 0U, aces = 0U;
  2. FOR loopuint8_t index = 0U; index < hand->count; ++index

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

      1. Executetotal += cardValue(hand->cards[index]);
      2. Decisionhand->cards[index].rank == 1U
        True
        1. Execute++aces;
        False

        Continue after this decision.

  3. WHILE looptotal > 21U && aces > 0U

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

      1. Executetotal -= 10U;
      2. Execute--aces;
  4. Exit this functionreturn aces > 0U;
Read original function source
static bool usableAce(const hand_t *hand)
{
    uint8_t total = 0U, aces = 0U;
    for (uint8_t index = 0U; index < hand->count; ++index) {
        total += cardValue(hand->cards[index]);
        if (hand->cards[index].rank == 1U) ++aces;
    }
    while (total > 21U && aces > 0U) { total -= 10U; --aces; }
    return aces > 0U;
}
naturalBlackjack() · source line 75
static bool naturalBlackjack(const hand_t *hand)

Calls into this documentation:

  1. Exit this functionreturn hand->count == 2U && handValue(hand, 0U) == 21U;
Read original function source
static bool naturalBlackjack(const hand_t *hand) { return hand->count == 2U && handValue(hand, 0U) == 21U; }
autoShouldHit() · source line 77
static bool autoShouldHit(void)

Calls into this documentation:

  1. Executeconst uint8_t total = handValue(&player_hand, 0U);
  2. Executeconst uint8_t dealer_card = cardValue(dealer_hand.cards[0]);
  3. DecisionusableAce(&player_hand)
    True
    1. Exit this functionreturn total <= 17U || (total == 18U && dealer_card >= 9U);
    False

    Continue after this decision.

  4. Decisiontotal <= 11U
    True
    1. Exit this functionreturn true;
    False

    Continue after this decision.

  5. Decisiontotal == 12U
    True
    1. Exit this functionreturn dealer_card < 4U || dealer_card > 6U;
    False

    Continue after this decision.

  6. Exit this functionreturn total <= 16U && dealer_card >= 7U;
Read original function source
static bool autoShouldHit(void)
{
    const uint8_t total = handValue(&player_hand, 0U);
    const uint8_t dealer_card = cardValue(dealer_hand.cards[0]);
    if (usableAce(&player_hand)) return total <= 17U || (total == 18U && dealer_card >= 9U);
    if (total <= 11U) return true;
    if (total == 12U) return dealer_card < 4U || dealer_card > 6U;
    return total <= 16U && dealer_card >= 7U;
}
initialiseDeck() · source line 87
static void initialiseDeck(void)
  1. Executestatic const char suits[] = { 'C', 'D', 'H', 'S' };
  2. Executeuint8_t index = 0U;
  3. FOR loopuint8_t suit = 0U; suit < 4U; ++suit

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

    1. FOR loopuint8_t rank = 1U; rank <= 13U; ++rank

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

      1. Executedeck[index++] = (card_t){ rank, suits[suit] };
  4. FOR loopint current = DECK_SIZE - 1; current > 0; --current

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

      1. Executeconst uint8_t selected = esp_random() % (uint32_t)(current + 1);
      2. Executeconst card_t temporary = deck[current];
      3. Executedeck[current] = deck[selected];
      4. Executedeck[selected] = temporary;
  5. Executedeck_position = 0U;
Read original function source
static void initialiseDeck(void)
{
    static const char suits[] = { 'C', 'D', 'H', 'S' };
    uint8_t index = 0U;
    for (uint8_t suit = 0U; suit < 4U; ++suit)
        for (uint8_t rank = 1U; rank <= 13U; ++rank) deck[index++] = (card_t){ rank, suits[suit] };
    for (int current = DECK_SIZE - 1; current > 0; --current) {
        const uint8_t selected = esp_random() % (uint32_t)(current + 1);
        const card_t temporary = deck[current]; deck[current] = deck[selected]; deck[selected] = temporary;
    }
    deck_position = 0U;
}
dealCard() · source line 100
static bool dealCard(hand_t *hand)

Calls into this documentation:

  1. Decisionhand->count >= MAX_HAND_CARDS || deck_position >= DECK_SIZE
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Executehand->cards[hand->count++] = deck[deck_position++];
  3. ExecuteaudioPlay(AUDIO_SFX_CARD_DEAL);
  4. Exit this functionreturn true;
Read original function source
static bool dealCard(hand_t *hand)
{
    if (hand->count >= MAX_HAND_CARDS || deck_position >= DECK_SIZE) return false;
    hand->cards[hand->count++] = deck[deck_position++];
    audioPlay(AUDIO_SFX_CARD_DEAL);
    return true;
}
loadScores() · source line 108
static void loadScores(void)

Calls into this documentation:

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

    Continue after this decision.

Read original function source
static void loadScores(void)
{
    bool found = false;
    scores = (blackjack_scores_t){ .version = 1U };
    if (!game_storage_read(SCORE_NAMESPACE, SCORE_KEY, &scores, sizeof(scores), &found) ||
        !found || scores.version != 1U) scores = (blackjack_scores_t){ .version = 1U };
}
saveScores() · source line 116
static void saveScores(void)

Calls into this documentation:

  1. Execute(void)game_storage_write(SCORE_NAMESPACE, SCORE_KEY, &scores, sizeof(scores));
Read original function source
static void saveScores(void) { (void)game_storage_write(SCORE_NAMESPACE, SCORE_KEY, &scores, sizeof(scores)); }
finishGame() · source line 118
static void finishGame(blackjack_result_t result, const char *message, uint32_t now_ms)

Calls into this documentation:

  1. ExecuteaudioPlay(result == BJ_WIN ? AUDIO_SFX_WAVE_CLEAR : result == BJ_LOSS ? AUDIO_SFX_GAME_OVER : AUDIO_SFX_MENU_SELECT);
  2. Executesnprintf(result_text, sizeof(result_text), "%s", message);
  3. Decision!auto_mode
    True
      1. Decisionresult == BJ_WIN
        True
          1. Execute++scores.wins;
          2. Execute++scores.streak;
          3. Decisionscores.streak > scores.best_streak
            True
            1. Executescores.best_streak = scores.streak;
            False

            Continue after this decision.

        False
          1. Executescores.streak = 0U;
          2. Decisionresult == BJ_LOSS
            True
            1. Execute++scores.losses;
            False
            1. Execute++scores.draws;
      2. ExecutesaveScores();
    False

    Continue after this decision.

  4. Decisionresult == BJ_WIN
    True
      1. Decisionauto_mode
        True
        1. ExecuteaddAIGold(BLACKJACK_WIN_GOLD);
        False
        1. ExecuteaddUserGoldWon(BLACKJACK_WIN_GOLD);
    False

    Continue after this decision.

  5. Executestate = BJ_FINISHED;
  6. Executeauto_restart_started = now_ms;
  7. Executeauto_seconds_shown = (uint8_t)((aiRestartDelay() + 999U) / 1000U);
  8. Executeredraw_needed = true;
Read original function source
static void finishGame(blackjack_result_t result, const char *message, uint32_t now_ms)
{
    audioPlay(result == BJ_WIN ? AUDIO_SFX_WAVE_CLEAR : result == BJ_LOSS ? AUDIO_SFX_GAME_OVER : AUDIO_SFX_MENU_SELECT);
    snprintf(result_text, sizeof(result_text), "%s", message);
    if (!auto_mode) {
        if (result == BJ_WIN) { ++scores.wins; ++scores.streak; if (scores.streak > scores.best_streak) scores.best_streak = scores.streak; }
        else { scores.streak = 0U; if (result == BJ_LOSS) ++scores.losses; else ++scores.draws; }
        saveScores();
    }
    if (result == BJ_WIN) {
        if (auto_mode) addAIGold(BLACKJACK_WIN_GOLD); else addUserGoldWon(BLACKJACK_WIN_GOLD);
    }
    state = BJ_FINISHED;
    auto_restart_started = now_ms;
    auto_seconds_shown = (uint8_t)((aiRestartDelay() + 999U) / 1000U);
    redraw_needed = true;
}
startDealerTurn() · source line 136
static void startDealerTurn(uint32_t now_ms)
  1. Executestate = BJ_DEALER;
  2. Executestate_started = now_ms;
  3. Executeredraw_needed = true;
Read original function source
static void startDealerTurn(uint32_t now_ms)
{
    state = BJ_DEALER;
    state_started = now_ms;
    redraw_needed = true;
}
beginGame() · source line 143
static void beginGame(uint32_t now_ms)

Calls into this documentation:

  1. Executeplayer_hand = (hand_t){0};
  2. Executedealer_hand = (hand_t){0};
  3. Executeselected_action = 0U;
  4. Executeresult_text[0] = '\0';
  5. ExecuteinitialiseDeck();
  6. ExecutedealCard(&player_hand);
  7. ExecutedealCard(&dealer_hand);
  8. ExecutedealCard(&player_hand);
  9. ExecutedealCard(&dealer_hand);
  10. DecisionnaturalBlackjack(&player_hand) && naturalBlackjack(&dealer_hand)
    True
    1. ExecutefinishGame(BJ_DRAW, "BOTH BLACKJACK - DRAW", now_ms);
    False
    1. DecisionnaturalBlackjack(&player_hand)
      True
      1. ExecutefinishGame(BJ_WIN, "BLACKJACK - YOU WIN", now_ms);
      False
      1. DecisionnaturalBlackjack(&dealer_hand)
        True
        1. ExecutefinishGame(BJ_LOSS, "DEALER BLACKJACK", now_ms);
        False
          1. Executestate = BJ_PLAYER;
          2. Executestate_started = now_ms;
          3. Executeredraw_needed = true;
Read original function source
static void beginGame(uint32_t now_ms)
{
    player_hand = (hand_t){0}; dealer_hand = (hand_t){0}; selected_action = 0U; result_text[0] = '\0';
    initialiseDeck();
    dealCard(&player_hand); dealCard(&dealer_hand); dealCard(&player_hand); dealCard(&dealer_hand);
    if (naturalBlackjack(&player_hand) && naturalBlackjack(&dealer_hand)) finishGame(BJ_DRAW, "BOTH BLACKJACK - DRAW", now_ms);
    else if (naturalBlackjack(&player_hand)) finishGame(BJ_WIN, "BLACKJACK - YOU WIN", now_ms);
    else if (naturalBlackjack(&dealer_hand)) finishGame(BJ_LOSS, "DEALER BLACKJACK", now_ms);
    else { state = BJ_PLAYER; state_started = now_ms; redraw_needed = true; }
}
startBlackjack() · source line 154
void startBlackjack(uint32_t now_ms)

Calls into this documentation:

  1. ExecuteloadScores();
  2. Executeauto_mode = false;
  3. Executeai_speed = AI_SPEED_DEFAULT_INDEX;
  4. Executerunning = true;
  5. ExecutemodeSelectionReset(&mode_input);
  6. Executestate = BJ_SCORES;
  7. Executestate_started = now_ms;
  8. Executeredraw_needed = true;
Read original function source
void startBlackjack(uint32_t now_ms)
{
    loadScores();
    auto_mode = false; ai_speed = AI_SPEED_DEFAULT_INDEX; running = true;
    modeSelectionReset(&mode_input);
    state = BJ_SCORES; state_started = now_ms; redraw_needed = true;
}
stopBlackjack() · source line 162
void stopBlackjack(void)
  1. Executerunning = false;
Read original function source
void stopBlackjack(void) { running = false; }
blackjackAction() · source line 164
void blackjackAction(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 == BJ_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. Executestate_started = now_ms;
          3. Executeredraw_needed = true;
        False

        Continue after this decision.

      2. Decisionselect
        True
          1. Executeauto_mode = false;
          2. Executestate = BJ_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 == BJ_PLAYER
    True
      1. Decisionnext
        True
          1. Executeselected_action = selected_action == 0U ? 1U : 0U;
          2. Executeredraw_needed = true;
        False

        Continue after this decision.

      2. Decisionselect && selected_action == 1U
        True
        1. ExecutestartDealerTurn(now_ms);
        False
        1. Decisionselect && dealCard(&player_hand)
          True
            1. Executeconst uint8_t total = handValue(&player_hand, 0U);
            2. Decisiontotal > 21U
              True
              1. ExecutefinishGame(BJ_LOSS, "BUST - DEALER WINS", now_ms);
              False
              1. Decisiontotal == 21U
                True
                1. ExecutestartDealerTurn(now_ms);
                False
                1. Executeredraw_needed = true;
          False

          Continue after this decision.

    False
    1. Decisionstate == BJ_FINISHED && select
      True
      1. ExecutebeginGame(now_ms);
      False

      Continue after this decision.

Read original function source
void blackjackAction(bool next, bool speed, bool select, uint32_t now_ms)
{
    if (!running) return;
    if (state == BJ_MODE) {
        if (next) beginGame(now_ms);
        return;
    }
    if (auto_mode) {
        if (speed) { cycleAISpeed(&ai_speed); state_started = now_ms; redraw_needed = true; }
        if (select) { auto_mode = false; state = BJ_MODE; modeSelectionReset(&mode_input); redraw_needed = true; }
        return;
    }
    if (state == BJ_PLAYER) {
        if (next) { selected_action = selected_action == 0U ? 1U : 0U; redraw_needed = true; }
        if (select && selected_action == 1U) startDealerTurn(now_ms);
        else if (select && dealCard(&player_hand)) {
            const uint8_t total = handValue(&player_hand, 0U);
            if (total > 21U) finishGame(BJ_LOSS, "BUST - DEALER WINS", now_ms);
            else if (total == 21U) startDealerTurn(now_ms);
            else redraw_needed = true;
        }
    } else if (state == BJ_FINISHED && select) beginGame(now_ms);
}
updateBlackjack() · source line 188
void updateBlackjack(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 == BJ_SCORES && now_ms - state_started >= SHOW_SCORES_MS
    True
      1. Executestate = BJ_MODE;
      2. Executeredraw_needed = true;
      3. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionstate == BJ_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 == BJ_FINISHED && auto_mode
    True
      1. Executeconst uint32_t elapsed = now_ms - auto_restart_started;
      2. Executeconst uint32_t delay = aiRestartDelay();
      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 == BJ_PLAYER && auto_mode && now_ms - state_started >= aiSpeedDelay(ai_speed)
    True
      1. Decision!autoShouldHit()
        True
        1. ExecutestartDealerTurn(now_ms);
        False
        1. DecisiondealCard(&player_hand)
          True
            1. Executeconst uint8_t total = handValue(&player_hand, 0U);
            2. Decisiontotal > 21U
              True
              1. ExecutefinishGame(BJ_LOSS, "BUST - DEALER WINS", now_ms);
              False
              1. Decisiontotal == 21U
                True
                1. ExecutestartDealerTurn(now_ms);
                False
                  1. Executestate_started = now_ms;
                  2. Executeredraw_needed = true;
          False

          Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionstate == BJ_DEALER && now_ms - state_started >= (auto_mode ? aiSpeedDelay(ai_speed) : DEALER_DELAY_MS)
    True
      1. DecisionhandValue(&dealer_hand, 0U) < 17U
        True
          1. ExecutedealCard(&dealer_hand);
          2. Executestate_started = now_ms;
          3. Executeredraw_needed = true;
        False
          1. Executeconst uint8_t player_total = handValue(&player_hand, 0U), dealer_total = handValue(&dealer_hand, 0U);
          2. Decisiondealer_total > 21U || player_total > dealer_total
            True
            1. ExecutefinishGame(BJ_WIN, dealer_total > 21U ? "DEALER BUST - YOU WIN" : "YOU WIN", now_ms);
            False
            1. Decisionplayer_total < dealer_total
              True
              1. ExecutefinishGame(BJ_LOSS, "DEALER WINS", now_ms);
              False
              1. ExecutefinishGame(BJ_DRAW, "PUSH - DRAW", now_ms);
    False

    Continue after this decision.

Read original function source
void updateBlackjack(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
                     uint32_t now_ms)
{
    if (!running) return;
    if (state == BJ_SCORES && now_ms - state_started >= SHOW_SCORES_MS) { state = BJ_MODE; redraw_needed = true; return; }
    if (state == BJ_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 == BJ_FINISHED && auto_mode) {
        const uint32_t elapsed = now_ms - auto_restart_started;
        const uint32_t delay = aiRestartDelay();
        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 == BJ_PLAYER && auto_mode && now_ms - state_started >= aiSpeedDelay(ai_speed)) {
        if (!autoShouldHit()) startDealerTurn(now_ms);
        else if (dealCard(&player_hand)) {
            const uint8_t total = handValue(&player_hand, 0U);
            if (total > 21U) finishGame(BJ_LOSS, "BUST - DEALER WINS", now_ms);
            else if (total == 21U) startDealerTurn(now_ms);
            else { state_started = now_ms; redraw_needed = true; }
        }
        return;
    }
    if (state == BJ_DEALER && now_ms - state_started >= (auto_mode ? aiSpeedDelay(ai_speed) : DEALER_DELAY_MS)) {
        if (handValue(&dealer_hand, 0U) < 17U) { dealCard(&dealer_hand); state_started = now_ms; redraw_needed = true; }
        else {
            const uint8_t player_total = handValue(&player_hand, 0U), dealer_total = handValue(&dealer_hand, 0U);
            if (dealer_total > 21U || player_total > dealer_total) finishGame(BJ_WIN, dealer_total > 21U ? "DEALER BUST - YOU WIN" : "YOU WIN", now_ms);
            else if (player_total < dealer_total) finishGame(BJ_LOSS, "DEALER WINS", now_ms);
            else finishGame(BJ_DRAW, "PUSH - DRAW", now_ms);
        }
    }
}
drawCard() · source line 229
static void drawCard(uint16_t *frame, const card_t *card, int x, int y)

Calls into this documentation:

  1. Executechar path[32];
  2. Executeconst char *rank = card->rank == 1U ? "A" : card->rank == 11U ? "J" : card->rank == 12U ? "Q" : card->rank == 13U ? "K" : NULL;
  3. Decisionrank != NULL
    True
    1. Executesnprintf(path, sizeof(path), "/spiffs/cards/%s%c.jpg", rank, card->suit);
    False
    1. Executesnprintf(path, sizeof(path), "/spiffs/cards/%u%c.jpg", (unsigned)card->rank, card->suit);
  4. Decision!imageDecodeJpegAt(path, frame, (unsigned)x, (unsigned)y)
    True
      1. Executefill_rect(frame, x, y, 48, 64, COLOR_WHITE);
      2. Executedraw_rect(frame, x, y, 48, 64, COLOR_RED);
      3. Executedraw_text_centred(frame, x + 24, y + 25, path + 14, 1, COLOR_BLACK);
    False

    Continue after this decision.

Read original function source
static void drawCard(uint16_t *frame, const card_t *card, int x, int y)
{
    char path[32];
    const char *rank = card->rank == 1U ? "A" : card->rank == 11U ? "J" : card->rank == 12U ? "Q" : card->rank == 13U ? "K" : NULL;
    if (rank != NULL) snprintf(path, sizeof(path), "/spiffs/cards/%s%c.jpg", rank, card->suit);
    else snprintf(path, sizeof(path), "/spiffs/cards/%u%c.jpg", (unsigned)card->rank, card->suit);
    if (!imageDecodeJpegAt(path, frame, (unsigned)x, (unsigned)y)) {
        fill_rect(frame, x, y, 48, 64, COLOR_WHITE); draw_rect(frame, x, y, 48, 64, COLOR_RED);
        draw_text_centred(frame, x + 24, y + 25, path + 14, 1, COLOR_BLACK);
    }
}
drawHand() · source line 241
static void drawHand(uint16_t *frame, const hand_t *hand, int y, bool hide_second)

Calls into this documentation:

  1. Decisionhand->count == 0U
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst int spacing = hand->count > 1U ? (266 - 48) / (hand->count - 1U) : 48;
  3. FOR loopuint8_t index = 0U; index < hand->count; ++index

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

      1. Executeconst int x = 54 + index * (spacing > 48 ? 48 : spacing);
      2. Decisionhide_second && index == 1U
        True
          1. Decision!imageDecodeJpegAt("/spiffs/cards/Back.jpg", frame, (unsigned)x, (unsigned)y)
            True
              1. Executefill_rect(frame, x, y, 48, 64, COLOR_BLUE);
              2. Executedraw_rect(frame, x, y, 48, 64, COLOR_WHITE);
            False

            Continue after this decision.

        False
        1. ExecutedrawCard(frame, &hand->cards[index], x, y);
Read original function source
static void drawHand(uint16_t *frame, const hand_t *hand, int y, bool hide_second)
{
    if (hand->count == 0U) return;
    const int spacing = hand->count > 1U ? (266 - 48) / (hand->count - 1U) : 48;
    for (uint8_t index = 0U; index < hand->count; ++index) {
        const int x = 54 + index * (spacing > 48 ? 48 : spacing);
        if (hide_second && index == 1U) {
            if (!imageDecodeJpegAt("/spiffs/cards/Back.jpg", frame, (unsigned)x, (unsigned)y)) {
                fill_rect(frame, x, y, 48, 64, COLOR_BLUE); draw_rect(frame, x, y, 48, 64, COLOR_WHITE);
            }
        } else drawCard(frame, &hand->cards[index], x, y);
    }
}
drawBlackjack() · source line 255
void drawBlackjack(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 == BJ_SCORES
    True
      1. Executedraw_text_centred(frame, 160, 8, "BLACKJACK", 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), "DRAWS %u STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak);
      6. Executedraw_text_centred(frame, 160, 105, text, 2, COLOR_WHITE);
      7. Executesnprintf(text, sizeof(text), "BEST STREAK %u", (unsigned)scores.best_streak);
      8. Executedraw_text_centred(frame, 160, 135, text, 2, COLOR_GOLD);
      9. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionstate == BJ_MODE
    True
      1. Executedraw_text_centred(frame, 160, 8, "BLACKJACK", 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. Executeconst bool reveal_dealer = state == BJ_DEALER || state == BJ_FINISHED;
  8. ExecutedrawHand(frame, &dealer_hand, 3, !reveal_dealer);
  9. ExecutedrawHand(frame, &player_hand, 86, false);
  10. Executedraw_text(frame, 2, 5, "DLR", 1, COLOR_CYAN);
  11. Executesnprintf(text, sizeof(text), reveal_dealer ? "%u" : "%u+?", (unsigned)handValue(&dealer_hand, reveal_dealer ? 0U : 1U));
  12. Executedraw_text_centred(frame, 25, 24, text, 2, COLOR_WHITE);
  13. Executedraw_text(frame, 2, 88, "YOU", 1, COLOR_GREEN);
  14. Executesnprintf(text, sizeof(text), "%u", (unsigned)handValue(&player_hand, 0U));
  15. Executedraw_text_centred(frame, 25, 107, text, 2, COLOR_WHITE);
  16. ExecutedrawGameGold(frame, 2, 134, 50, true);
  17. Decisionstate == BJ_PLAYER
    True
      1. Executedraw_text_centred(frame, 160, 70, auto_mode ? "AUTO TURN" : "YOUR TURN", 1, COLOR_WHITE);
      2. Decisionauto_mode
        True
          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. Executedraw_text_centred(frame, 80, 153, "HIT", 2, selected_action == 0U ? COLOR_YELLOW : COLOR_WHITE);
          2. Executedraw_text_centred(frame, 240, 153, "STAY", 2, selected_action == 1U ? COLOR_YELLOW : COLOR_WHITE);
    False
    1. Decisionstate == BJ_DEALER
      True
        1. Executedraw_text_centred(frame, 160, 70, "DEALER TURN", 1, COLOR_WHITE);
        2. Executedraw_text_centred(frame, 160, 155, "PLEASE WAIT", 1, COLOR_LIGHTGRAY);
      False
        1. Executedraw_text_centred(frame, 160, 70, result_text, 1, COLOR_YELLOW);
        2. Decisionauto_mode
          True
            1. Executesnprintf(text, sizeof(text), "NEXT GAME %u E SPEED C STOP", (unsigned)auto_seconds_shown);
            2. Executedraw_text_centred(frame, 160, 155, text, 1, COLOR_LIGHTGRAY);
          False
          1. Executedraw_text_centred(frame, 160, 155, "C NEW GAME", 1, COLOR_LIGHTGRAY);
Read original function source
void drawBlackjack(uint16_t *frame)
{
    if (!running || !redraw_needed) return;
    redraw_needed = false;
    clear(frame, COLOR_BLACK);
    char text[40];
    if (state == BJ_SCORES) {
        draw_text_centred(frame, 160, 8, "BLACKJACK", 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), "DRAWS %u    STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak); draw_text_centred(frame, 160, 105, text, 2, COLOR_WHITE);
        snprintf(text, sizeof(text), "BEST STREAK %u", (unsigned)scores.best_streak); draw_text_centred(frame, 160, 135, text, 2, COLOR_GOLD);
        return;
    }
    if (state == BJ_MODE) {
        draw_text_centred(frame, 160, 8, "BLACKJACK", 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;
    }
    const bool reveal_dealer = state == BJ_DEALER || state == BJ_FINISHED;
    drawHand(frame, &dealer_hand, 3, !reveal_dealer);
    drawHand(frame, &player_hand, 86, false);
    draw_text(frame, 2, 5, "DLR", 1, COLOR_CYAN);
    snprintf(text, sizeof(text), reveal_dealer ? "%u" : "%u+?", (unsigned)handValue(&dealer_hand, reveal_dealer ? 0U : 1U)); draw_text_centred(frame, 25, 24, text, 2, COLOR_WHITE);
    draw_text(frame, 2, 88, "YOU", 1, COLOR_GREEN); snprintf(text, sizeof(text), "%u", (unsigned)handValue(&player_hand, 0U)); draw_text_centred(frame, 25, 107, text, 2, COLOR_WHITE);
    drawGameGold(frame, 2, 134, 50, true);
    if (state == BJ_PLAYER) {
        draw_text_centred(frame, 160, 70, auto_mode ? "AUTO TURN" : "YOUR TURN", 1, COLOR_WHITE);
        if (auto_mode) { snprintf(text, sizeof(text), "E SPEED %s  C STOP", aiSpeedShortText(ai_speed)); draw_text_centred(frame, 160, 155, text, 1, COLOR_LIGHTGRAY); }
        else { draw_text_centred(frame, 80, 153, "HIT", 2, selected_action == 0U ? COLOR_YELLOW : COLOR_WHITE); draw_text_centred(frame, 240, 153, "STAY", 2, selected_action == 1U ? COLOR_YELLOW : COLOR_WHITE); }
    } else if (state == BJ_DEALER) {
        draw_text_centred(frame, 160, 70, "DEALER TURN", 1, COLOR_WHITE);
        draw_text_centred(frame, 160, 155, "PLEASE WAIT", 1, COLOR_LIGHTGRAY);
    } else {
        draw_text_centred(frame, 160, 70, result_text, 1, COLOR_YELLOW);
        if (auto_mode) {
            snprintf(text, sizeof(text), "NEXT GAME %u  E SPEED  C STOP", (unsigned)auto_seconds_shown);
            draw_text_centred(frame, 160, 155, text, 1, COLOR_LIGHTGRAY);
        } else draw_text_centred(frame, 160, 155, "C NEW GAME", 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 "blackjack.h"

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

#define DECK_SIZE 52U
#define MAX_HAND_CARDS 12U
#define SHOW_SCORES_MS 1000U
#define DEALER_DELAY_MS 850U
#define BLACKJACK_WIN_GOLD 10U
#define SCORE_NAMESPACE "arcade_results"
#define SCORE_KEY "blackjack"

typedef enum { BJ_SCORES, BJ_MODE, BJ_PLAYER, BJ_DEALER, BJ_FINISHED } blackjack_state_t;
typedef enum { BJ_WIN, BJ_LOSS, BJ_DRAW } blackjack_result_t;

typedef struct { uint8_t rank; char suit; } card_t;
typedef struct { card_t cards[MAX_HAND_CARDS]; uint8_t count; } hand_t;
typedef struct {
    uint32_t version;
    uint32_t wins;
    uint32_t losses;
    uint32_t draws;
    uint32_t streak;
    uint32_t best_streak;
} blackjack_scores_t;

static card_t deck[DECK_SIZE];
static hand_t player_hand, dealer_hand;
static blackjack_scores_t scores;
static blackjack_state_t state;
static uint8_t deck_position, selected_action, 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 result_text[32];

static uint8_t cardValue(card_t card)
{
    return card.rank == 1U ? 11U : card.rank >= 10U ? 10U : card.rank;
}

static uint8_t handValue(const hand_t *hand, uint8_t visible_count)
{
    if (visible_count == 0U || visible_count > hand->count) visible_count = hand->count;
    uint8_t total = 0U, aces = 0U;
    for (uint8_t index = 0U; index < visible_count; ++index) {
        total += cardValue(hand->cards[index]);
        if (hand->cards[index].rank == 1U) ++aces;
    }
    while (total > 21U && aces > 0U) { total -= 10U; --aces; }
    return total;
}

static bool usableAce(const hand_t *hand)
{
    uint8_t total = 0U, aces = 0U;
    for (uint8_t index = 0U; index < hand->count; ++index) {
        total += cardValue(hand->cards[index]);
        if (hand->cards[index].rank == 1U) ++aces;
    }
    while (total > 21U && aces > 0U) { total -= 10U; --aces; }
    return aces > 0U;
}

static bool naturalBlackjack(const hand_t *hand) { return hand->count == 2U && handValue(hand, 0U) == 21U; }

static bool autoShouldHit(void)
{
    const uint8_t total = handValue(&player_hand, 0U);
    const uint8_t dealer_card = cardValue(dealer_hand.cards[0]);
    if (usableAce(&player_hand)) return total <= 17U || (total == 18U && dealer_card >= 9U);
    if (total <= 11U) return true;
    if (total == 12U) return dealer_card < 4U || dealer_card > 6U;
    return total <= 16U && dealer_card >= 7U;
}

static void initialiseDeck(void)
{
    static const char suits[] = { 'C', 'D', 'H', 'S' };
    uint8_t index = 0U;
    for (uint8_t suit = 0U; suit < 4U; ++suit)
        for (uint8_t rank = 1U; rank <= 13U; ++rank) deck[index++] = (card_t){ rank, suits[suit] };
    for (int current = DECK_SIZE - 1; current > 0; --current) {
        const uint8_t selected = esp_random() % (uint32_t)(current + 1);
        const card_t temporary = deck[current]; deck[current] = deck[selected]; deck[selected] = temporary;
    }
    deck_position = 0U;
}

static bool dealCard(hand_t *hand)
{
    if (hand->count >= MAX_HAND_CARDS || deck_position >= DECK_SIZE) return false;
    hand->cards[hand->count++] = deck[deck_position++];
    audioPlay(AUDIO_SFX_CARD_DEAL);
    return true;
}

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

static void saveScores(void) { (void)game_storage_write(SCORE_NAMESPACE, SCORE_KEY, &scores, sizeof(scores)); }

static void finishGame(blackjack_result_t result, const char *message, uint32_t now_ms)
{
    audioPlay(result == BJ_WIN ? AUDIO_SFX_WAVE_CLEAR : result == BJ_LOSS ? AUDIO_SFX_GAME_OVER : AUDIO_SFX_MENU_SELECT);
    snprintf(result_text, sizeof(result_text), "%s", message);
    if (!auto_mode) {
        if (result == BJ_WIN) { ++scores.wins; ++scores.streak; if (scores.streak > scores.best_streak) scores.best_streak = scores.streak; }
        else { scores.streak = 0U; if (result == BJ_LOSS) ++scores.losses; else ++scores.draws; }
        saveScores();
    }
    if (result == BJ_WIN) {
        if (auto_mode) addAIGold(BLACKJACK_WIN_GOLD); else addUserGoldWon(BLACKJACK_WIN_GOLD);
    }
    state = BJ_FINISHED;
    auto_restart_started = now_ms;
    auto_seconds_shown = (uint8_t)((aiRestartDelay() + 999U) / 1000U);
    redraw_needed = true;
}

static void startDealerTurn(uint32_t now_ms)
{
    state = BJ_DEALER;
    state_started = now_ms;
    redraw_needed = true;
}

static void beginGame(uint32_t now_ms)
{
    player_hand = (hand_t){0}; dealer_hand = (hand_t){0}; selected_action = 0U; result_text[0] = '\0';
    initialiseDeck();
    dealCard(&player_hand); dealCard(&dealer_hand); dealCard(&player_hand); dealCard(&dealer_hand);
    if (naturalBlackjack(&player_hand) && naturalBlackjack(&dealer_hand)) finishGame(BJ_DRAW, "BOTH BLACKJACK - DRAW", now_ms);
    else if (naturalBlackjack(&player_hand)) finishGame(BJ_WIN, "BLACKJACK - YOU WIN", now_ms);
    else if (naturalBlackjack(&dealer_hand)) finishGame(BJ_LOSS, "DEALER BLACKJACK", now_ms);
    else { state = BJ_PLAYER; state_started = now_ms; redraw_needed = true; }
}

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

void stopBlackjack(void) { running = false; }

void blackjackAction(bool next, bool speed, bool select, uint32_t now_ms)
{
    if (!running) return;
    if (state == BJ_MODE) {
        if (next) beginGame(now_ms);
        return;
    }
    if (auto_mode) {
        if (speed) { cycleAISpeed(&ai_speed); state_started = now_ms; redraw_needed = true; }
        if (select) { auto_mode = false; state = BJ_MODE; modeSelectionReset(&mode_input); redraw_needed = true; }
        return;
    }
    if (state == BJ_PLAYER) {
        if (next) { selected_action = selected_action == 0U ? 1U : 0U; redraw_needed = true; }
        if (select && selected_action == 1U) startDealerTurn(now_ms);
        else if (select && dealCard(&player_hand)) {
            const uint8_t total = handValue(&player_hand, 0U);
            if (total > 21U) finishGame(BJ_LOSS, "BUST - DEALER WINS", now_ms);
            else if (total == 21U) startDealerTurn(now_ms);
            else redraw_needed = true;
        }
    } else if (state == BJ_FINISHED && select) beginGame(now_ms);
}

void updateBlackjack(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
                     uint32_t now_ms)
{
    if (!running) return;
    if (state == BJ_SCORES && now_ms - state_started >= SHOW_SCORES_MS) { state = BJ_MODE; redraw_needed = true; return; }
    if (state == BJ_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 == BJ_FINISHED && auto_mode) {
        const uint32_t elapsed = now_ms - auto_restart_started;
        const uint32_t delay = aiRestartDelay();
        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 == BJ_PLAYER && auto_mode && now_ms - state_started >= aiSpeedDelay(ai_speed)) {
        if (!autoShouldHit()) startDealerTurn(now_ms);
        else if (dealCard(&player_hand)) {
            const uint8_t total = handValue(&player_hand, 0U);
            if (total > 21U) finishGame(BJ_LOSS, "BUST - DEALER WINS", now_ms);
            else if (total == 21U) startDealerTurn(now_ms);
            else { state_started = now_ms; redraw_needed = true; }
        }
        return;
    }
    if (state == BJ_DEALER && now_ms - state_started >= (auto_mode ? aiSpeedDelay(ai_speed) : DEALER_DELAY_MS)) {
        if (handValue(&dealer_hand, 0U) < 17U) { dealCard(&dealer_hand); state_started = now_ms; redraw_needed = true; }
        else {
            const uint8_t player_total = handValue(&player_hand, 0U), dealer_total = handValue(&dealer_hand, 0U);
            if (dealer_total > 21U || player_total > dealer_total) finishGame(BJ_WIN, dealer_total > 21U ? "DEALER BUST - YOU WIN" : "YOU WIN", now_ms);
            else if (player_total < dealer_total) finishGame(BJ_LOSS, "DEALER WINS", now_ms);
            else finishGame(BJ_DRAW, "PUSH - DRAW", now_ms);
        }
    }
}

static void drawCard(uint16_t *frame, const card_t *card, int x, int y)
{
    char path[32];
    const char *rank = card->rank == 1U ? "A" : card->rank == 11U ? "J" : card->rank == 12U ? "Q" : card->rank == 13U ? "K" : NULL;
    if (rank != NULL) snprintf(path, sizeof(path), "/spiffs/cards/%s%c.jpg", rank, card->suit);
    else snprintf(path, sizeof(path), "/spiffs/cards/%u%c.jpg", (unsigned)card->rank, card->suit);
    if (!imageDecodeJpegAt(path, frame, (unsigned)x, (unsigned)y)) {
        fill_rect(frame, x, y, 48, 64, COLOR_WHITE); draw_rect(frame, x, y, 48, 64, COLOR_RED);
        drawPet2001TextCentredTransparent(frame, x + 24, y + 25, path + 14, COLOR_BLACK, 1);
    }
}

static void drawHand(uint16_t *frame, const hand_t *hand, int y, bool hide_second)
{
    if (hand->count == 0U) return;
    const int spacing = hand->count > 1U ? (266 - 48) / (hand->count - 1U) : 48;
    for (uint8_t index = 0U; index < hand->count; ++index) {
        const int x = 54 + index * (spacing > 48 ? 48 : spacing);
        if (hide_second && index == 1U) {
            if (!imageDecodeJpegAt("/spiffs/cards/Back.jpg", frame, (unsigned)x, (unsigned)y)) {
                fill_rect(frame, x, y, 48, 64, COLOR_BLUE); draw_rect(frame, x, y, 48, 64, COLOR_WHITE);
            }
        } else drawCard(frame, &hand->cards[index], x, y);
    }
}

void drawBlackjack(uint16_t *frame)
{
    if (!running || !redraw_needed) return;
    redraw_needed = false;
    clear(frame, COLOR_BLACK);
    char text[40];
    if (state == BJ_SCORES) {
        drawPet2001TextCentredTransparent(frame, 160, 8, "BLACKJACK", 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), "DRAWS %u    STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak); drawPet2001TextCentredTransparent(frame, 160, 105, text, COLOR_WHITE, 1);
        snprintf(text, sizeof(text), "BEST STREAK %u", (unsigned)scores.best_streak); drawPet2001TextCentredTransparent(frame, 160, 135, text, COLOR_GOLD, 1);
        return;
    }
    if (state == BJ_MODE) {
        drawPet2001TextCentredTransparent(frame, 160, 8, "BLACKJACK", 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;
    }
    const bool reveal_dealer = state == BJ_DEALER || state == BJ_FINISHED;
    drawHand(frame, &dealer_hand, 3, !reveal_dealer);
    drawHand(frame, &player_hand, 86, false);
    drawPet2001TextTransparent(frame, 2, 5, "DLR", COLOR_CYAN, 1);
    snprintf(text, sizeof(text), reveal_dealer ? "%u" : "%u+?", (unsigned)handValue(&dealer_hand, reveal_dealer ? 0U : 1U)); drawPet2001TextCentredTransparent(frame, 25, 24, text, COLOR_WHITE, 2);
    drawPet2001TextTransparent(frame, 2, 88, "YOU", COLOR_GREEN, 1); snprintf(text, sizeof(text), "%u", (unsigned)handValue(&player_hand, 0U)); drawPet2001TextCentredTransparent(frame, 25, 107, text, COLOR_WHITE, 2);
    drawGameGold(frame, 2, 134, 50, true);
    if (state == BJ_PLAYER) {
        drawPet2001TextCentredTransparent(frame, 160, 70, auto_mode ? "AUTO TURN" : "YOUR TURN", COLOR_WHITE, 1);
        if (auto_mode) { snprintf(text, sizeof(text), "E SPEED %s  C STOP", aiSpeedShortText(ai_speed)); drawPet2001TextCentredTransparent(frame, 160, 155, text, COLOR_LIGHTGRAY, 1); }
        else { drawPet2001TextCentredTransparent(frame, 80, 153, "HIT", selected_action == 0U ? COLOR_YELLOW : COLOR_WHITE, 2); drawPet2001TextCentredTransparent(frame, 240, 153, "STAY", selected_action == 1U ? COLOR_YELLOW : COLOR_WHITE, 2); }
    } else if (state == BJ_DEALER) {
        drawPet2001TextCentredTransparent(frame, 160, 70, "DEALER TURN", COLOR_WHITE, 1);
        drawPet2001TextCentredTransparent(frame, 160, 155, "PLEASE WAIT", COLOR_LIGHTGRAY, 1);
    } else {
        drawPet2001TextCentredTransparent(frame, 160, 70, result_text, COLOR_YELLOW, 1);
        if (auto_mode) {
            snprintf(text, sizeof(text), "NEXT GAME %u  E SPEED  C STOP", (unsigned)auto_seconds_shown);
            drawPet2001TextCentredTransparent(frame, 160, 155, text, COLOR_LIGHTGRAY, 1);
        } else drawPet2001TextCentredTransparent(frame, 160, 155, "C NEW GAME", COLOR_LIGHTGRAY, 1);
    }
}