Poker Dice

Source: src/poker_dice.c · 287 lines · 24 functions.

Screen captures

Views of this part running on the Mini Arcade.

Poker Dice running on the physical Mini Arcade
Poker Dice displayed on the physical Mini Arcade.
Poker Dice roll screen on the physical Mini Arcade
Poker Dice showing a roll in progress.

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 38
static void setMessage(const char *text)
  1. Executesnprintf(message, sizeof(message), "%s", text);
Read original function source
static void setMessage(const char *text) { snprintf(message, sizeof(message), "%s", text); }
loadScores() · source line 40
static void loadScores(void)

Calls into this documentation:

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

    Continue after this decision.

Read original function source
static void loadScores(void)
{
    bool found = false;
    scores = (poker_dice_scores_t){ .version = 1U };
    if (!game_storage_read(POKER_DICE_SCORE_NAMESPACE, POKER_DICE_SCORE_KEY, &scores, sizeof(scores), &found) ||
        !found || scores.version != 1U) scores = (poker_dice_scores_t){ .version = 1U };
}
saveScores() · source line 48
static void saveScores(void)

Calls into this documentation:

  1. Execute(void)game_storage_write(POKER_DICE_SCORE_NAMESPACE, POKER_DICE_SCORE_KEY, &scores, sizeof(scores));
Read original function source
static void saveScores(void) { (void)game_storage_write(POKER_DICE_SCORE_NAMESPACE, POKER_DICE_SCORE_KEY, &scores, sizeof(scores)); }
countDice() · source line 50
static void countDice(const uint8_t dice[DICE_COUNT], uint8_t counts[7])
  1. Executememset(counts, 0, 7U);
  2. FOR loopuint8_t i = 0; i < DICE_COUNT; ++i

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

    1. Decisiondice[i] >= 1U && dice[i] <= 6U
      True
      1. Execute++counts[dice[i]];
      False

      Continue after this decision.

Read original function source
static void countDice(const uint8_t dice[DICE_COUNT], uint8_t counts[7])
{
    memset(counts, 0, 7U);
    for (uint8_t i = 0; i < DICE_COUNT; ++i) if (dice[i] >= 1U && dice[i] <= 6U) ++counts[dice[i]];
}
addRemainingDescending() · source line 56
static void addRemainingDescending(poker_dice_hand_t *hand, uint8_t *tie_index, const uint8_t counts[7], uint8_t excluded1, uint8_t excluded2)
  1. FOR loopint value = 6; value >= 1; --value

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

      1. Decisionvalue == excluded1 || value == excluded2
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      2. FOR loopuint8_t count = 0; count < counts[value] && *tie_index < DICE_COUNT; ++count

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

        1. Executehand->tie[(*tie_index)++] = (uint8_t)value;
Read original function source
static void addRemainingDescending(poker_dice_hand_t *hand, uint8_t *tie_index, const uint8_t counts[7], uint8_t excluded1, uint8_t excluded2)
{
    for (int value = 6; value >= 1; --value) {
        if (value == excluded1 || value == excluded2) continue;
        for (uint8_t count = 0; count < counts[value] && *tie_index < DICE_COUNT; ++count)
            hand->tie[(*tie_index)++] = (uint8_t)value;
    }
}
evaluateHand() · source line 65
static poker_dice_hand_t evaluateHand(const uint8_t dice[DICE_COUNT])

Calls into this documentation:

  1. Executeuint8_t counts[7], five = 0U, four = 0U, three = 0U, pair_high = 0U, pair_low = 0U;
  2. Executepoker_dice_hand_t hand = { .rank = PD_BUST, .tie = {0} };
  3. ExecutecountDice(dice, counts);
  4. FOR loopuint8_t value = 1U; value <= 6U; ++value

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

      1. Decisioncounts[value] == 5U
        True
        1. Executefive = value;
        False
        1. Decisioncounts[value] == 4U
          True
          1. Executefour = value;
          False
          1. Decisioncounts[value] == 3U
            True
            1. Executethree = value;
            False
            1. Decisioncounts[value] == 2U
              True
                1. Executepair_low = pair_high;
                2. Executepair_high = value;
              False

              Continue after this decision.

  5. Decisionfive
    True
      1. Executehand.rank = PD_FIVE_KIND;
      2. Executehand.tie[0] = five;
      3. Exit this functionreturn hand;
    False

    Continue after this decision.

  6. Decisionfour
    True
      1. Executeuint8_t tie = 1U;
      2. Executehand.rank = PD_FOUR_KIND;
      3. Executehand.tie[0] = four;
      4. ExecuteaddRemainingDescending(&hand, &tie, counts, four, 0U);
      5. Exit this functionreturn hand;
    False

    Continue after this decision.

  7. Decisionthree && pair_high
    True
      1. Executehand.rank = PD_FULL_HOUSE;
      2. Executehand.tie[0] = three;
      3. Executehand.tie[1] = pair_high;
      4. Exit this functionreturn hand;
    False

    Continue after this decision.

  8. Executebool straight_low = true, straight_high = true;
  9. FOR loopuint8_t value = 1U; value <= 5U; ++value

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

    1. Executestraight_low &= counts[value] == 1U;
  10. FOR loopuint8_t value = 2U; value <= 6U; ++value

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

    1. Executestraight_high &= counts[value] == 1U;
  11. Decisionstraight_low || straight_high
    True
      1. Executehand.rank = PD_STRAIGHT;
      2. Executehand.tie[0] = straight_high ? 6U : 5U;
      3. Exit this functionreturn hand;
    False

    Continue after this decision.

  12. FOR loopint start = 3; start >= 1; --start

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

      1. Executebool small_straight = true;
      2. FOR loopuint8_t offset = 0U; offset < 4U; ++offset

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

        1. Executesmall_straight &= counts[start + offset] > 0U;
      3. Decision!small_straight
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      4. Executehand.rank = PD_SMALL_STRAIGHT;
      5. Executehand.tie[0] = (uint8_t)(start + 3);
      6. Executeuint8_t tie = 1U;
      7. FOR loopint value = 6; value >= 1; --value

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

        1. FOR loopuint8_t count = (value >= start && value <= start + 3) ? 1U : 0U; count < counts[value] && tie < DICE_COUNT; ++count

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

          1. Executehand.tie[tie++] = (uint8_t)value;
      8. Exit this functionreturn hand;
  13. Decisionthree
    True
      1. Executeuint8_t tie = 1U;
      2. Executehand.rank = PD_THREE_KIND;
      3. Executehand.tie[0] = three;
      4. ExecuteaddRemainingDescending(&hand, &tie, counts, three, 0U);
      5. Exit this functionreturn hand;
    False

    Continue after this decision.

  14. Decisionpair_low
    True
      1. Executeuint8_t tie = 2U;
      2. Executehand.rank = PD_TWO_PAIRS;
      3. Executehand.tie[0] = pair_high;
      4. Executehand.tie[1] = pair_low;
      5. ExecuteaddRemainingDescending(&hand, &tie, counts, pair_high, pair_low);
      6. Exit this functionreturn hand;
    False

    Continue after this decision.

  15. Decisionpair_high
    True
      1. Executeuint8_t tie = 1U;
      2. Executehand.rank = PD_ONE_PAIR;
      3. Executehand.tie[0] = pair_high;
      4. ExecuteaddRemainingDescending(&hand, &tie, counts, pair_high, 0U);
      5. Exit this functionreturn hand;
    False

    Continue after this decision.

  16. Executeuint8_t tie = 0U;
  17. ExecuteaddRemainingDescending(&hand, &tie, counts, 0U, 0U);
  18. Exit this functionreturn hand;
