Simon

Source: src/simon.c · 300 lines · 13 functions.

Screen captures

Views of this part running on the Mini Arcade.

Simon game screen on the Mini Arcade
Simon showing its four coloured input areas.

Screen / module overview

  1. Enter Simon

    Seed randomness and enter WAITING. Any A/B/C/D press starts a one-pad sequence.

  2. Show the sequence

    Wait 700 ms, light each pad with its tone for 360 ms and leave a 180 ms gap between pads.

  3. Wait for player input

    Compare each pressed pad against the next sequence entry.

  4. 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.

  5. 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.

  6. 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)
  1. Exit this functionreturn (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:

  1. Executeint min_x = ax;
  2. Executeint max_x = ax;
  3. Executeint min_y = ay;
  4. Executeint max_y = ay;
  5. Decisionbx < min_x
    True
    1. Executemin_x = bx;
    False

    Continue after this decision.

  6. Decisioncx < min_x
    True
    1. Executemin_x = cx;
    False

    Continue after this decision.

  7. Decisionbx > max_x
    True
    1. Executemax_x = bx;
    False

    Continue after this decision.

  8. Decisioncx > max_x
    True
    1. Executemax_x = cx;
    False

    Continue after this decision.

  9. Decisionby < min_y
    True
    1. Executemin_y = by;
    False

    Continue after this decision.

  10. Decisioncy < min_y
    True
    1. Executemin_y = cy;
    False

    Continue after this decision.

  11. Decisionby > max_y
    True
    1. Executemax_y = by;
    False

    Continue after this decision.

  12. Decisioncy > max_y
    True
    1. Executemax_y = cy;
    False

    Continue after this decision.

  13. Decisionmin_x < 0
    True
    1. Executemin_x = 0;
    False

    Continue after this decision.

  14. Decisionmin_y < 0
    True
    1. Executemin_y = 0;
    False

    Continue after this decision.

  15. Decisionmax_x >= LCD_WIDTH
    True
    1. Executemax_x = LCD_WIDTH - 1;
    False

    Continue after this decision.

  16. Decisionmax_y >= LCD_HEIGHT
    True
    1. Executemax_y = LCD_HEIGHT - 1;
    False

    Continue after this decision.

  17. Executeconst int64_t orientation = triangleEdge(ax, ay, bx, by, cx, cy);
  18. Decisionorientation == 0
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  19. FOR loopint y = min_y; y <= max_y; ++y

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

      1. FOR loopint x = min_x; x <= max_x; ++x

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

          1. Executeconst int64_t first = triangleEdge(ax, ay, bx, by, x, y);
          2. Executeconst int64_t second = triangleEdge(bx, by, cx, cy, x, y);
          3. Executeconst int64_t third = triangleEdge(cx, cy, ax, ay, x, y);
          4. Decision(orientation > 0 && first >= 0 && second >= 0 && third >= 0) || (orientation < 0 && first <= 0 && second <= 0 && third <= 0)
            True
              1. Executeframe[(size_t)y * LCD_WIDTH + x] = color;
            False

            Continue 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:

  1. Select a casepad

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase SIMON_PAD_A:
      2. ExecutefillSimonTriangle(frame, 0, 0, LCD_WIDTH - 1, 0, SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
      3. Exit enclosing loop or switchbreak;
      4. Case / labelcase SIMON_PAD_B:
      5. ExecutefillSimonTriangle(frame, LCD_WIDTH - 1, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1, SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
      6. Exit enclosing loop or switchbreak;
      7. Case / labelcase SIMON_PAD_C:
      8. ExecutefillSimonTriangle(frame, LCD_WIDTH - 1, LCD_HEIGHT - 1, 0, LCD_HEIGHT - 1, SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
      9. Exit enclosing loop or switchbreak;
      10. Case / labelcase SIMON_PAD_D:
      11. ExecutefillSimonTriangle(frame, 0, LCD_HEIGHT - 1, 0, 0, SIMON_CENTRE_X, SIMON_CENTRE_Y, color);
      12. Exit enclosing loop or switchbreak;
      13. Case / labeldefault:
      14. Exit enclosing loop or switchbreak;
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)
  1. Executerandom_state ^= random_state << 13U;
  2. Executerandom_state ^= random_state >> 17U;
  3. Executerandom_state ^= random_state << 5U;
  4. Exit this functionreturn 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:

  1. ExecuteaudioStopTestTone();
  2. Executelit_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:

  1. Executelit_pad = (int8_t)pad;
  2. ExecuteaudioStartTestTone(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)
  1. Executeshow_index = 0U;
  2. Executestate = SIMON_SHOWING_DELAY;
  3. Executestate_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:

  1. Decisionsequence_length < SIMON_MAX_STEPS
    True
      1. Executesequence[sequence_length++] = (uint8_t)(nextRandom() % SIMON_PAD_COUNT);
    False

    Continue 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)
  1. Executerandom_state = esp_random();
  2. Decisionrandom_state == 0U
    True
    1. Executerandom_state = 0x51A0BEEFU;
    False

    Continue after this decision.

  3. Executesequence_length = 0U;
  4. Executeshow_index = 0U;
  5. Executeplayer_index = 0U;
  6. Executecompleted_rounds = 0U;
  7. Executelit_pad = -1;
  8. Executestate = SIMON_WAITING;
  9. Executestate_changed_at = now_ms;
  10. Executerunning = 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:

  1. ExecutestopPadTone();
  2. Executerunning = 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:

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

    Continue after this decision.

  2. Decisionstate == SIMON_WAITING
    True
      1. Decisiona_pressed || b_pressed || c_pressed || d_pressed
        True
          1. Executesequence_length = 0U;
          2. ExecuteaddSequencePad();
          3. ExecutebeginSequence(now_ms);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionstate != SIMON_PLAYER_TURN
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executesimon_pad_t pressed;
  5. Decisiona_pressed
    True
    1. Executepressed = SIMON_PAD_A;
    False
    1. Decisionb_pressed
      True
      1. Executepressed = SIMON_PAD_B;
      False
      1. Decisionc_pressed
        True
        1. Executepressed = SIMON_PAD_C;
        False
        1. Decisiond_pressed
          True
          1. Executepressed = SIMON_PAD_D;
          False
          1. Exit this functionreturn;
  6. Decisionpressed != (simon_pad_t)sequence[player_index]
    True
      1. Executelit_pad = -1;
      2. ExecuteaudioStartTestTone(160U);
      3. Decisioncompleted_rounds != 0U
        True
          1. ExecutestartHighScoreEntry(HIGH_SCORE_SIMON, completed_rounds, now_ms);
        False

        Continue after this decision.

      4. Executestate = SIMON_FAILED;
      5. Executestate_changed_at = now_ms;
      6. Exit this functionreturn;
    False

    Continue after this decision.

  7. ExecutelightPad(pressed);
  8. Executestate = SIMON_PLAYER_FLASH;
  9. Executestate_changed_at = now_ms;
  10. 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:

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

    Continue after this decision.

  2. Executeconst uint32_t elapsed = now_ms - state_changed_at;
  3. Decisionstate == SIMON_SHOWING_DELAY && elapsed >= SIMON_START_DELAY_MS
    True
      1. ExecutelightPad((simon_pad_t)sequence[show_index]);
      2. Executestate = SIMON_SHOWING_PAD;
      3. Executestate_changed_at = now_ms;
    False
    1. Decisionstate == SIMON_SHOWING_PAD && elapsed >= SIMON_NOTE_MS
      True
        1. ExecutestopPadTone();
        2. Executestate = SIMON_SHOWING_GAP;
        3. Executestate_changed_at = now_ms;
      False
      1. Decisionstate == SIMON_SHOWING_GAP && elapsed >= SIMON_GAP_MS
        True
          1. Execute++show_index;
          2. Decisionshow_index >= sequence_length
            True
              1. Executeplayer_index = 0U;
              2. Executestate = SIMON_PLAYER_TURN;
            False
              1. ExecutelightPad((simon_pad_t)sequence[show_index]);
              2. Executestate = SIMON_SHOWING_PAD;
          3. Executestate_changed_at = now_ms;
        False
        1. Decisionstate == SIMON_PLAYER_FLASH && elapsed >= SIMON_PLAYER_FLASH_MS
          True
            1. ExecutestopPadTone();
            2. Decisionplayer_index >= sequence_length
              True
                1. Executecompleted_rounds = sequence_length;
                2. Decisionsequence_length < SIMON_MAX_STEPS
                  True
                    1. ExecuteaddSequencePad();
                    2. ExecutebeginSequence(now_ms);
                  False
                    1. Executestate = SIMON_WAITING;
                    2. Executestate_changed_at = now_ms;
              False
                1. Executestate = SIMON_PLAYER_TURN;
                2. Executestate_changed_at = now_ms;
          False
          1. Decisionstate == SIMON_FAILED && elapsed >= SIMON_FAILURE_MS
            True
              1. ExecuteaudioStopTestTone();
              2. Executestate = SIMON_WAITING;
              3. Executestate_changed_at = now_ms;
            False

            Continue 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:

  1. Decision!running || frame == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeclear(frame, COLOR_BLACK);
  3. FOR loopunsigned pad = 0U; pad < SIMON_PAD_COUNT; ++pad

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

      1. Executeconst bool highlighted = lit_pad == (int8_t)pad;
      2. Executeconst uint16_t color = highlighted ? pad_lit[pad] : pad_dim[pad];
      3. ExecutefillSimonPad(frame, (simon_pad_t)pad, color);
  4. Executefill_circle(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS, COLOR_BLACK);
  5. Executedraw_ring(frame, SIMON_CENTRE_X, SIMON_CENTRE_Y, SIMON_CENTRE_RADIUS, COLOR_WHITE);
  6. FOR loopunsigned pad = 0U; pad < SIMON_PAD_COUNT; ++pad

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

      1. Executeconst bool highlighted = lit_pad == (int8_t)pad;
      2. Executedraw_text_centred(frame, pad_label_x[pad], pad_label_y[pad], pad_label[pad], 3, highlighted ? COLOR_BLACK : COLOR_WHITE);
  7. Executedraw_text_centred(frame, SIMON_CENTRE_X, 46, "SIMON", 1, COLOR_WHITE);
  8. Decisionstate == SIMON_WAITING
    True
      1. Executedraw_text_centred(frame, SIMON_CENTRE_X, 80, "A B C D START", 1, COLOR_WHITE);
    False
    1. Decisionstate == SIMON_FAILED
      True
        1. Executechar score[24];
        2. Executesnprintf(score, sizeof(score), "OUT AT %u", (unsigned)sequence_length);
        3. Executedraw_text_centred(frame, SIMON_CENTRE_X, 72, "WRONG!", 2, COLOR_RED);
        4. Executedraw_text_centred(frame, SIMON_CENTRE_X, 96, score, 1, COLOR_WHITE);
      False
      1. Decisionstate == SIMON_PLAYER_TURN || state == SIMON_PLAYER_FLASH
        True
          1. Executechar round[24];
          2. Executesnprintf(round, sizeof(round), "ROUND %u", (unsigned)sequence_length);
          3. Executedraw_text_centred(frame, SIMON_CENTRE_X, 72, "YOUR TURN", 1, COLOR_WHITE);
          4. Executedraw_text_centred(frame, SIMON_CENTRE_X, 88, round, 1, COLOR_LIGHTGRAY);
        False
          1. Executedraw_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);
    }
}