Poker Dice
Source: src/poker_dice.c · 287 lines · 24 functions.
Screen captures
Views of this part running on the Mini Arcade.


Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
setMessage() · source line 38
static void setMessage(const char *text)
- Execute
snprintf(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:
- Execute
bool found = false; - Execute
scores = (poker_dice_scores_t){ .version = 1U }; - Decision
!game_storage_read(POKER_DICE_SCORE_NAMESPACE, POKER_DICE_SCORE_KEY, &scores, sizeof(scores), &found) || !found || scores.version != 1UTrue- Execute
scores = (poker_dice_scores_t){ .version = 1U };
FalseContinue 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:
- 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])
- Execute
memset(counts, 0, 7U); - FOR loop
uint8_t i = 0; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
dice[i] >= 1U && dice[i] <= 6UTrue- Execute
++counts[dice[i]];
FalseContinue 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)
- FOR loop
int value = 6; value >= 1; --valueRepeat body while the loop condition permits; then continue below.
- Decision
value == excluded1 || value == excluded2True- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
uint8_t count = 0; count < counts[value] && *tie_index < DICE_COUNT; ++countRepeat body while the loop condition permits; then continue below.
- Execute
hand->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:
- Execute
uint8_t counts[7], five = 0U, four = 0U, three = 0U, pair_high = 0U, pair_low = 0U; - Execute
poker_dice_hand_t hand = { .rank = PD_BUST, .tie = {0} }; - Execute
countDice(dice, counts); - FOR loop
uint8_t value = 1U; value <= 6U; ++valueRepeat body while the loop condition permits; then continue below.
- Decision
counts[value] == 5UTrue- Execute
five = value;
False- Decision
counts[value] == 4UTrue- Execute
four = value;
False- Decision
counts[value] == 3UTrue- Execute
three = value;
False- Decision
counts[value] == 2UTrue- Execute
pair_low = pair_high; - Execute
pair_high = value;
FalseContinue after this decision.
- Decision
fiveTrue- Execute
hand.rank = PD_FIVE_KIND; - Execute
hand.tie[0] = five; - Exit this function
return hand;
FalseContinue after this decision.
- Decision
fourTrue- Execute
uint8_t tie = 1U; - Execute
hand.rank = PD_FOUR_KIND; - Execute
hand.tie[0] = four; - Execute
addRemainingDescending(&hand, &tie, counts, four, 0U); - Exit this function
return hand;
FalseContinue after this decision.
- Decision
three && pair_highTrue- Execute
hand.rank = PD_FULL_HOUSE; - Execute
hand.tie[0] = three; - Execute
hand.tie[1] = pair_high; - Exit this function
return hand;
FalseContinue after this decision.
- Execute
bool straight_low = true, straight_high = true; - FOR loop
uint8_t value = 1U; value <= 5U; ++valueRepeat body while the loop condition permits; then continue below.
- Execute
straight_low &= counts[value] == 1U;
- FOR loop
uint8_t value = 2U; value <= 6U; ++valueRepeat body while the loop condition permits; then continue below.
- Execute
straight_high &= counts[value] == 1U;
- Decision
straight_low || straight_highTrue- Execute
hand.rank = PD_STRAIGHT; - Execute
hand.tie[0] = straight_high ? 6U : 5U; - Exit this function
return hand;
FalseContinue after this decision.
- FOR loop
int start = 3; start >= 1; --startRepeat body while the loop condition permits; then continue below.
- Execute
bool small_straight = true; - FOR loop
uint8_t offset = 0U; offset < 4U; ++offsetRepeat body while the loop condition permits; then continue below.
- Execute
small_straight &= counts[start + offset] > 0U;
- Decision
!small_straightTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
hand.rank = PD_SMALL_STRAIGHT; - Execute
hand.tie[0] = (uint8_t)(start + 3); - Execute
uint8_t tie = 1U; - FOR loop
int value = 6; value >= 1; --valueRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t count = (value >= start && value <= start + 3) ? 1U : 0U; count < counts[value] && tie < DICE_COUNT; ++countRepeat body while the loop condition permits; then continue below.
- Execute
hand.tie[tie++] = (uint8_t)value;
- Exit this function
return hand;
- Decision
threeTrue- Execute
uint8_t tie = 1U; - Execute
hand.rank = PD_THREE_KIND; - Execute
hand.tie[0] = three; - Execute
addRemainingDescending(&hand, &tie, counts, three, 0U); - Exit this function
return hand;
FalseContinue after this decision.
- Decision
pair_lowTrue- Execute
uint8_t tie = 2U; - Execute
hand.rank = PD_TWO_PAIRS; - Execute
hand.tie[0] = pair_high; - Execute
hand.tie[1] = pair_low; - Execute
addRemainingDescending(&hand, &tie, counts, pair_high, pair_low); - Exit this function
return hand;
FalseContinue after this decision.
- Decision
pair_highTrue- Execute
uint8_t tie = 1U; - Execute
hand.rank = PD_ONE_PAIR; - Execute
hand.tie[0] = pair_high; - Execute
addRemainingDescending(&hand, &tie, counts, pair_high, 0U); - Exit this function
return hand;
FalseContinue after this decision.
- Execute
uint8_t tie = 0U; - Execute
addRemainingDescending(&hand, &tie, counts, 0U, 0U); - Exit this function
return 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)
- Decision
left.rank != right.rankTrue- Exit this function
return left.rank > right.rank ? 1 : -1;
FalseContinue after this decision.
- FOR loop
uint8_t i = 0U; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
left.tie[i] != right.tie[i]True- Exit this function
return left.tie[i] > right.tie[i] ? 1 : -1;
FalseContinue after this decision.
- Exit this function
return 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)
- Execute
static const char *const names[] = { "BUST", "ONE PAIR", "TWO PAIRS", "THREE KIND", "SMALL STRAIGHT", "STRAIGHT", "FULL HOUSE", "FOUR KIND", "FIVE KIND" }; - Exit this function
return 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:
- FOR loop
uint8_t i = 0U; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
!held[i]True- Execute
dice[i] = (uint8_t)(esp_random() % 6U) + 1U;
FalseContinue after this decision.
- Execute
audioPlay(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:
- Execute
action_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:
- Execute
uint8_t counts[7]; - Execute
poker_dice_hand_t hand = evaluateHand(dice); - Execute
memset(held, 0, DICE_COUNT * sizeof(held[0])); - Execute
countDice(dice, counts); - Decision
hand.rank >= PD_STRAIGHTTrue- FOR loop
uint8_t i = 0U; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Execute
held[i] = true;
- Exit this function
return;
FalseContinue after this decision.
- Decision
hand.rank == PD_SMALL_STRAIGHTTrue- Execute
const uint8_t start = hand.tie[0] - 3U; - Execute
bool value_held[7] = {false}; - FOR loop
uint8_t i = 0U; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Decision
dice[i] >= start && dice[i] <= start + 3U && !value_held[dice[i]]True- Execute
held[i] = true; - Execute
value_held[dice[i]] = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint8_t minimum_group = hand.rank == PD_TWO_PAIRS ? 2U : hand.rank == PD_THREE_KIND ? 3U : hand.rank == PD_ONE_PAIR ? 2U : 0U; - Decision
minimum_groupTrue- FOR loop
uint8_t i = 0U; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Execute
held[i] = counts[dice[i]] >= minimum_group;
- Exit this function
return;
FalseContinue after this decision.
- Execute
uint8_t best_start = 0U, best_length = 0U; - FOR loop
uint8_t start = 1U; start <= 6U; ++startRepeat body while the loop condition permits; then continue below.
- Execute
uint8_t length = 0U; - WHILE loop
start + length <= 6U && counts[start + length]Repeat body while the loop condition permits; then continue below.
- Execute
++length;
- Decision
length > best_lengthTrue- Execute
best_start = start; - Execute
best_length = length;
FalseContinue after this decision.
- Decision
best_length >= 3UTrue- FOR loop
uint8_t i = 0U; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Execute
held[i] = dice[i] >= best_start && dice[i] < best_start + best_length;
FalseContinue 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:
- Execute
state = PD_CPU_TURN; - Execute
cpu_rolls = 0U; - Execute
memset(cpu_dice, 0, sizeof(cpu_dice)); - Execute
memset(cpu_held, 0, sizeof(cpu_held)); - Execute
setMessage(auto_mode ? "B TURN" : "CPU TURN"); - Execute
scheduleComputer(now_ms); - Execute
redraw_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:
- Execute
state = PD_PLAYER_RESULT; - Execute
setMessage(handName(evaluateHand(player_dice).rank)); - Execute
scheduleComputer(now_ms); - Execute
redraw_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:
- Execute
const int8_t result = compareHands(evaluateHand(player_dice), evaluateHand(cpu_dice)); - Execute
state = PD_FINISHED; - Decision
auto_modeTrue- Execute
setMessage(result > 0 ? "A WINS" : result < 0 ? "B WINS" : "DRAW"); - Execute
auto_restart_started = now_ms; - Execute
auto_seconds = (uint8_t)((aiRestartDelay() + 999U) / 1000U);
False- Decision
result > 0True- Execute
++scores.wins; - Execute
++scores.streak; - Execute
addUserGoldWon(POKER_DICE_WIN_GOLD); - Execute
saveScores(); - Execute
setMessage("YOU WIN +10 GOLD");
False- Decision
result < 0True- Execute
++scores.losses; - Execute
scores.streak = 0U; - Execute
saveScores(); - Execute
setMessage("CPU WINS");
False- Execute
++scores.draws; - Execute
scores.streak = 0U; - Execute
saveScores(); - Execute
setMessage("DRAW");
- Execute
audioPlay(result > 0 ? AUDIO_SFX_WAVE_CLEAR : result < 0 ? AUDIO_SFX_GAME_OVER : AUDIO_SFX_MENU_SELECT); - Execute
redraw_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:
- Execute
uint8_t *dice = player_side ? player_dice : cpu_dice; - Execute
bool *held = player_side ? player_held : cpu_held; - Execute
uint8_t *rolls = player_side ? &player_rolls : &cpu_rolls; - Execute
rollDice(dice, held); - Execute
++*rolls; - Execute
const poker_dice_hand_t hand = evaluateHand(dice); - Decision
*rolls >= MAX_ROLLS || hand.rank >= PD_STRAIGHTTrue- Decision
player_sideTrue- Execute
finishPlayerTurn(now_ms);
False- Execute
finishGame(now_ms);
- Exit this function
return;
FalseContinue after this decision.
- Execute
chooseComputerHolds(dice, held); - Execute
setMessage("CHOOSING DICE"); - Execute
scheduleComputer(now_ms); - Execute
redraw_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:
- Decision
player_rolls >= MAX_ROLLSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
rollDice(player_dice, player_held); - Execute
++player_rolls; - Execute
selected_control = player_rolls >= MAX_ROLLS ? ROLL_CONTROL : 0U; - Execute
snprintf(message, sizeof(message), "ROLLED %u/3", (unsigned)player_rolls); - Decision
player_rolls >= MAX_ROLLSTrue- Execute
finishPlayerTurn(now_ms);
False- Execute
redraw_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:
- Execute
memset(player_dice, 0, sizeof(player_dice)); - Execute
memset(cpu_dice, 0, sizeof(cpu_dice)); - Execute
memset(player_held, 0, sizeof(player_held)); - Execute
memset(cpu_held, 0, sizeof(cpu_held)); - Execute
player_rolls = cpu_rolls = 0U; - Execute
selected_control = ROLL_CONTROL; - Execute
state = PD_PLAYER_TURN; - Execute
setMessage(auto_mode ? "A STARTS" : "YOUR TURN"); - Decision
auto_modeTrue- Execute
scheduleComputer(now_ms);
FalseContinue after this decision.
- Execute
redraw_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:
- Execute
loadScores(); - Execute
running = true; - Execute
auto_mode = false; - Execute
selected_mode = 0U; - Execute
ai_speed = AI_SPEED_DEFAULT_INDEX; - Execute
modeSelectionReset(&mode_input); - Execute
state = PD_SCORES; - Execute
state_started = now_ms; - Execute
redraw_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)
- Execute
running = 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:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == PD_MODETrue- Decision
actionTrue- Execute
auto_mode = selected_mode != 0U; - Execute
beginGame(now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
auto_modeTrue- Decision
speedTrue- Execute
cycleAISpeed(&ai_speed); - Execute
scheduleComputer(now_ms); - Execute
redraw_needed = true;
FalseContinue after this decision.
- Decision
actionTrue- Execute
auto_mode = false; - Execute
selected_mode = 0U; - Execute
modeSelectionReset(&mode_input); - Execute
state = PD_MODE; - Execute
redraw_needed = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == PD_FINISHEDTrue- Decision
actionTrue- Execute
beginGame(now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state != PD_PLAYER_TURNTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
nextTrue- Execute
selected_control = (uint8_t)((selected_control + 1U) % (DICE_COUNT + 1U)); - Execute
redraw_needed = true;
FalseContinue after this decision.
- Decision
!actionTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
selected_control == ROLL_CONTROLTrue- Execute
playerRoll(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
player_dice[selected_control]True- Execute
player_held[selected_control] = !player_held[selected_control]; - Execute
audioPlay(AUDIO_SFX_DICE_HOLD); - Execute
redraw_needed = true;
FalseContinue 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:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == PD_SCORES && now_ms - state_started >= SCORE_DISPLAY_MSTrue- Execute
state = PD_MODE; - Execute
redraw_needed = true; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == PD_MODETrue- Decision
modeSelectionNext(&mode_input, x_offset, y_offset, joystick_valid, left, right, now_ms)True- Execute
selected_mode ^= 1U; - Execute
redraw_needed = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == PD_FINISHED && auto_modeTrue- Execute
const uint32_t delay = aiRestartDelay(), elapsed = now_ms - auto_restart_started; - Decision
elapsed >= delayTrue- Execute
beginGame(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Execute
const uint8_t seconds = (uint8_t)((delay - elapsed + 999U) / 1000U); - Decision
seconds != auto_secondsTrue- Execute
auto_seconds = seconds; - Execute
redraw_needed = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == PD_PLAYER_RESULT && now_ms >= action_dueTrue- Execute
beginCpuTurn(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
(state == PD_CPU_TURN || (auto_mode && state == PD_PLAYER_TURN)) && now_ms >= action_dueTrue- Execute
automatedRoll(state == PD_PLAYER_TURN, now_ms);
FalseContinue 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:
- Execute
fill_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:
- Execute
const int size = 50; - Execute
const uint16_t border = selected ? COLOR_YELLOW : held ? COLOR_GREEN : COLOR_WHITE; - Execute
fill_rect(frame, x, y, size, size, COLOR_DARKGREY); - Execute
draw_rect(frame, x, y, size, size, border); - Decision
heldTrue- Execute
draw_rect(frame, x + 2, y + 2, size - 4, size - 4, COLOR_GREEN);
FalseContinue after this decision.
- Decision
!valueTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
value == 1U || value == 3U || value == 5UTrue- Execute
drawPip(frame, x + 25, y + 25);
FalseContinue after this decision.
- Decision
value >= 2UTrue- Execute
drawPip(frame, x + 13, y + 13); - Execute
drawPip(frame, x + 37, y + 37);
FalseContinue after this decision.
- Decision
value >= 4UTrue- Execute
drawPip(frame, x + 37, y + 13); - Execute
drawPip(frame, x + 13, y + 37);
FalseContinue after this decision.
- Decision
value == 6UTrue- Execute
drawPip(frame, x + 13, y + 25); - Execute
drawPip(frame, x + 37, y + 25);
FalseContinue 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:
- Decision
!running || !redraw_neededTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
redraw_needed = false; - Execute
clear(frame, COLOR_BLACK); - Execute
char text[44]; - Decision
state == PD_SCORESTrue- Execute
draw_text_centred(frame, 160, 8, "POKER DICE", 3, COLOR_CYAN); - Execute
draw_text_centred(frame, 160, 43, "GAME RECORD", 2, COLOR_LIGHTGRAY); - Execute
snprintf(text, sizeof(text), "WINS %u LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses); - Execute
draw_text_centred(frame, 160, 73, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "DRAWS %u STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak); - Execute
draw_text_centred(frame, 160, 103, text, 2, COLOR_GOLD); - Execute
draw_text_centred(frame, 160, 140, "FIVE DICE - THREE ROLLS", 1, COLOR_LIGHTGRAY); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == PD_MODETrue- Execute
draw_text_centred(frame, 160, 8, "POKER DICE", 3, COLOR_CYAN); - Execute
drawGameGold(frame, 250, 2, 65, true); - Execute
draw_text_centred(frame, 160, 44, "SELECT MODE", 2, COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 83, 81, "PLAY", 2, selected_mode == 0U ? COLOR_YELLOW : COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 237, 81, "AUTO", 2, selected_mode ? COLOR_YELLOW : COLOR_LIGHTGRAY); - Execute
draw_text_centred(frame, 160, 140, "D/B/STICK CHANGE A/K START", 1, COLOR_LIGHTGRAY); - Exit this function
return;
FalseContinue after this decision.
- Execute
const bool player_view = state == PD_PLAYER_TURN || state == PD_PLAYER_RESULT; - Execute
const uint8_t *dice = player_view ? player_dice : cpu_dice; - Execute
const bool *held = player_view ? player_held : cpu_held; - Execute
const uint8_t rolls = player_view ? player_rolls : cpu_rolls; - Execute
draw_text(frame, 6, 3, "POKER DICE", 2, COLOR_CYAN); - Execute
drawGameGold(frame, 250, 2, 65, true); - Execute
draw_text(frame, 5, 28, auto_mode ? (player_view ? "A" : "B") : (player_view ? "YOU" : "CPU"), 1, COLOR_CYAN); - Execute
snprintf(text, sizeof(text), "ROLL %u/3", (unsigned)rolls); - Execute
draw_text(frame, 260, 28, text, 1, COLOR_CYAN); - FOR loop
uint8_t i = 0U; i < DICE_COUNT; ++iRepeat body while the loop condition permits; then continue below.
- Execute
drawDie(frame, dice[i], 7 + (int)i * 62, 43, !auto_mode && state == PD_PLAYER_TURN && selected_control == i, held[i]);
- Execute
draw_text_centred(frame, 160, 101, rolls ? handName(evaluateHand(dice).rank) : "ROLL THE DICE", 2, COLOR_CYAN); - Execute
draw_text_centred(frame, 160, 122, message, 1, COLOR_WHITE); - Decision
!auto_mode && state == PD_PLAYER_TURNTrue- Execute
const bool selected = selected_control == ROLL_CONTROL; - Execute
fill_rect(frame, 101, 140, 118, 22, selected ? COLOR_YELLOW : COLOR_BLACK); - Execute
draw_rect(frame, 101, 140, 118, 22, COLOR_YELLOW); - Execute
draw_text_centred(frame, 160, 145, "ROLL", 2, selected ? COLOR_BLACK : COLOR_WHITE);
False- Decision
auto_modeTrue- Decision
state == PD_FINISHEDTrue- Execute
snprintf(text, sizeof(text), "NEXT %u E SPEED A/K STOP", (unsigned)auto_seconds);
False- Execute
snprintf(text, sizeof(text), "AUTO %s E SPEED A/K STOP", aiSpeedShortText(ai_speed));
- Execute
draw_text_centred(frame, 160, 151, text, 1, COLOR_LIGHTGRAY);
False- Decision
state == PD_FINISHEDTrue- Execute
draw_text_centred(frame, 160, 151, "A/K NEW GAME", 1, COLOR_LIGHTGRAY);
False- Execute
draw_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);
}
}