Read original function source
static poker_dice_hand_t evaluateHand(const uint8_t dice[DICE_COUNT])
{
    uint8_t counts[7], five = 0U, four = 0U, three = 0U, pair_high = 0U, pair_low = 0U;
    poker_dice_hand_t hand = { .rank = PD_BUST, .tie = {0} };
    countDice(dice, counts);
    for (uint8_t value = 1U; value <= 6U; ++value) {
        if (counts[value] == 5U) five = value;
        else if (counts[value] == 4U) four = value;
        else if (counts[value] == 3U) three = value;
        else if (counts[value] == 2U) { pair_low = pair_high; pair_high = value; }
    }
    if (five) { hand.rank = PD_FIVE_KIND; hand.tie[0] = five; return hand; }
    if (four) { uint8_t tie = 1U; hand.rank = PD_FOUR_KIND; hand.tie[0] = four; addRemainingDescending(&hand, &tie, counts, four, 0U); return hand; }
    if (three && pair_high) { hand.rank = PD_FULL_HOUSE; hand.tie[0] = three; hand.tie[1] = pair_high; return hand; }
    bool straight_low = true, straight_high = true;
    for (uint8_t value = 1U; value <= 5U; ++value) straight_low &= counts[value] == 1U;
    for (uint8_t value = 2U; value <= 6U; ++value) straight_high &= counts[value] == 1U;
    if (straight_low || straight_high) { hand.rank = PD_STRAIGHT; hand.tie[0] = straight_high ? 6U : 5U; return hand; }
    for (int start = 3; start >= 1; --start) {
        bool small_straight = true;
        for (uint8_t offset = 0U; offset < 4U; ++offset) small_straight &= counts[start + offset] > 0U;
        if (!small_straight) continue;
        hand.rank = PD_SMALL_STRAIGHT; hand.tie[0] = (uint8_t)(start + 3); uint8_t tie = 1U;
        for (int value = 6; value >= 1; --value) for (uint8_t count = (value >= start && value <= start + 3) ? 1U : 0U; count < counts[value] && tie < DICE_COUNT; ++count) hand.tie[tie++] = (uint8_t)value;
        return hand;
    }
    if (three) { uint8_t tie = 1U; hand.rank = PD_THREE_KIND; hand.tie[0] = three; addRemainingDescending(&hand, &tie, counts, three, 0U); return hand; }
    if (pair_low) { uint8_t tie = 2U; hand.rank = PD_TWO_PAIRS; hand.tie[0] = pair_high; hand.tie[1] = pair_low; addRemainingDescending(&hand, &tie, counts, pair_high, pair_low); return hand; }
    if (pair_high) { uint8_t tie = 1U; hand.rank = PD_ONE_PAIR; hand.tie[0] = pair_high; addRemainingDescending(&hand, &tie, counts, pair_high, 0U); return hand; }
    uint8_t tie = 0U; addRemainingDescending(&hand, &tie, counts, 0U, 0U); return hand;
}
compareHands() · source line 97
static int8_t compareHands(poker_dice_hand_t left, poker_dice_hand_t right)
  1. Decisionleft.rank != right.rank
    True
    1. Exit this functionreturn left.rank > right.rank ? 1 : -1;
    False

    Continue after this decision.

  2. FOR loopuint8_t i = 0U; i < DICE_COUNT; ++i

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

    1. Decisionleft.tie[i] != right.tie[i]
      True
      1. Exit this functionreturn left.tie[i] > right.tie[i] ? 1 : -1;
      False

      Continue after this decision.

  3. Exit this functionreturn 0;
Read original function source
static int8_t compareHands(poker_dice_hand_t left, poker_dice_hand_t right)
{
    if (left.rank != right.rank) return left.rank > right.rank ? 1 : -1;
    for (uint8_t i = 0U; i < DICE_COUNT; ++i) if (left.tie[i] != right.tie[i]) return left.tie[i] > right.tie[i] ? 1 : -1;
    return 0;
}
handName() · source line 104
static const char *handName(poker_dice_hand_rank_t rank)
  1. Executestatic const char *const names[] = { "BUST", "ONE PAIR", "TWO PAIRS", "THREE KIND", "SMALL STRAIGHT", "STRAIGHT", "FULL HOUSE", "FOUR KIND", "FIVE KIND" };
  2. Exit this functionreturn names[rank];
Read original function source
static const char *handName(poker_dice_hand_rank_t rank)
{
    static const char *const names[] = { "BUST", "ONE PAIR", "TWO PAIRS", "THREE KIND", "SMALL STRAIGHT", "STRAIGHT", "FULL HOUSE", "FOUR KIND", "FIVE KIND" };
    return names[rank];
}
rollDice() · source line 110
static void rollDice(uint8_t dice[DICE_COUNT], const bool held[DICE_COUNT])

Calls into this documentation:

  1. FOR loopuint8_t i = 0U; i < DICE_COUNT; ++i

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

    1. Decision!held[i]
      True
      1. Executedice[i] = (uint8_t)(esp_random() % 6U) + 1U;
      False

      Continue after this decision.

  2. ExecuteaudioPlay(AUDIO_SFX_DICE_ROLL);
Read original function source
static void rollDice(uint8_t dice[DICE_COUNT], const bool held[DICE_COUNT])
{
    for (uint8_t i = 0U; i < DICE_COUNT; ++i) if (!held[i]) dice[i] = (uint8_t)(esp_random() % 6U) + 1U;
    audioPlay(AUDIO_SFX_DICE_ROLL);
}
scheduleComputer() · source line 116
static void scheduleComputer(uint32_t now_ms)

Calls into this documentation:

  1. Executeaction_due = now_ms + aiSpeedDelay(ai_speed);
Read original function source
static void scheduleComputer(uint32_t now_ms) { action_due = now_ms + aiSpeedDelay(ai_speed); }
chooseComputerHolds() · source line 118
static void chooseComputerHolds(const uint8_t dice[DICE_COUNT], bool held[DICE_COUNT])

Calls into this documentation:

  1. Executeuint8_t counts[7];
  2. Executepoker_dice_hand_t hand = evaluateHand(dice);
  3. Executememset(held, 0, DICE_COUNT * sizeof(held[0]));
  4. ExecutecountDice(dice, counts);
  5. Decisionhand.rank >= PD_STRAIGHT
    True
      1. FOR loopuint8_t i = 0U; i < DICE_COUNT; ++i

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

        1. Executeheld[i] = true;
      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionhand.rank == PD_SMALL_STRAIGHT
    True
      1. Executeconst uint8_t start = hand.tie[0] - 3U;
      2. Executebool value_held[7] = {false};
      3. FOR loopuint8_t i = 0U; i < DICE_COUNT; ++i

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

        1. Decisiondice[i] >= start && dice[i] <= start + 3U && !value_held[dice[i]]
          True
            1. Executeheld[i] = true;
            2. Executevalue_held[dice[i]] = true;
          False

          Continue after this decision.

      4. Exit this functionreturn;
    False

    Continue after this decision.

  7. Executeconst uint8_t minimum_group = hand.rank == PD_TWO_PAIRS ? 2U : hand.rank == PD_THREE_KIND ? 3U : hand.rank == PD_ONE_PAIR ? 2U : 0U;
  8. Decisionminimum_group
    True
      1. FOR loopuint8_t i = 0U; i < DICE_COUNT; ++i

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

        1. Executeheld[i] = counts[dice[i]] >= minimum_group;
      2. Exit this functionreturn;
    False

    Continue after this decision.

  9. Executeuint8_t best_start = 0U, best_length = 0U;
  10. FOR loopuint8_t start = 1U; start <= 6U; ++start

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

      1. Executeuint8_t length = 0U;
      2. WHILE loopstart + length <= 6U && counts[start + length]

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

        1. Execute++length;
      3. Decisionlength > best_length
        True
          1. Executebest_start = start;
          2. Executebest_length = length;
        False

        Continue after this decision.

  11. Decisionbest_length >= 3U
    True
    1. FOR loopuint8_t i = 0U; i < DICE_COUNT; ++i

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

      1. Executeheld[i] = dice[i] >= best_start && dice[i] < best_start + best_length;
    False

    Continue after this decision.

