Simon
Source: src/simon.c · 300 lines · 13 functions.
Screen captures
Views of this part running on the Mini Arcade.

Screen / module overview
- Enter Simon
Seed randomness and enter WAITING. Any A/B/C/D press starts a one-pad sequence.
- Show the sequence
Wait 700 ms, light each pad with its tone for 360 ms and leave a 180 ms gap between pads.
- Wait for player input
Compare each pressed pad against the next sequence entry.
- Correct pad
Flash it for 180 ms. If more input is needed, resume the player turn. A complete round appends a pad and replays the sequence, up to 32 pads.
- Wrong pad
Play the failure tone and request high-score entry when completed rounds are nonzero. Show failure for 1.3 seconds, then return to WAITING.
- Draw current state
Render A top, B right, C bottom and D left, highlighting the active pad and showing WATCH, YOUR TURN or WRONG.
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
triangleEdge() · source line 62
static int64_t triangleEdge(int ax, int ay, int bx, int by, int px, int py)
- Exit this function
return (int64_t)(px - ax) * (by - ay) - (int64_t)(py - ay) * (bx - ax);
Read original function source
static int64_t triangleEdge(int ax, int ay, int bx, int by, int px, int py)
{
return (int64_t)(px - ax) * (by - ay) - (int64_t)(py - ay) * (bx - ax);
}fillSimonTriangle() · source line 67
static void fillSimonTriangle(uint16_t *frame, int ax, int ay, int bx, int by,
int cx, int cy, uint16_t color)Calls into this documentation:
- Execute
int min_x = ax; - Execute
int max_x = ax; - Execute
int min_y = ay; - Execute
int max_y = ay; - Decision
bx < min_xTrue- Execute
min_x = bx;
FalseContinue after this decision.
- Decision
cx < min_xTrue- Execute
min_x = cx;
FalseContinue after this decision.
- Decision
bx > max_xTrue- Execute
max_x = bx;
FalseContinue after this decision.
- Decision
cx > max_xTrue- Execute
max_x = cx;
FalseContinue after this decision.
- Decision
by < min_yTrue- Execute
min_y = by;
FalseContinue after this decision.
- Decision
cy < min_yTrue- Execute
min_y = cy;
FalseContinue after this decision.
- Decision
by > max_yTrue- Execute
max_y = by;
FalseContinue after this decision.
- Decision
cy > max_yTrue- Execute
max_y = cy;
FalseContinue after this decision.
- Decision
min_x < 0True- Execute
min_x = 0;
FalseContinue after this decision.
- Decision
min_y < 0True- Execute
min_y = 0;
FalseContinue after this decision.
- Decision
max_x >= LCD_WIDTHTrue- Execute
max_x = LCD_WIDTH - 1;
FalseContinue after this decision.
- Decision
max_y >= LCD_HEIGHTTrue- Execute
max_y = LCD_HEIGHT - 1;
FalseContinue after this decision.
- Execute
const int64_t orientation = triangleEdge(ax, ay, bx, by, cx, cy); - Decision
orientation == 0True- Exit this function
return;
FalseContinue after this decision.
- FOR loop
int y = min_y; y <= max_y; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
int x = min_x; x <= max_x; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const int64_t first = triangleEdge(ax, ay, bx, by, x, y); - Execute
const int64_t second = triangleEdge(bx, by, cx, cy, x, y); - Execute
const int64_t third = triangleEdge(cx, cy, ax, ay, x, y); - Decision
(orientation > 0 && first >= 0 && second >= 0 && third >= 0) || (orientation < 0 && first <= 0 && second <= 0 && third <= 0)True- Execute
frame[(size_t)y * LCD_WIDTH + x] = color;
FalseContinue after this decision.
Read original function source
static void fillSimonTriangle(uint16_t *frame, int ax, int ay, int bx, int by,
int cx, int cy, uint16_t color)
{
int min_x = ax;
int max_x = ax;
int min_y = ay;
int max_y = ay;
if (bx < min_x) min_x = bx;
if (cx < min_x) min_x = cx;
if (bx > max_x) max_x = bx;
if (cx > max_x) max_x = cx;
if (by < min_y) min_y = by;
if (cy < min_y) min_y = cy;
if (by > max_y) max_y = by;
if (cy > max_y) max_y = cy;
if (min_x < 0) min_x = 0;
if (min_y < 0) min_y = 0;
if (max_x >= LCD_WIDTH) max_x = LCD_WIDTH - 1;
if (max_y >= LCD_HEIGHT) max_y = LCD_HEIGHT - 1;
const int64_t orientation = triangleEdge(ax, ay, bx, by, cx, cy);
if (orientation == 0) return;
for (int y = min_y; y <= max_y; ++y) {
for (int x = min_x; x <= max_x; ++x) {
const int64_t first = triangleEdge(ax, ay, bx, by, x, y);
const int64_t second = triangleEdge(bx, by, cx, cy, x, y);
const int64_t third = triangleEdge(cx, cy, ax, ay, x, y);
if ((orientation > 0 && first >= 0 && second >= 0 && third >= 0) ||
(orientation < 0 && first <= 0 && second <= 0 && third <= 0)) {
frame[(size_t)y * LCD_WIDTH + x] = color;
}
}
}
}fillSimonPad() · source line 102
static void fillSimonPad(uint16_t *frame, simon_pad_t pad, uint16_t color)
Calls into this documentation:
- Select a case
padEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case SIMON_PAD_A: - Execute
fillSimonTriangle(frame, 0, 0, LCD_WIDTH - 1, 0, SIMON_CENTRE_X, SIMON_CENTRE_Y, color); - Exit enclosing loop or switch
break; - Case / label
case SIMON_PAD_B: - Execute
fillSimonTriangle(frame, LCD_WIDTH - 1, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1, SIMON_CENTRE_X, SIMON_CENTRE_Y, color); - Exit enclosing loop or switch
break; - Case / label
case SIMON_PAD_C: - Execute
fillSimonTriangle(frame, LCD_WIDTH - 1, LCD_HEIGHT - 1, 0, LCD_HEIGHT - 1, SIMON_CENTRE_X, SIMON_CENTRE_Y, color); - Exit enclosing loop or switch
break; - Case / label
case SIMON_PAD_D: - Execute
fillSimonTriangle(frame, 0, LCD_HEIGHT - 1, 0, 0, SIMON_CENTRE_X, SIMON_CENTRE_Y, color); - Exit enclosing loop or switch
break; - Case / label
default: - Exit enclosing loop or switch
break;
Read original function source
static void fillSimonPad(uint16_t *frame, simon_pad_t pad, uint16_t color)
{
switch (pad) {
case SIMON_PAD_A:
fillSimonTriangle(frame, 0, 0, LCD_WIDTH - 1, 0,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
case SIMON_PAD_B:
fillSimonTriangle(frame, LCD_WIDTH - 1, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
case SIMON_PAD_C:
fillSimonTriangle(frame, LCD_WIDTH - 1, LCD_HEIGHT - 1, 0, LCD_HEIGHT - 1,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
case SIMON_PAD_D:
fillSimonTriangle(frame, 0, LCD_HEIGHT - 1, 0, 0,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
default:
break;
}
}nextRandom() · source line 126
static uint32_t nextRandom(void)
- Execute
random_state ^= random_state << 13U; - Execute
random_state ^= random_state >> 17U; - Execute
random_state ^= random_state << 5U; - Exit this function
return random_state;
Read original function source
static uint32_t nextRandom(void)
{
random_state ^= random_state << 13U;
random_state ^= random_state >> 17U;
random_state ^= random_state << 5U;
return random_state;
}stopPadTone() · source line 134
static void stopPadTone(void)
Calls into this documentation:
- Execute
audioStopTestTone(); - Execute
lit_pad = -1;
Read original function source
static void stopPadTone(void)
{
audioStopTestTone();
lit_pad = -1;
}lightPad() · source line 140
static void lightPad(simon_pad_t pad)
Calls into this documentation:
- Execute
lit_pad = (int8_t)pad; - Execute
audioStartTestTone(pad_tone[pad]);
Read original function source
static void lightPad(simon_pad_t pad)
{
lit_pad = (int8_t)pad;
audioStartTestTone(pad_tone[pad]);
}beginSequence() · source line 146
static void beginSequence(uint32_t now_ms)
- Execute
show_index = 0U; - Execute
state = SIMON_SHOWING_DELAY; - Execute
state_changed_at = now_ms;
Read original function source
static void beginSequence(uint32_t now_ms)
{
show_index = 0U;
state = SIMON_SHOWING_DELAY;
state_changed_at = now_ms;
}addSequencePad() · source line 153
static void addSequencePad(void)
Calls into this documentation:
- Decision
sequence_length < SIMON_MAX_STEPSTrue- Execute
sequence[sequence_length++] = (uint8_t)(nextRandom() % SIMON_PAD_COUNT);
FalseContinue after this decision.
Read original function source
static void addSequencePad(void)
{
if (sequence_length < SIMON_MAX_STEPS) {
sequence[sequence_length++] = (uint8_t)(nextRandom() % SIMON_PAD_COUNT);
}
}startSimon() · source line 160
void startSimon(uint32_t now_ms)
- Execute
random_state = esp_random(); - Decision
random_state == 0UTrue- Execute
random_state = 0x51A0BEEFU;
FalseContinue after this decision.
- Execute
sequence_length = 0U; - Execute
show_index = 0U; - Execute
player_index = 0U; - Execute
completed_rounds = 0U; - Execute
lit_pad = -1; - Execute
state = SIMON_WAITING; - Execute
state_changed_at = now_ms; - Execute
running = true;
Read original function source
void startSimon(uint32_t now_ms)
{
random_state = esp_random();
if (random_state == 0U) random_state = 0x51A0BEEFU;
sequence_length = 0U;
show_index = 0U;
player_index = 0U;
completed_rounds = 0U;
lit_pad = -1;
state = SIMON_WAITING;
state_changed_at = now_ms;
running = true;
}stopSimon() · source line 174
void stopSimon(void)
Calls into this documentation:
- Execute
stopPadTone(); - Execute
running = false;
Read original function source
void stopSimon(void)
{
stopPadTone();
running = false;
}simonAction() · source line 180
void simonAction(bool a_pressed, bool b_pressed, bool c_pressed, bool d_pressed,
uint32_t now_ms)Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == SIMON_WAITINGTrue- Decision
a_pressed || b_pressed || c_pressed || d_pressedTrue- Execute
sequence_length = 0U; - Execute
addSequencePad(); - Execute
beginSequence(now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state != SIMON_PLAYER_TURNTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
simon_pad_t pressed; - Decision
a_pressedTrue- Execute
pressed = SIMON_PAD_A;
False- Decision
b_pressedTrue- Execute
pressed = SIMON_PAD_B;
False- Decision
c_pressedTrue- Execute
pressed = SIMON_PAD_C;
False- Decision
d_pressedTrue- Execute
pressed = SIMON_PAD_D;
False- Exit this function
return;
- Decision
pressed != (simon_pad_t)sequence[player_index]True- Execute
lit_pad = -1; - Execute
audioStartTestTone(160U); - Decision
completed_rounds != 0UTrue- Execute
startHighScoreEntry(HIGH_SCORE_SIMON, completed_rounds, now_ms);
FalseContinue after this decision.
- Execute
state = SIMON_FAILED; - Execute
state_changed_at = now_ms; - Exit this function
return;
FalseContinue after this decision.
- Execute
lightPad(pressed); - Execute
state = SIMON_PLAYER_FLASH; - Execute
state_changed_at = now_ms; - Execute
++player_index;
Read original function source
void simonAction(bool a_pressed, bool b_pressed, bool c_pressed, bool d_pressed,
uint32_t now_ms)
{
if (!running) return;
if (state == SIMON_WAITING) {
if (a_pressed || b_pressed || c_pressed || d_pressed) {
sequence_length = 0U;
addSequencePad();
beginSequence(now_ms);
}
return;
}
if (state != SIMON_PLAYER_TURN) return;
simon_pad_t pressed;
if (a_pressed) pressed = SIMON_PAD_A;
else if (b_pressed) pressed = SIMON_PAD_B;
else if (c_pressed) pressed = SIMON_PAD_C;
else if (d_pressed) pressed = SIMON_PAD_D;
else return;
if (pressed != (simon_pad_t)sequence[player_index]) {
lit_pad = -1;
audioStartTestTone(160U);
if (completed_rounds != 0U) {
startHighScoreEntry(HIGH_SCORE_SIMON, completed_rounds, now_ms);
}
state = SIMON_FAILED;
state_changed_at = now_ms;
return;
}
lightPad(pressed);
state = SIMON_PLAYER_FLASH;
state_changed_at = now_ms;
++player_index;
}updateSimon() · source line 218
void updateSimon(uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed = now_ms - state_changed_at; - Decision
state == SIMON_SHOWING_DELAY && elapsed >= SIMON_START_DELAY_MSTrue- Execute
lightPad((simon_pad_t)sequence[show_index]); - Execute
state = SIMON_SHOWING_PAD; - Execute
state_changed_at = now_ms;
False- Decision
state == SIMON_SHOWING_PAD && elapsed >= SIMON_NOTE_MSTrue- Execute
stopPadTone(); - Execute
state = SIMON_SHOWING_GAP; - Execute
state_changed_at = now_ms;
False- Decision
state == SIMON_SHOWING_GAP && elapsed >= SIMON_GAP_MSTrue- Execute
++show_index; - Decision
show_index >= sequence_lengthTrue- Execute
player_index = 0U; - Execute
state = SIMON_PLAYER_TURN;
False- Execute
lightPad((simon_pad_t)sequence[show_index]); - Execute
state = SIMON_SHOWING_PAD;
- Execute
state_changed_at = now_ms;
False- Decision
state == SIMON_PLAYER_FLASH && elapsed >= SIMON_PLAYER_FLASH_MSTrue- Execute
stopPadTone(); - Decision
player_index >= sequence_lengthTrue- Execute
completed_rounds = sequence_length; - Decision
sequence_length < SIMON_MAX_STEPSTrue- Execute
addSequencePad(); - Execute
beginSequence(now_ms);
False- Execute
state = SIMON_WAITING; - Execute
state_changed_at = now_ms;
False- Execute
state = SIMON_PLAYER_TURN; - Execute
state_changed_at = now_ms;
False- Decision
state == SIMON_FAILED && elapsed >= SIMON_FAILURE_MSTrue- Execute
audioStopTestTone(); - Execute
state = SIMON_WAITING; - Execute
state_changed_at = now_ms;
FalseContinue after this decision.
Read original function source
void updateSimon(uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - state_changed_at;
if (state == SIMON_SHOWING_DELAY && elapsed >= SIMON_START_DELAY_MS) {
lightPad((simon_pad_t)sequence[show_index]);
state = SIMON_SHOWING_PAD;
state_changed_at = now_ms;
} else if (state == SIMON_SHOWING_PAD && elapsed >= SIMON_NOTE_MS) {
stopPadTone();
state = SIMON_SHOWING_GAP;
state_changed_at = now_ms;
} else if (state == SIMON_SHOWING_GAP && elapsed >= SIMON_GAP_MS) {
++show_index;
if (show_index >= sequence_length) {
player_index = 0U;
state = SIMON_PLAYER_TURN;
} else {
lightPad((simon_pad_t)sequence[show_index]);
state = SIMON_SHOWING_PAD;
}
state_changed_at = now_ms;
} else if (state == SIMON_PLAYER_FLASH && elapsed >= SIMON_PLAYER_FLASH_MS) {
stopPadTone();
if (player_index >= sequence_length) {
completed_rounds = sequence_length;
if (sequence_length < SIMON_MAX_STEPS) {
addSequencePad();
beginSequence(now_ms);
} else {
state = SIMON_WAITING;
state_changed_at = now_ms;
}
} else {
state = SIMON_PLAYER_TURN;
state_changed_at = now_ms;
}
} else if (state == SIMON_FAILED && elapsed >= SIMON_FAILURE_MS) {
audioStopTestTone();
state = SIMON_WAITING;
state_changed_at = now_ms;
}
}drawSimon() · source line 262
void drawSimon(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - FOR loop
unsigned pad = 0U; pad < SIMON_PAD_COUNT; ++padRepeat body while the loop condition permits; then continue below.
- Execute
const bool highlighted = lit_pad == (int8_t)pad; - Execute
const uint16_t color = highlighted ? pad_lit[pad] : pad_dim[pad]; - Execute
fillSimonPad(frame, (simon_pad_t)pad, color);
- Execute
fill_circle(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS, COLOR_BLACK); - Execute
draw_ring(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS, COLOR_WHITE); - FOR loop
unsigned pad = 0U; pad < SIMON_PAD_COUNT; ++padRepeat body while the loop condition permits; then continue below.
- Execute
const bool highlighted = lit_pad == (int8_t)pad; - Execute
draw_text_centred(frame, pad_label_x[pad], pad_label_y[pad], pad_label[pad], 3, highlighted ? COLOR_BLACK : COLOR_WHITE);
- Execute
draw_text_centred(frame, SIMON_CENTRE_X, 46, "SIMON", 1, COLOR_WHITE); - Decision
state == SIMON_WAITINGTrue- Execute
draw_text_centred(frame, SIMON_CENTRE_X, 80, "A B C D START", 1, COLOR_WHITE);
False- Decision
state == SIMON_FAILEDTrue- Execute
char score[24]; - Execute
snprintf(score, sizeof(score), "OUT AT %u", (unsigned)sequence_length); - Execute
draw_text_centred(frame, SIMON_CENTRE_X, 72, "WRONG!", 2, COLOR_RED); - Execute
draw_text_centred(frame, SIMON_CENTRE_X, 96, score, 1, COLOR_WHITE);
False- Decision
state == SIMON_PLAYER_TURN || state == SIMON_PLAYER_FLASHTrue- Execute
char round[24]; - Execute
snprintf(round, sizeof(round), "ROUND %u", (unsigned)sequence_length); - Execute
draw_text_centred(frame, SIMON_CENTRE_X, 72, "YOUR TURN", 1, COLOR_WHITE); - Execute
draw_text_centred(frame, SIMON_CENTRE_X, 88, round, 1, COLOR_LIGHTGRAY);
False- Execute
draw_text_centred(frame, SIMON_CENTRE_X, 80, "WATCH", 1, COLOR_WHITE);
Read original function source
void drawSimon(uint16_t *frame)
{
if (!running || frame == NULL) return;
clear(frame, COLOR_BLACK);
for (unsigned pad = 0U; pad < SIMON_PAD_COUNT; ++pad) {
const bool highlighted = lit_pad == (int8_t)pad;
const uint16_t color = highlighted ? pad_lit[pad] : pad_dim[pad];
fillSimonPad(frame, (simon_pad_t)pad, color);
}
fill_circle(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS,
COLOR_BLACK);
draw_ring(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS,
COLOR_WHITE);
for (unsigned pad = 0U; pad < SIMON_PAD_COUNT; ++pad) {
const bool highlighted = lit_pad == (int8_t)pad;
draw_text_centred(frame, pad_label_x[pad], pad_label_y[pad], pad_label[pad], 3,
highlighted ? COLOR_BLACK : COLOR_WHITE);
}
draw_text_centred(frame, SIMON_CENTRE_X, 46, "SIMON", 1, COLOR_WHITE);
if (state == SIMON_WAITING) {
draw_text_centred(frame, SIMON_CENTRE_X, 80, "A B C D START", 1, COLOR_WHITE);
} else if (state == SIMON_FAILED) {
char score[24];
snprintf(score, sizeof(score), "OUT AT %u", (unsigned)sequence_length);
draw_text_centred(frame, SIMON_CENTRE_X, 72, "WRONG!", 2, COLOR_RED);
draw_text_centred(frame, SIMON_CENTRE_X, 96, score, 1, COLOR_WHITE);
} else if (state == SIMON_PLAYER_TURN || state == SIMON_PLAYER_FLASH) {
char round[24];
snprintf(round, sizeof(round), "ROUND %u", (unsigned)sequence_length);
draw_text_centred(frame, SIMON_CENTRE_X, 72, "YOUR TURN", 1, COLOR_WHITE);
draw_text_centred(frame, SIMON_CENTRE_X, 88, round, 1, COLOR_LIGHTGRAY);
} else {
draw_text_centred(frame, SIMON_CENTRE_X, 80, "WATCH", 1, COLOR_WHITE);
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "simon.h"
#include "audio.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "high_scores.h"
#include "esp_random.h"
#include <stdbool.h>
#include <stdio.h>
#define SIMON_MAX_STEPS 32U
#define SIMON_START_DELAY_MS 700U
#define SIMON_NOTE_MS 360U
#define SIMON_GAP_MS 180U
#define SIMON_PLAYER_FLASH_MS 180U
#define SIMON_FAILURE_MS 1300U
#define SIMON_CENTRE_X (LCD_WIDTH / 2)
#define SIMON_CENTRE_Y (LCD_HEIGHT / 2)
#define SIMON_CENTRE_RADIUS 53
typedef enum {
SIMON_PAD_A,
SIMON_PAD_B,
SIMON_PAD_C,
SIMON_PAD_D,
SIMON_PAD_COUNT,
} simon_pad_t;
typedef enum {
SIMON_WAITING,
SIMON_SHOWING_DELAY,
SIMON_SHOWING_PAD,
SIMON_SHOWING_GAP,
SIMON_PLAYER_TURN,
SIMON_PLAYER_FLASH,
SIMON_FAILED,
} simon_state_t;
static const int pad_label_x[SIMON_PAD_COUNT] = {160, 276, 160, 44};
static const int pad_label_y[SIMON_PAD_COUNT] = {6, 73, 141, 73};
static const uint16_t pad_dim[SIMON_PAD_COUNT] = {
COLOR_OLIVE, COLOR_MAROON, COLOR_DARKBLUE, COLOR_DARKGREEN,
};
static const uint16_t pad_lit[SIMON_PAD_COUNT] = {
COLOR_YELLOW, COLOR_RED, COLOR_BLUE, COLOR_LIME,
};
static const uint16_t pad_tone[SIMON_PAD_COUNT] = {523U, 659U, 784U, 392U};
static const char pad_label[SIMON_PAD_COUNT][2] = {"A", "B", "C", "D"};
static uint8_t sequence[SIMON_MAX_STEPS];
static uint32_t random_state;
static uint32_t state_changed_at;
static uint8_t sequence_length;
static uint8_t show_index;
static uint8_t player_index;
static uint8_t completed_rounds;
static int8_t lit_pad;
static simon_state_t state;
static bool running;
static int64_t triangleEdge(int ax, int ay, int bx, int by, int px, int py)
{
return (int64_t)(px - ax) * (by - ay) - (int64_t)(py - ay) * (bx - ax);
}
static void fillSimonTriangle(uint16_t *frame, int ax, int ay, int bx, int by,
int cx, int cy, uint16_t color)
{
int min_x = ax;
int max_x = ax;
int min_y = ay;
int max_y = ay;
if (bx < min_x) min_x = bx;
if (cx < min_x) min_x = cx;
if (bx > max_x) max_x = bx;
if (cx > max_x) max_x = cx;
if (by < min_y) min_y = by;
if (cy < min_y) min_y = cy;
if (by > max_y) max_y = by;
if (cy > max_y) max_y = cy;
if (min_x < 0) min_x = 0;
if (min_y < 0) min_y = 0;
if (max_x >= LCD_WIDTH) max_x = LCD_WIDTH - 1;
if (max_y >= LCD_HEIGHT) max_y = LCD_HEIGHT - 1;
const int64_t orientation = triangleEdge(ax, ay, bx, by, cx, cy);
if (orientation == 0) return;
for (int y = min_y; y <= max_y; ++y) {
for (int x = min_x; x <= max_x; ++x) {
const int64_t first = triangleEdge(ax, ay, bx, by, x, y);
const int64_t second = triangleEdge(bx, by, cx, cy, x, y);
const int64_t third = triangleEdge(cx, cy, ax, ay, x, y);
if ((orientation > 0 && first >= 0 && second >= 0 && third >= 0) ||
(orientation < 0 && first <= 0 && second <= 0 && third <= 0)) {
frame[(size_t)y * LCD_WIDTH + x] = color;
}
}
}
}
static void fillSimonPad(uint16_t *frame, simon_pad_t pad, uint16_t color)
{
switch (pad) {
case SIMON_PAD_A:
fillSimonTriangle(frame, 0, 0, LCD_WIDTH - 1, 0,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
case SIMON_PAD_B:
fillSimonTriangle(frame, LCD_WIDTH - 1, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
case SIMON_PAD_C:
fillSimonTriangle(frame, LCD_WIDTH - 1, LCD_HEIGHT - 1, 0, LCD_HEIGHT - 1,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
case SIMON_PAD_D:
fillSimonTriangle(frame, 0, LCD_HEIGHT - 1, 0, 0,
SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
break;
default:
break;
}
}
static uint32_t nextRandom(void)
{
random_state ^= random_state << 13U;
random_state ^= random_state >> 17U;
random_state ^= random_state << 5U;
return random_state;
}
static void stopPadTone(void)
{
audioStopTestTone();
lit_pad = -1;
}
static void lightPad(simon_pad_t pad)
{
lit_pad = (int8_t)pad;
audioStartTestTone(pad_tone[pad]);
}
static void beginSequence(uint32_t now_ms)
{
show_index = 0U;
state = SIMON_SHOWING_DELAY;
state_changed_at = now_ms;
}
static void addSequencePad(void)
{
if (sequence_length < SIMON_MAX_STEPS) {
sequence[sequence_length++] = (uint8_t)(nextRandom() % SIMON_PAD_COUNT);
}
}
void startSimon(uint32_t now_ms)
{
random_state = esp_random();
if (random_state == 0U) random_state = 0x51A0BEEFU;
sequence_length = 0U;
show_index = 0U;
player_index = 0U;
completed_rounds = 0U;
lit_pad = -1;
state = SIMON_WAITING;
state_changed_at = now_ms;
running = true;
}
void stopSimon(void)
{
stopPadTone();
running = false;
}
void simonAction(bool a_pressed, bool b_pressed, bool c_pressed, bool d_pressed,
uint32_t now_ms)
{
if (!running) return;
if (state == SIMON_WAITING) {
if (a_pressed || b_pressed || c_pressed || d_pressed) {
sequence_length = 0U;
addSequencePad();
beginSequence(now_ms);
}
return;
}
if (state != SIMON_PLAYER_TURN) return;
simon_pad_t pressed;
if (a_pressed) pressed = SIMON_PAD_A;
else if (b_pressed) pressed = SIMON_PAD_B;
else if (c_pressed) pressed = SIMON_PAD_C;
else if (d_pressed) pressed = SIMON_PAD_D;
else return;
if (pressed != (simon_pad_t)sequence[player_index]) {
lit_pad = -1;
audioStartTestTone(160U);
if (completed_rounds != 0U) {
startHighScoreEntry(HIGH_SCORE_SIMON, completed_rounds, now_ms);
}
state = SIMON_FAILED;
state_changed_at = now_ms;
return;
}
lightPad(pressed);
state = SIMON_PLAYER_FLASH;
state_changed_at = now_ms;
++player_index;
}
void updateSimon(uint32_t now_ms)
{
if (!running) return;
const uint32_t elapsed = now_ms - state_changed_at;
if (state == SIMON_SHOWING_DELAY && elapsed >= SIMON_START_DELAY_MS) {
lightPad((simon_pad_t)sequence[show_index]);
state = SIMON_SHOWING_PAD;
state_changed_at = now_ms;
} else if (state == SIMON_SHOWING_PAD && elapsed >= SIMON_NOTE_MS) {
stopPadTone();
state = SIMON_SHOWING_GAP;
state_changed_at = now_ms;
} else if (state == SIMON_SHOWING_GAP && elapsed >= SIMON_GAP_MS) {
++show_index;
if (show_index >= sequence_length) {
player_index = 0U;
state = SIMON_PLAYER_TURN;
} else {
lightPad((simon_pad_t)sequence[show_index]);
state = SIMON_SHOWING_PAD;
}
state_changed_at = now_ms;
} else if (state == SIMON_PLAYER_FLASH && elapsed >= SIMON_PLAYER_FLASH_MS) {
stopPadTone();
if (player_index >= sequence_length) {
completed_rounds = sequence_length;
if (sequence_length < SIMON_MAX_STEPS) {
addSequencePad();
beginSequence(now_ms);
} else {
state = SIMON_WAITING;
state_changed_at = now_ms;
}
} else {
state = SIMON_PLAYER_TURN;
state_changed_at = now_ms;
}
} else if (state == SIMON_FAILED && elapsed >= SIMON_FAILURE_MS) {
audioStopTestTone();
state = SIMON_WAITING;
state_changed_at = now_ms;
}
}
void drawSimon(uint16_t *frame)
{
if (!running || frame == NULL) return;
clear(frame, COLOR_BLACK);
for (unsigned pad = 0U; pad < SIMON_PAD_COUNT; ++pad) {
const bool highlighted = lit_pad == (int8_t)pad;
const uint16_t color = highlighted ? pad_lit[pad] : pad_dim[pad];
fillSimonPad(frame, (simon_pad_t)pad, color);
}
fill_circle(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS,
COLOR_BLACK);
draw_ring(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS,
COLOR_WHITE);
for (unsigned pad = 0U; pad < SIMON_PAD_COUNT; ++pad) {
const bool highlighted = lit_pad == (int8_t)pad;
drawPet2001TextCentredTransparent(frame, pad_label_x[pad], pad_label_y[pad], pad_label[pad],
highlighted ? COLOR_BLACK : COLOR_WHITE, 3);
}
drawPet2001TextCentredTransparent(frame, SIMON_CENTRE_X, 46, "SIMON", COLOR_WHITE, 1);
if (state == SIMON_WAITING) {
drawPet2001TextCentredTransparent(frame, SIMON_CENTRE_X, 80, "A B C D START", COLOR_WHITE, 1);
} else if (state == SIMON_FAILED) {
char score[24];
snprintf(score, sizeof(score), "OUT AT %u", (unsigned)sequence_length);
drawPet2001TextCentredTransparent(frame, SIMON_CENTRE_X, 72, "WRONG!", COLOR_RED, 2);
drawPet2001TextCentredTransparent(frame, SIMON_CENTRE_X, 96, score, COLOR_WHITE, 1);
} else if (state == SIMON_PLAYER_TURN || state == SIMON_PLAYER_FLASH) {
char round[24];
snprintf(round, sizeof(round), "ROUND %u", (unsigned)sequence_length);
drawPet2001TextCentredTransparent(frame, SIMON_CENTRE_X, 72, "YOUR TURN", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, SIMON_CENTRE_X, 88, round, COLOR_LIGHTGRAY, 1);
} else {
drawPet2001TextCentredTransparent(frame, SIMON_CENTRE_X, 80, "WATCH", COLOR_WHITE, 1);
}
}