Read original function source
static void chooseComputerHolds(const uint8_t dice[DICE_COUNT], bool held[DICE_COUNT])
{
    uint8_t counts[7]; poker_dice_hand_t hand = evaluateHand(dice);
    memset(held, 0, DICE_COUNT * sizeof(held[0])); countDice(dice, counts);
    if (hand.rank >= PD_STRAIGHT) { for (uint8_t i = 0U; i < DICE_COUNT; ++i) held[i] = true; return; }
    if (hand.rank == PD_SMALL_STRAIGHT) {
        const uint8_t start = hand.tie[0] - 3U; bool value_held[7] = {false};
        for (uint8_t i = 0U; i < DICE_COUNT; ++i) if (dice[i] >= start && dice[i] <= start + 3U && !value_held[dice[i]]) { held[i] = true; value_held[dice[i]] = true; }
        return;
    }
    const uint8_t minimum_group = hand.rank == PD_TWO_PAIRS ? 2U : hand.rank == PD_THREE_KIND ? 3U : hand.rank == PD_ONE_PAIR ? 2U : 0U;
    if (minimum_group) { for (uint8_t i = 0U; i < DICE_COUNT; ++i) held[i] = counts[dice[i]] >= minimum_group; return; }
    uint8_t best_start = 0U, best_length = 0U;
    for (uint8_t start = 1U; start <= 6U; ++start) { uint8_t length = 0U; while (start + length <= 6U && counts[start + length]) ++length; if (length > best_length) { best_start = start; best_length = length; } }
    if (best_length >= 3U) for (uint8_t i = 0U; i < DICE_COUNT; ++i) held[i] = dice[i] >= best_start && dice[i] < best_start + best_length;
}
beginCpuTurn() · source line 135
static void beginCpuTurn(uint32_t now_ms)

Calls into this documentation:

  1. Executestate = PD_CPU_TURN;
  2. Executecpu_rolls = 0U;
  3. Executememset(cpu_dice, 0, sizeof(cpu_dice));
  4. Executememset(cpu_held, 0, sizeof(cpu_held));
  5. ExecutesetMessage(auto_mode ? "B TURN" : "CPU TURN");
  6. ExecutescheduleComputer(now_ms);
  7. Executeredraw_needed = true;
Read original function source
static void beginCpuTurn(uint32_t now_ms)
{
    state = PD_CPU_TURN; cpu_rolls = 0U; memset(cpu_dice, 0, sizeof(cpu_dice)); memset(cpu_held, 0, sizeof(cpu_held));
    setMessage(auto_mode ? "B TURN" : "CPU TURN"); scheduleComputer(now_ms); redraw_needed = true;
}
finishPlayerTurn() · source line 141
static void finishPlayerTurn(uint32_t now_ms)

Calls into this documentation:

  1. Executestate = PD_PLAYER_RESULT;
  2. ExecutesetMessage(handName(evaluateHand(player_dice).rank));
  3. ExecutescheduleComputer(now_ms);
  4. Executeredraw_needed = true;
Read original function source
static void finishPlayerTurn(uint32_t now_ms)
{
    state = PD_PLAYER_RESULT; setMessage(handName(evaluateHand(player_dice).rank)); scheduleComputer(now_ms); redraw_needed = true;
}
finishGame() · source line 146
static void finishGame(uint32_t now_ms)

Calls into this documentation:

  1. Executeconst int8_t result = compareHands(evaluateHand(player_dice), evaluateHand(cpu_dice));
  2. Executestate = PD_FINISHED;
  3. Decisionauto_mode
    True
      1. ExecutesetMessage(result > 0 ? "A WINS" : result < 0 ? "B WINS" : "DRAW");
      2. Executeauto_restart_started = now_ms;
      3. Executeauto_seconds = (uint8_t)((aiRestartDelay() + 999U) / 1000U);
    False
    1. Decisionresult > 0
      True
        1. Execute++scores.wins;
        2. Execute++scores.streak;
        3. ExecuteaddUserGoldWon(POKER_DICE_WIN_GOLD);
        4. ExecutesaveScores();
        5. ExecutesetMessage("YOU WIN +10 GOLD");
      False
      1. Decisionresult < 0
        True
          1. Execute++scores.losses;
          2. Executescores.streak = 0U;
          3. ExecutesaveScores();
          4. ExecutesetMessage("CPU WINS");
        False
          1. Execute++scores.draws;
          2. Executescores.streak = 0U;
          3. ExecutesaveScores();
          4. ExecutesetMessage("DRAW");
  4. ExecuteaudioPlay(result > 0 ? AUDIO_SFX_WAVE_CLEAR : result < 0 ? AUDIO_SFX_GAME_OVER : AUDIO_SFX_MENU_SELECT);
  5. Executeredraw_needed = true;
Read original function source
static void finishGame(uint32_t now_ms)
{
    const int8_t result = compareHands(evaluateHand(player_dice), evaluateHand(cpu_dice));
    state = PD_FINISHED;
    if (auto_mode) { setMessage(result > 0 ? "A WINS" : result < 0 ? "B WINS" : "DRAW"); auto_restart_started = now_ms; auto_seconds = (uint8_t)((aiRestartDelay() + 999U) / 1000U); }
    else if (result > 0) { ++scores.wins; ++scores.streak; addUserGoldWon(POKER_DICE_WIN_GOLD); saveScores(); setMessage("YOU WIN +10 GOLD"); }
    else if (result < 0) { ++scores.losses; scores.streak = 0U; saveScores(); setMessage("CPU WINS"); }
    else { ++scores.draws; scores.streak = 0U; saveScores(); setMessage("DRAW"); }
    audioPlay(result > 0 ? AUDIO_SFX_WAVE_CLEAR : result < 0 ? AUDIO_SFX_GAME_OVER : AUDIO_SFX_MENU_SELECT);
    redraw_needed = true;
}
automatedRoll() · source line 158
static void automatedRoll(bool player_side, uint32_t now_ms)

Calls into this documentation:

  1. Executeuint8_t *dice = player_side ? player_dice : cpu_dice;
  2. Executebool *held = player_side ? player_held : cpu_held;
  3. Executeuint8_t *rolls = player_side ? &player_rolls : &cpu_rolls;
  4. ExecuterollDice(dice, held);
  5. Execute++*rolls;
  6. Executeconst poker_dice_hand_t hand = evaluateHand(dice);
  7. Decision*rolls >= MAX_ROLLS || hand.rank >= PD_STRAIGHT
    True
      1. Decisionplayer_side
        True
        1. ExecutefinishPlayerTurn(now_ms);
        False
        1. ExecutefinishGame(now_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  8. ExecutechooseComputerHolds(dice, held);
  9. ExecutesetMessage("CHOOSING DICE");
  10. ExecutescheduleComputer(now_ms);
  11. Executeredraw_needed = true;
Read original function source
static void automatedRoll(bool player_side, uint32_t now_ms)
{
    uint8_t *dice = player_side ? player_dice : cpu_dice; bool *held = player_side ? player_held : cpu_held; uint8_t *rolls = player_side ? &player_rolls : &cpu_rolls;
    rollDice(dice, held); ++*rolls; const poker_dice_hand_t hand = evaluateHand(dice);
    if (*rolls >= MAX_ROLLS || hand.rank >= PD_STRAIGHT) { if (player_side) finishPlayerTurn(now_ms); else finishGame(now_ms); return; }
    chooseComputerHolds(dice, held); setMessage("CHOOSING DICE"); scheduleComputer(now_ms); redraw_needed = true;
}
playerRoll() · source line 166
static void playerRoll(uint32_t now_ms)

Calls into this documentation:

  1. Decisionplayer_rolls >= MAX_ROLLS
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. ExecuterollDice(player_dice, player_held);
  3. Execute++player_rolls;
  4. Executeselected_control = player_rolls >= MAX_ROLLS ? ROLL_CONTROL : 0U;
  5. Executesnprintf(message, sizeof(message), "ROLLED %u/3", (unsigned)player_rolls);
  6. Decisionplayer_rolls >= MAX_ROLLS
    True
    1. ExecutefinishPlayerTurn(now_ms);
    False
    1. Executeredraw_needed = true;
Read original function source
static void playerRoll(uint32_t now_ms)
{
    if (player_rolls >= MAX_ROLLS) return;
    rollDice(player_dice, player_held); ++player_rolls; selected_control = player_rolls >= MAX_ROLLS ? ROLL_CONTROL : 0U;
    snprintf(message, sizeof(message), "ROLLED %u/3", (unsigned)player_rolls);
    if (player_rolls >= MAX_ROLLS) finishPlayerTurn(now_ms); else redraw_needed = true;
}
beginGame() · source line 174
static void beginGame(uint32_t now_ms)

Calls into this documentation:

  1. Executememset(player_dice, 0, sizeof(player_dice));
  2. Executememset(cpu_dice, 0, sizeof(cpu_dice));
  3. Executememset(player_held, 0, sizeof(player_held));
  4. Executememset(cpu_held, 0, sizeof(cpu_held));
  5. Executeplayer_rolls = cpu_rolls = 0U;
  6. Executeselected_control = ROLL_CONTROL;
  7. Executestate = PD_PLAYER_TURN;
  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)
{
    memset(player_dice, 0, sizeof(player_dice)); memset(cpu_dice, 0, sizeof(cpu_dice)); memset(player_held, 0, sizeof(player_held)); memset(cpu_held, 0, sizeof(cpu_held));
    player_rolls = cpu_rolls = 0U; selected_control = ROLL_CONTROL; state = PD_PLAYER_TURN; setMessage(auto_mode ? "A STARTS" : "YOUR TURN");
    if (auto_mode) scheduleComputer(now_ms);
    redraw_needed = true;
}
startPokerDice() · source line 182
void startPokerDice(uint32_t now_ms)

Calls into this documentation:

  1. ExecuteloadScores();
  2. Executerunning = true;
  3. Executeauto_mode = false;
  4. Executeselected_mode = 0U;
  5. Executeai_speed = AI_SPEED_DEFAULT_INDEX;
  6. ExecutemodeSelectionReset(&mode_input);
  7. Executestate = PD_SCORES;
  8. Executestate_started = now_ms;
  9. Executeredraw_needed = true;
Read original function source
void startPokerDice(uint32_t now_ms)
{
    loadScores(); running = true; auto_mode = false; selected_mode = 0U; ai_speed = AI_SPEED_DEFAULT_INDEX; modeSelectionReset(&mode_input); state = PD_SCORES; state_started = now_ms; redraw_needed = true;
}
stopPokerDice() · source line 187
void stopPokerDice(void)
  1. Executerunning = false;
Read original function source
void stopPokerDice(void) { running = false; }
pokerDiceAction() · source line 189
void pokerDiceAction(bool action, bool next, bool speed, uint32_t now_ms)

Calls into this documentation:

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

    Continue after this decision.

  2. Decisionstate == PD_MODE
    True
      1. Decisionaction
        True
          1. Executeauto_mode = selected_mode != 0U;
          2. 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. Decisionaction
        True
          1. Executeauto_mode = false;
          2. Executeselected_mode = 0U;
          3. ExecutemodeSelectionReset(&mode_input);
          4. Executestate = PD_MODE;
          5. Executeredraw_needed = true;
        False

        Continue after this decision.

      3. Exit this functionreturn;
    False

    Continue after this decision.

  4. Decisionstate == PD_FINISHED
    True
      1. Decisionaction
        True
        1. ExecutebeginGame(now_ms);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionstate != PD_PLAYER_TURN
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionnext
    True
      1. Executeselected_control = (uint8_t)((selected_control + 1U) % (DICE_COUNT + 1U));
      2. Executeredraw_needed = true;
    False

    Continue after this decision.

  7. Decision!action
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  8. Decisionselected_control == ROLL_CONTROL
    True
      1. ExecuteplayerRoll(now_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  9. Decisionplayer_dice[selected_control]
    True
      1. Executeplayer_held[selected_control] = !player_held[selected_control];
      2. ExecuteaudioPlay(AUDIO_SFX_DICE_HOLD);
      3. Executeredraw_needed = true;
    False

    Continue after this decision.

Read original function source
void pokerDiceAction(bool action, bool next, bool speed, uint32_t now_ms)
{
    if (!running) return;
    if (state == PD_MODE) { if (action) { auto_mode = selected_mode != 0U; beginGame(now_ms); } return; }
    if (auto_mode) { if (speed) { cycleAISpeed(&ai_speed); scheduleComputer(now_ms); redraw_needed = true; } if (action) { auto_mode = false; selected_mode = 0U; modeSelectionReset(&mode_input); state = PD_MODE; redraw_needed = true; } return; }
    if (state == PD_FINISHED) { if (action) beginGame(now_ms); return; }
    if (state != PD_PLAYER_TURN) return;
    if (next) { selected_control = (uint8_t)((selected_control + 1U) % (DICE_COUNT + 1U)); redraw_needed = true; }
    if (!action) return;
    if (selected_control == ROLL_CONTROL) { playerRoll(now_ms); return; }
    if (player_dice[selected_control]) { player_held[selected_control] = !player_held[selected_control]; audioPlay(AUDIO_SFX_DICE_HOLD); redraw_needed = true; }
}
updatePokerDice() · source line 202
void updatePokerDice(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 == PD_SCORES && now_ms - state_started >= SCORE_DISPLAY_MS
    True
      1. Executestate = PD_MODE;
      2. Executeredraw_needed = true;
      3. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionstate == PD_MODE
    True
      1. DecisionmodeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right, now_ms)
        True
          1. Executeselected_mode ^= 1U;
          2. Executeredraw_needed = true;
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

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

        Continue after this decision.

      3. Executeconst uint8_t seconds = (uint8_t)((delay - elapsed + 999U) / 1000U);
      4. Decisionseconds != auto_seconds
        True
          1. Executeauto_seconds = seconds;
          2. Executeredraw_needed = true;
        False

        Continue after this decision.

      5. Exit this functionreturn;
    False

    Continue after this decision.

  5. Decisionstate == PD_PLAYER_RESULT && now_ms >= action_due
    True
      1. ExecutebeginCpuTurn(now_ms);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decision(state == PD_CPU_TURN || (auto_mode && state == PD_PLAYER_TURN)) && now_ms >= action_due
    True
    1. ExecuteautomatedRoll(state == PD_PLAYER_TURN, now_ms);
    False

    Continue after this decision.

Read original function source
void updatePokerDice(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
                     uint32_t now_ms)
{
    if (!running) return;
    if (state == PD_SCORES && now_ms - state_started >= SCORE_DISPLAY_MS) { state = PD_MODE; redraw_needed = true; return; }
    if (state == PD_MODE) {
        if (modeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right, now_ms)) {
            selected_mode ^= 1U;
            redraw_needed = true;
        }
        return;
    }
    if (state == PD_FINISHED && auto_mode) {
        const uint32_t delay = aiRestartDelay(), elapsed = now_ms - auto_restart_started;
        if (elapsed >= delay) { beginGame(now_ms); return; }
        const uint8_t seconds = (uint8_t)((delay - elapsed + 999U) / 1000U);
        if (seconds != auto_seconds) { auto_seconds = seconds; redraw_needed = true; }
        return;
    }
    if (state == PD_PLAYER_RESULT && now_ms >= action_due) { beginCpuTurn(now_ms); return; }
    if ((state == PD_CPU_TURN || (auto_mode && state == PD_PLAYER_TURN)) && now_ms >= action_due) automatedRoll(state == PD_PLAYER_TURN, now_ms);
}
drawPip() · source line 225
static void drawPip(uint16_t *frame, int x, int y)

Calls into this documentation:

  1. Executefill_circle(frame, x, y, 3, COLOR_WHITE);
Read original function source
static void drawPip(uint16_t *frame, int x, int y) { fill_circle(frame, x, y, 3, COLOR_WHITE); }
drawDie() · source line 227
static void drawDie(uint16_t *frame, uint8_t value, int x, int y, bool selected, bool held)

Calls into this documentation:

  1. Executeconst int size = 50;
  2. Executeconst uint16_t border = selected ? COLOR_YELLOW : held ? COLOR_GREEN : COLOR_WHITE;
  3. Executefill_rect(frame, x, y, size, size, COLOR_DARKGREY);
  4. Executedraw_rect(frame, x, y, size, size, border);
  5. Decisionheld
    True
    1. Executedraw_rect(frame, x + 2, y + 2, size - 4, size - 4, COLOR_GREEN);
    False

    Continue after this decision.

  6. Decision!value
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  7. Decisionvalue == 1U || value == 3U || value == 5U
    True
    1. ExecutedrawPip(frame, x + 25, y + 25);
    False

    Continue after this decision.

  8. Decisionvalue >= 2U
    True
      1. ExecutedrawPip(frame, x + 13, y + 13);
      2. ExecutedrawPip(frame, x + 37, y + 37);
    False

    Continue after this decision.

  9. Decisionvalue >= 4U
    True
      1. ExecutedrawPip(frame, x + 37, y + 13);
      2. ExecutedrawPip(frame, x + 13, y + 37);
    False

    Continue after this decision.

  10. Decisionvalue == 6U
    True
      1. ExecutedrawPip(frame, x + 13, y + 25);
      2. ExecutedrawPip(frame, x + 37, y + 25);
    False

    Continue after this decision.

Read original function source
static void drawDie(uint16_t *frame, uint8_t value, int x, int y, bool selected, bool held)
{
    const int size = 50; const uint16_t border = selected ? COLOR_YELLOW : held ? COLOR_GREEN : COLOR_WHITE;
    fill_rect(frame, x, y, size, size, COLOR_DARKGREY); draw_rect(frame, x, y, size, size, border); if (held) draw_rect(frame, x + 2, y + 2, size - 4, size - 4, COLOR_GREEN);
    if (!value) return;
    if (value == 1U || value == 3U || value == 5U) drawPip(frame, x + 25, y + 25);
    if (value >= 2U) { drawPip(frame, x + 13, y + 13); drawPip(frame, x + 37, y + 37); }
    if (value >= 4U) { drawPip(frame, x + 37, y + 13); drawPip(frame, x + 13, y + 37); }
    if (value == 6U) { drawPip(frame, x + 13, y + 25); drawPip(frame, x + 37, y + 25); }
}
drawPokerDice() · source line 238
void drawPokerDice(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[44];
  5. Decisionstate == PD_SCORES
    True
      1. Executedraw_text_centred(frame, 160, 8, "POKER DICE", 3, COLOR_CYAN);
      2. Executedraw_text_centred(frame, 160, 43, "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, 73, 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, 103, text, 2, COLOR_GOLD);
      7. Executedraw_text_centred(frame, 160, 140, "FIVE DICE - THREE ROLLS", 1, COLOR_LIGHTGRAY);
      8. Exit this functionreturn;
    False

    Continue after this decision.

  6. Decisionstate == PD_MODE
    True
      1. Executedraw_text_centred(frame, 160, 8, "POKER DICE", 3, COLOR_CYAN);
      2. ExecutedrawGameGold(frame, 250, 2, 65, true);
      3. Executedraw_text_centred(frame, 160, 44, "SELECT MODE", 2, COLOR_LIGHTGRAY);
      4. Executedraw_text_centred(frame, 83, 81, "PLAY", 2, selected_mode == 0U ? COLOR_YELLOW : COLOR_LIGHTGRAY);
      5. Executedraw_text_centred(frame, 237, 81, "AUTO", 2, selected_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY);
      6. Executedraw_text_centred(frame, 160, 140, "D/B/STICK CHANGE A/K START", 1, COLOR_LIGHTGRAY);
      7. Exit this functionreturn;
    False

    Continue after this decision.

  7. Executeconst bool player_view = state == PD_PLAYER_TURN || state == PD_PLAYER_RESULT;
  8. Executeconst uint8_t *dice = player_view ? player_dice : cpu_dice;
  9. Executeconst bool *held = player_view ? player_held : cpu_held;
  10. Executeconst uint8_t rolls = player_view ? player_rolls : cpu_rolls;
  11. Executedraw_text(frame, 6, 3, "POKER DICE", 2, COLOR_CYAN);
  12. ExecutedrawGameGold(frame, 250, 2, 65, true);
  13. Executedraw_text(frame, 5, 28, auto_mode ? (player_view ? "A" : "B") : (player_view ? "YOU" : "CPU"), 1, COLOR_CYAN);
  14. Executesnprintf(text, sizeof(text), "ROLL %u/3", (unsigned)rolls);
  15. Executedraw_text(frame, 260, 28, text, 1, COLOR_CYAN);
  16. FOR loopuint8_t i = 0U; i < DICE_COUNT; ++i

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

    1. ExecutedrawDie(frame, dice[i], 7 + (int)i * 62, 43, !auto_mode && state == PD_PLAYER_TURN && selected_control == i, held[i]);
  17. Executedraw_text_centred(frame, 160, 101, rolls ? handName(evaluateHand(dice).rank) : "ROLL THE DICE", 2, COLOR_CYAN);
  18. Executedraw_text_centred(frame, 160, 122, message, 1, COLOR_WHITE);
  19. Decision!auto_mode && state == PD_PLAYER_TURN
    True
      1. Executeconst bool selected = selected_control == ROLL_CONTROL;
      2. Executefill_rect(frame, 101, 140, 118, 22, selected ? COLOR_YELLOW : COLOR_BLACK);
      3. Executedraw_rect(frame, 101, 140, 118, 22, COLOR_YELLOW);
      4. Executedraw_text_centred(frame, 160, 145, "ROLL", 2, selected ? COLOR_BLACK : COLOR_WHITE);
    False
    1. Decisionauto_mode
      True
        1. Decisionstate == PD_FINISHED
          True
          1. Executesnprintf(text, sizeof(text), "NEXT %u E SPEED A/K STOP", (unsigned)auto_seconds);
          False
          1. Executesnprintf(text, sizeof(text), "AUTO %s E SPEED A/K STOP", aiSpeedShortText(ai_speed));
        2. Executedraw_text_centred(frame, 160, 151, text, 1, COLOR_LIGHTGRAY);
      False
      1. Decisionstate == PD_FINISHED
        True
        1. Executedraw_text_centred(frame, 160, 151, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
        False
        1. Executedraw_text_centred(frame, 160, 151, "CPU THINKING", 1, COLOR_LIGHTGRAY);
Read original function source
void drawPokerDice(uint16_t *frame)
{
    if (!running || !redraw_needed) return;
    redraw_needed = false; clear(frame, COLOR_BLACK); char text[44];
    if (state == PD_SCORES) {
        draw_text_centred(frame, 160, 8, "POKER DICE", 3, COLOR_CYAN); draw_text_centred(frame, 160, 43, "GAME RECORD", 2, COLOR_LIGHTGRAY);
        snprintf(text, sizeof(text), "WINS %u    LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses); draw_text_centred(frame, 160, 73, text, 2, COLOR_WHITE);
        snprintf(text, sizeof(text), "DRAWS %u    STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak); draw_text_centred(frame, 160, 103, text, 2, COLOR_GOLD);
        draw_text_centred(frame, 160, 140, "FIVE DICE - THREE ROLLS", 1, COLOR_LIGHTGRAY); return;
    }
    if (state == PD_MODE) {
        draw_text_centred(frame, 160, 8, "POKER DICE", 3, COLOR_CYAN); drawGameGold(frame, 250, 2, 65, true); draw_text_centred(frame, 160, 44, "SELECT MODE", 2, COLOR_LIGHTGRAY);
        draw_text_centred(frame, 83, 81, "PLAY", 2, selected_mode == 0U ? COLOR_YELLOW : COLOR_LIGHTGRAY); draw_text_centred(frame, 237, 81, "AUTO", 2, selected_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY);
        draw_text_centred(frame, 160, 140, "D/B/STICK CHANGE  A/K START", 1, COLOR_LIGHTGRAY); return;
    }
    const bool player_view = state == PD_PLAYER_TURN || state == PD_PLAYER_RESULT;
    const uint8_t *dice = player_view ? player_dice : cpu_dice; const bool *held = player_view ? player_held : cpu_held; const uint8_t rolls = player_view ? player_rolls : cpu_rolls;
    draw_text(frame, 6, 3, "POKER DICE", 2, COLOR_CYAN); drawGameGold(frame, 250, 2, 65, true);
    draw_text(frame, 5, 28, auto_mode ? (player_view ? "A" : "B") : (player_view ? "YOU" : "CPU"), 1, COLOR_CYAN); snprintf(text, sizeof(text), "ROLL %u/3", (unsigned)rolls); draw_text(frame, 260, 28, text, 1, COLOR_CYAN);
    for (uint8_t i = 0U; i < DICE_COUNT; ++i) drawDie(frame, dice[i], 7 + (int)i * 62, 43, !auto_mode && state == PD_PLAYER_TURN && selected_control == i, held[i]);
    draw_text_centred(frame, 160, 101, rolls ? handName(evaluateHand(dice).rank) : "ROLL THE DICE", 2, COLOR_CYAN); draw_text_centred(frame, 160, 122, message, 1, COLOR_WHITE);
    if (!auto_mode && state == PD_PLAYER_TURN) {
        const bool selected = selected_control == ROLL_CONTROL; fill_rect(frame, 101, 140, 118, 22, selected ? COLOR_YELLOW : COLOR_BLACK); draw_rect(frame, 101, 140, 118, 22, COLOR_YELLOW); draw_text_centred(frame, 160, 145, "ROLL", 2, selected ? COLOR_BLACK : COLOR_WHITE);
    } else if (auto_mode) {
        if (state == PD_FINISHED) snprintf(text, sizeof(text), "NEXT %u  E SPEED  A/K STOP", (unsigned)auto_seconds); else snprintf(text, sizeof(text), "AUTO %s  E SPEED  A/K STOP", aiSpeedShortText(ai_speed));
        draw_text_centred(frame, 160, 151, text, 1, COLOR_LIGHTGRAY);
    } else if (state == PD_FINISHED) draw_text_centred(frame, 160, 151, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
    else draw_text_centred(frame, 160, 151, "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 "poker_dice.h"

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

#include <stdio.h>
#include <string.h>

#define DICE_COUNT 5U
#define MAX_ROLLS 3U
#define ROLL_CONTROL DICE_COUNT
#define SCORE_DISPLAY_MS 2000U
#define POKER_DICE_WIN_GOLD 10U
#define POKER_DICE_SCORE_NAMESPACE "arcade_results"
#define POKER_DICE_SCORE_KEY "poker_dice"

typedef enum { PD_SCORES, PD_MODE, PD_PLAYER_TURN, PD_PLAYER_RESULT, PD_CPU_TURN, PD_FINISHED } poker_dice_state_t;
typedef enum { PD_BUST, PD_ONE_PAIR, PD_TWO_PAIRS, PD_THREE_KIND, PD_SMALL_STRAIGHT, PD_STRAIGHT, PD_FULL_HOUSE, PD_FOUR_KIND, PD_FIVE_KIND } poker_dice_hand_rank_t;

typedef struct { poker_dice_hand_rank_t rank; uint8_t tie[DICE_COUNT]; } poker_dice_hand_t;
typedef struct { uint32_t version, wins, losses, draws, streak; } poker_dice_scores_t;

static poker_dice_scores_t scores;
static poker_dice_state_t state;
static uint8_t player_dice[DICE_COUNT], cpu_dice[DICE_COUNT];
static bool player_held[DICE_COUNT], cpu_held[DICE_COUNT];
static uint8_t player_rolls, cpu_rolls, selected_control, selected_mode, ai_speed, auto_seconds;
static uint32_t state_started, action_due, auto_restart_started;
static bool auto_mode, running, redraw_needed;
static mode_selection_input_t mode_input;
static char message[32];

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

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

static void saveScores(void) { (void)game_storage_write(POKER_DICE_SCORE_NAMESPACE, POKER_DICE_SCORE_KEY, &scores, sizeof(scores)); }

static void countDice(const uint8_t dice[DICE_COUNT], uint8_t counts[7])
{
    memset(counts, 0, 7U);
    for (uint8_t i = 0; i < DICE_COUNT; ++i) if (dice[i] >= 1U && dice[i] <= 6U) ++counts[dice[i]];
}

static void addRemainingDescending(poker_dice_hand_t *hand, uint8_t *tie_index, const uint8_t counts[7], uint8_t excluded1, uint8_t excluded2)
{
    for (int value = 6; value >= 1; --value) {
        if (value == excluded1 || value == excluded2) continue;
        for (uint8_t count = 0; count < counts[value] && *tie_index < DICE_COUNT; ++count)
            hand->tie[(*tie_index)++] = (uint8_t)value;
    }
}

static poker_dice_hand_t evaluateHand(const uint8_t dice[DICE_COUNT])
{
    uint8_t counts[7], five = 0U, four = 0U, three = 0U, pair_high = 0U, pair_low = 0U;
    poker_dice_hand_t hand = { .rank = PD_BUST, .tie = {0} };
    countDice(dice, counts);
    for (uint8_t value = 1U; value <= 6U; ++value) {
        if (counts[value] == 5U) five = value;
        else if (counts[value] == 4U) four = value;
        else if (counts[value] == 3U) three = value;
        else if (counts[value] == 2U) { pair_low = pair_high; pair_high = value; }
    }
    if (five) { hand.rank = PD_FIVE_KIND; hand.tie[0] = five; return hand; }
    if (four) { uint8_t tie = 1U; hand.rank = PD_FOUR_KIND; hand.tie[0] = four; addRemainingDescending(&hand, &tie, counts, four, 0U); return hand; }
    if (three && pair_high) { hand.rank = PD_FULL_HOUSE; hand.tie[0] = three; hand.tie[1] = pair_high; return hand; }
    bool straight_low = true, straight_high = true;
    for (uint8_t value = 1U; value <= 5U; ++value) straight_low &= counts[value] == 1U;
    for (uint8_t value = 2U; value <= 6U; ++value) straight_high &= counts[value] == 1U;
    if (straight_low || straight_high) { hand.rank = PD_STRAIGHT; hand.tie[0] = straight_high ? 6U : 5U; return hand; }
    for (int start = 3; start >= 1; --start) {
        bool small_straight = true;
        for (uint8_t offset = 0U; offset < 4U; ++offset) small_straight &= counts[start + offset] > 0U;
        if (!small_straight) continue;
        hand.rank = PD_SMALL_STRAIGHT; hand.tie[0] = (uint8_t)(start + 3); uint8_t tie = 1U;
        for (int value = 6; value >= 1; --value) for (uint8_t count = (value >= start && value <= start + 3) ? 1U : 0U; count < counts[value] && tie < DICE_COUNT; ++count) hand.tie[tie++] = (uint8_t)value;
        return hand;
    }
    if (three) { uint8_t tie = 1U; hand.rank = PD_THREE_KIND; hand.tie[0] = three; addRemainingDescending(&hand, &tie, counts, three, 0U); return hand; }
    if (pair_low) { uint8_t tie = 2U; hand.rank = PD_TWO_PAIRS; hand.tie[0] = pair_high; hand.tie[1] = pair_low; addRemainingDescending(&hand, &tie, counts, pair_high, pair_low); return hand; }
    if (pair_high) { uint8_t tie = 1U; hand.rank = PD_ONE_PAIR; hand.tie[0] = pair_high; addRemainingDescending(&hand, &tie, counts, pair_high, 0U); return hand; }
    uint8_t tie = 0U; addRemainingDescending(&hand, &tie, counts, 0U, 0U); return hand;
}

static int8_t compareHands(poker_dice_hand_t left, poker_dice_hand_t right)
{
    if (left.rank != right.rank) return left.rank > right.rank ? 1 : -1;
    for (uint8_t i = 0U; i < DICE_COUNT; ++i) if (left.tie[i] != right.tie[i]) return left.tie[i] > right.tie[i] ? 1 : -1;
    return 0;
}

static const char *handName(poker_dice_hand_rank_t rank)
{
    static const char *const names[] = { "BUST", "ONE PAIR", "TWO PAIRS", "THREE KIND", "SMALL STRAIGHT", "STRAIGHT", "FULL HOUSE", "FOUR KIND", "FIVE KIND" };
    return names[rank];
}

static void rollDice(uint8_t dice[DICE_COUNT], const bool held[DICE_COUNT])
{
    for (uint8_t i = 0U; i < DICE_COUNT; ++i) if (!held[i]) dice[i] = (uint8_t)(esp_random() % 6U) + 1U;
    audioPlay(AUDIO_SFX_DICE_ROLL);
}

static void scheduleComputer(uint32_t now_ms) { action_due = now_ms + aiSpeedDelay(ai_speed); }

static void chooseComputerHolds(const uint8_t dice[DICE_COUNT], bool held[DICE_COUNT])
{
    uint8_t counts[7]; poker_dice_hand_t hand = evaluateHand(dice);
    memset(held, 0, DICE_COUNT * sizeof(held[0])); countDice(dice, counts);
    if (hand.rank >= PD_STRAIGHT) { for (uint8_t i = 0U; i < DICE_COUNT; ++i) held[i] = true; return; }
    if (hand.rank == PD_SMALL_STRAIGHT) {
        const uint8_t start = hand.tie[0] - 3U; bool value_held[7] = {false};
        for (uint8_t i = 0U; i < DICE_COUNT; ++i) if (dice[i] >= start && dice[i] <= start + 3U && !value_held[dice[i]]) { held[i] = true; value_held[dice[i]] = true; }
        return;
    }
    const uint8_t minimum_group = hand.rank == PD_TWO_PAIRS ? 2U : hand.rank == PD_THREE_KIND ? 3U : hand.rank == PD_ONE_PAIR ? 2U : 0U;
    if (minimum_group) { for (uint8_t i = 0U; i < DICE_COUNT; ++i) held[i] = counts[dice[i]] >= minimum_group; return; }
    uint8_t best_start = 0U, best_length = 0U;
    for (uint8_t start = 1U; start <= 6U; ++start) { uint8_t length = 0U; while (start + length <= 6U && counts[start + length]) ++length; if (length > best_length) { best_start = start; best_length = length; } }
    if (best_length >= 3U) for (uint8_t i = 0U; i < DICE_COUNT; ++i) held[i] = dice[i] >= best_start && dice[i] < best_start + best_length;
}

static void beginCpuTurn(uint32_t now_ms)
{
    state = PD_CPU_TURN; cpu_rolls = 0U; memset(cpu_dice, 0, sizeof(cpu_dice)); memset(cpu_held, 0, sizeof(cpu_held));
    setMessage(auto_mode ? "B TURN" : "CPU TURN"); scheduleComputer(now_ms); redraw_needed = true;
}

static void finishPlayerTurn(uint32_t now_ms)
{
    state = PD_PLAYER_RESULT; setMessage(handName(evaluateHand(player_dice).rank)); scheduleComputer(now_ms); redraw_needed = true;
}

static void finishGame(uint32_t now_ms)
{
    const int8_t result = compareHands(evaluateHand(player_dice), evaluateHand(cpu_dice));
    state = PD_FINISHED;
    if (auto_mode) { setMessage(result > 0 ? "A WINS" : result < 0 ? "B WINS" : "DRAW"); auto_restart_started = now_ms; auto_seconds = (uint8_t)((aiRestartDelay() + 999U) / 1000U); }
    else if (result > 0) { ++scores.wins; ++scores.streak; addUserGoldWon(POKER_DICE_WIN_GOLD); saveScores(); setMessage("YOU WIN +10 GOLD"); }
    else if (result < 0) { ++scores.losses; scores.streak = 0U; saveScores(); setMessage("CPU WINS"); }
    else { ++scores.draws; scores.streak = 0U; saveScores(); setMessage("DRAW"); }
    audioPlay(result > 0 ? AUDIO_SFX_WAVE_CLEAR : result < 0 ? AUDIO_SFX_GAME_OVER : AUDIO_SFX_MENU_SELECT);
    redraw_needed = true;
}

static void automatedRoll(bool player_side, uint32_t now_ms)
{
    uint8_t *dice = player_side ? player_dice : cpu_dice; bool *held = player_side ? player_held : cpu_held; uint8_t *rolls = player_side ? &player_rolls : &cpu_rolls;
    rollDice(dice, held); ++*rolls; const poker_dice_hand_t hand = evaluateHand(dice);
    if (*rolls >= MAX_ROLLS || hand.rank >= PD_STRAIGHT) { if (player_side) finishPlayerTurn(now_ms); else finishGame(now_ms); return; }
    chooseComputerHolds(dice, held); setMessage("CHOOSING DICE"); scheduleComputer(now_ms); redraw_needed = true;
}

static void playerRoll(uint32_t now_ms)
{
    if (player_rolls >= MAX_ROLLS) return;
    rollDice(player_dice, player_held); ++player_rolls; selected_control = player_rolls >= MAX_ROLLS ? ROLL_CONTROL : 0U;
    snprintf(message, sizeof(message), "ROLLED %u/3", (unsigned)player_rolls);
    if (player_rolls >= MAX_ROLLS) finishPlayerTurn(now_ms); else redraw_needed = true;
}

static void beginGame(uint32_t now_ms)
{
    memset(player_dice, 0, sizeof(player_dice)); memset(cpu_dice, 0, sizeof(cpu_dice)); memset(player_held, 0, sizeof(player_held)); memset(cpu_held, 0, sizeof(cpu_held));
    player_rolls = cpu_rolls = 0U; selected_control = ROLL_CONTROL; state = PD_PLAYER_TURN; setMessage(auto_mode ? "A STARTS" : "YOUR TURN");
    if (auto_mode) scheduleComputer(now_ms);
    redraw_needed = true;
}

void startPokerDice(uint32_t now_ms)
{
    loadScores(); running = true; auto_mode = false; selected_mode = 0U; ai_speed = AI_SPEED_DEFAULT_INDEX; modeSelectionReset(&mode_input); state = PD_SCORES; state_started = now_ms; redraw_needed = true;
}

void stopPokerDice(void) { running = false; }

void pokerDiceAction(bool action, bool next, bool speed, uint32_t now_ms)
{
    if (!running) return;
    if (state == PD_MODE) { if (action) { auto_mode = selected_mode != 0U; beginGame(now_ms); } return; }
    if (auto_mode) { if (speed) { cycleAISpeed(&ai_speed); scheduleComputer(now_ms); redraw_needed = true; } if (action) { auto_mode = false; selected_mode = 0U; modeSelectionReset(&mode_input); state = PD_MODE; redraw_needed = true; } return; }
    if (state == PD_FINISHED) { if (action) beginGame(now_ms); return; }
    if (state != PD_PLAYER_TURN) return;
    if (next) { selected_control = (uint8_t)((selected_control + 1U) % (DICE_COUNT + 1U)); redraw_needed = true; }
    if (!action) return;
    if (selected_control == ROLL_CONTROL) { playerRoll(now_ms); return; }
    if (player_dice[selected_control]) { player_held[selected_control] = !player_held[selected_control]; audioPlay(AUDIO_SFX_DICE_HOLD); redraw_needed = true; }
}

void updatePokerDice(int x_offset, int y_offset, bool joystick_valid, bool left, bool right,
                     uint32_t now_ms)
{
    if (!running) return;
    if (state == PD_SCORES && now_ms - state_started >= SCORE_DISPLAY_MS) { state = PD_MODE; redraw_needed = true; return; }
    if (state == PD_MODE) {
        if (modeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right, now_ms)) {
            selected_mode ^= 1U;
            redraw_needed = true;
        }
        return;
    }
    if (state == PD_FINISHED && auto_mode) {
        const uint32_t delay = aiRestartDelay(), elapsed = now_ms - auto_restart_started;
        if (elapsed >= delay) { beginGame(now_ms); return; }
        const uint8_t seconds = (uint8_t)((delay - elapsed + 999U) / 1000U);
        if (seconds != auto_seconds) { auto_seconds = seconds; redraw_needed = true; }
        return;
    }
    if (state == PD_PLAYER_RESULT && now_ms >= action_due) { beginCpuTurn(now_ms); return; }
    if ((state == PD_CPU_TURN || (auto_mode && state == PD_PLAYER_TURN)) && now_ms >= action_due) automatedRoll(state == PD_PLAYER_TURN, now_ms);
}

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

static void drawDie(uint16_t *frame, uint8_t value, int x, int y, bool selected, bool held)
{
    const int size = 50; const uint16_t border = selected ? COLOR_YELLOW : held ? COLOR_GREEN : COLOR_WHITE;
    fill_rect(frame, x, y, size, size, COLOR_DARKGREY); draw_rect(frame, x, y, size, size, border); if (held) draw_rect(frame, x + 2, y + 2, size - 4, size - 4, COLOR_GREEN);
    if (!value) return;
    if (value == 1U || value == 3U || value == 5U) drawPip(frame, x + 25, y + 25);
    if (value >= 2U) { drawPip(frame, x + 13, y + 13); drawPip(frame, x + 37, y + 37); }
    if (value >= 4U) { drawPip(frame, x + 37, y + 13); drawPip(frame, x + 13, y + 37); }
    if (value == 6U) { drawPip(frame, x + 13, y + 25); drawPip(frame, x + 37, y + 25); }
}

void drawPokerDice(uint16_t *frame)
{
    if (!running || !redraw_needed) return;
    redraw_needed = false; clear(frame, COLOR_BLACK); char text[44];
    if (state == PD_SCORES) {
        drawPet2001TextCentredTransparent(frame, 160, 8, "POKER DICE", COLOR_CYAN, 3);
        drawPet2001TextCentredTransparent(frame, 160, 43, "GAME RECORD", COLOR_LIGHTGRAY, 2);
        // Score totals are uint32 values; one-times PET cells keep their longest form on-screen.
        snprintf(text, sizeof(text), "WINS %u    LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses);
        drawPet2001TextCentredTransparent(frame, 160, 73, text, COLOR_WHITE, 1);
        snprintf(text, sizeof(text), "DRAWS %u    STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak);
        drawPet2001TextCentredTransparent(frame, 160, 103, text, COLOR_GOLD, 1);
        drawPet2001TextCentredTransparent(frame, 160, 140, "FIVE DICE - THREE ROLLS", COLOR_LIGHTGRAY, 1);
        return;
    }
    if (state == PD_MODE) {
        drawPet2001TextCentredTransparent(frame, 160, 8, "POKER DICE", COLOR_CYAN, 3);
        drawGameGold(frame, 250, 2, 65, true);
        drawPet2001TextCentredTransparent(frame, 160, 44, "SELECT MODE", COLOR_LIGHTGRAY, 2);
        drawPet2001TextCentredTransparent(frame, 83, 81, "PLAY", selected_mode == 0U ? COLOR_YELLOW : COLOR_LIGHTGRAY, 2);
        drawPet2001TextCentredTransparent(frame, 237, 81, "AUTO", selected_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY, 2);
        drawPet2001TextCentredTransparent(frame, 160, 140, "D/B/STICK CHANGE  A/K START", COLOR_LIGHTGRAY, 1);
        return;
    }
    const bool player_view = state == PD_PLAYER_TURN || state == PD_PLAYER_RESULT;
    const uint8_t *dice = player_view ? player_dice : cpu_dice; const bool *held = player_view ? player_held : cpu_held; const uint8_t rolls = player_view ? player_rolls : cpu_rolls;
    drawPet2001TextTransparent(frame, 6, 3, "POKER DICE", COLOR_CYAN, 2);
    drawGameGold(frame, 250, 2, 65, true);
    drawPet2001TextTransparent(frame, 5, 28, auto_mode ? (player_view ? "A" : "B") : (player_view ? "YOU" : "CPU"), COLOR_CYAN, 1);
    snprintf(text, sizeof(text), "ROLL %u/3", (unsigned)rolls);
    drawPet2001TextTransparent(frame, 256, 28, text, COLOR_CYAN, 1);
    for (uint8_t i = 0U; i < DICE_COUNT; ++i) drawDie(frame, dice[i], 7 + (int)i * 62, 43, !auto_mode && state == PD_PLAYER_TURN && selected_control == i, held[i]);
    drawPet2001TextCentredTransparent(frame, 160, 101, rolls ? handName(evaluateHand(dice).rank) : "ROLL THE DICE", COLOR_CYAN, 2);
    drawPet2001TextCentredTransparent(frame, 160, 122, message, COLOR_WHITE, 1);
    if (!auto_mode && state == PD_PLAYER_TURN) {
        const bool selected = selected_control == ROLL_CONTROL;
        fill_rect(frame, 101, 140, 118, 22, selected ? COLOR_YELLOW : COLOR_BLACK);
        draw_rect(frame, 101, 140, 118, 22, COLOR_YELLOW);
        drawPet2001TextCentredTransparent(frame, 160, 145, "ROLL", selected ? COLOR_BLACK : COLOR_WHITE, 2);
    } else if (auto_mode) {
        if (state == PD_FINISHED) snprintf(text, sizeof(text), "NEXT %u  E SPEED  A/K STOP", (unsigned)auto_seconds);
        else snprintf(text, sizeof(text), "AUTO %s  E SPEED  A/K STOP", aiSpeedShortText(ai_speed));
        drawPet2001TextCentredTransparent(frame, 160, 151, text, COLOR_LIGHTGRAY, 1);
    } else if (state == PD_FINISHED) {
        drawPet2001TextCentredTransparent(frame, 160, 151, "A/K NEW GAME", COLOR_LIGHTGRAY, 1);
    } else {
        drawPet2001TextCentredTransparent(frame, 160, 151, "CPU THINKING", COLOR_LIGHTGRAY, 1);
    }
}