Mirko Pavleski
Published © GPL3+

Ultimate 2-Player Reaction Timer with WS2812B LED Strips

This project transforms basic components like LED strips and an Arduino into a professional-grade arcade experience

BeginnerFull instructions provided3 hours301
Ultimate 2-Player Reaction Timer with WS2812B LED Strips

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
WS2812 Led Strip wiyh 50 Led
×2
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×2
Speaker: 0.25W, 8 ohms
Speaker: 0.25W, 8 ohms
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×3
Resistor 10k ohm
Resistor 10k ohm
×3

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

Schematic

...

Code

Code

C/C++
...
//by mircemk 2026

#include <Adafruit_NeoPixel.h>

#define PIN_L 5
#define PIN_R 6

#define PIN_START 7
#define PIN_BTN_L 8
#define PIN_BTN_R 9

#define PIN_SPK 10

#define PIN_BRIGHT A4
#define PIN_ZOOM   A5

const int LEDS = 50;

const uint8_t BRIGHT_MAX = 100;          // максимум наместо 255
const unsigned long BRIGHT_UPDATE_MS = 25;
const uint8_t BRIGHT_SMOOTH_SHIFT = 3;   // 1/8 smoothing (поголемо = помазно)
const uint8_t BRIGHT_DEADBAND = 1;       // игнорирај +/-1 чекор

// ---- LAYOUT ----
const int SCORE_LEDS = 5;   // 45..49
const int GAP_LEDS   = 5;   // 40..44 (always OFF)
const int PLAY_LEDS  = LEDS - SCORE_LEDS - GAP_LEDS; // 40 (0..39)

const int GAP_BOTTOM   = PLAY_LEDS;                // 40
const int GAP_TOP      = PLAY_LEDS + GAP_LEDS - 1; // 44
const int SCORE_BOTTOM = PLAY_LEDS + GAP_LEDS;     // 45
const int SCORE_TOP    = LEDS - 1;                 // 49

unsigned long lastBrightUpdate = 0;
uint16_t brightFilt = 0;     // работи во 0..(BRIGHT_MAX<<8)
uint8_t brightApplied = 255; // force first apply

// ---- TIMING ----
const unsigned long IDLE_STEP_MS = 30;
const unsigned long BLINK_MS     = 180;
const unsigned long RED_MIN_MS   = 4000;
const unsigned long RED_MAX_MS   = 8000;

const unsigned long BAR_STEP_MS  = 10;
const unsigned long SECOND_PRESS_TIMEOUT_MS = 2000;

// Winner baseline scale (fixed)
const unsigned long ABS_SCALE_MS = 1000;  // 0..1000ms -> 0..40 LEDs

// MATCH FLASH (only first 40 LEDs)
const byte          MATCH_FLASH_TIMES  = 3;
const unsigned long MATCH_FLASH_PERIOD = 220;

// After match flash: show solid winner color for 5 seconds, then go to idle animation
const unsigned long FINAL_HOLD_MS = 5000;

// Debounce
const unsigned long DEBOUNCE_MS = 20;

// ---- SOUND ----
const uint16_t IDLE_F_MIN = 100;
const uint16_t IDLE_F_MAX = 1200;

const uint16_t BLINK_F1 = 300;
const uint16_t BLINK_F2 = 500;

const uint16_t RED_F    = 1000;

const unsigned long ROUND_WIN_DELAY_MS = 500;

// Round win (first 4 notes of match theme) + pause
const uint16_t ROUND_WIN_FREQS[] = { 523, 659, 784, 1046, 0 };
const uint16_t ROUND_WIN_DURS[]  = { 180, 180, 200, 260, 120 };

// False-start buzzer (low)
const uint16_t FALSE_FREQS[] = { 160, 120, 90, 0, 90, 0 };
const uint16_t FALSE_DURS[]  = { 180, 180, 220, 80, 220, 140 };

// Match win sequence (longer)
const uint16_t MATCH_FREQS[] = { 523, 659, 784, 1046, 784, 659, 523, 0, 880, 0 };
const uint16_t MATCH_DURS[]  = { 120, 120, 140, 200,  120, 120, 180, 90, 260, 150 };

Adafruit_NeoPixel stripL(LEDS, PIN_L, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel stripR(LEDS, PIN_R, NEO_GRB + NEO_KHZ800);

uint32_t OFF, RED, GREEN, BLUE;
uint32_t L_COL, R_COL;

enum State {
  IDLE,
  BLINK,
  REDGO,
  SHOWBARS_ANIM,
  RESULT_WAIT_START,
  MATCHFLASH,
  MATCH_HOLD,
  MATCH_IDLE
};
State state = IDLE;

// ---- debounce state ----
bool stableStart = LOW, stableL = LOW, stableR = LOW;
bool lastReadStart = LOW, lastReadL = LOW, lastReadR = LOW;
unsigned long lastChangeStart = 0;
unsigned long lastChangeL = 0;
unsigned long lastChangeR = 0;

// ---- idle chase ----
unsigned long lastIdle = 0;
int chasePos = 0;
byte chaseHue = 0;

// ---- blink ----
unsigned long lastBlink = 0;
bool blinkToggle = false;
unsigned long redGoTime = 0;

// ---- round ----
bool lUsed = false, rUsed = false;
bool gotL = false, gotR = false;
unsigned long tRed = 0;
unsigned long reactL = 0, reactR = 0;

byte winner = 0; // 1 left, 2 right

int targetBarL = 0, targetBarR = 0;
int curBarL = 0, curBarR = 0;
unsigned long lastBarStep = 0;

// ---- score ----
byte scoreL = 0, scoreR = 0;

// ---- match flash ----
uint32_t matchWinnerColor = 0;
unsigned long lastFlashT = 0;
bool flashOn = false;
byte flashCount = 0;

// ---- final hold timer ----
unsigned long finalHoldStart = 0;

// ---- delayed round-win sound trigger ----
bool roundWinSoundPending = false;
unsigned long roundWinSoundAt = 0;

// ---- LED FREEZE DURING ROUND WIN SOUND ----
bool freezeLeds = false;     // when true: DO NOT call show() anywhere
int frozenBarL = 0;
int frozenBarR = 0;

// ================= SOUND PLAYER (non-blocking) =================
struct BeepSeq {
  const uint16_t* freqs = nullptr;
  const uint16_t* durs  = nullptr; // ms per tone
  uint8_t len = 0;
  uint8_t idx = 0;
  bool active = false;
  unsigned long nextT = 0;
};

BeepSeq seq;
int continuousFreq = 0;
int currentToneFreq = -1;

void soundStop() {
  noTone(PIN_SPK);
  currentToneFreq = -1;
}

void soundSetContinuous(int f) {
  continuousFreq = f;
}

void soundStartSeq(const uint16_t* freqs, const uint16_t* durs, uint8_t len) {
  seq.freqs = freqs;
  seq.durs  = durs;
  seq.len   = len;
  seq.idx   = 0;
  seq.active = true;
  seq.nextT = 0;
}

bool soundSeqActive() { return seq.active; }

void soundUpdate(unsigned long now) {
  if (seq.active) {
    if (seq.nextT == 0 || (long)(now - seq.nextT) >= 0) {
      if (seq.idx >= seq.len) {
        seq.active = false;
      } else {
        uint16_t f = seq.freqs[seq.idx];
        uint16_t d = seq.durs[seq.idx];

        if (f == 0) {
          noTone(PIN_SPK);
          currentToneFreq = -1;
        } else {
          // fixed duration per note
          tone(PIN_SPK, f, d);
          currentToneFreq = (int)f;
        }

        seq.nextT = now + d;
        seq.idx++;
      }
    }
    return;
  }

  // No sequence active -> continuous tone
  if (continuousFreq <= 0) {
    if (currentToneFreq != -1) soundStop();
  } else {
    if (currentToneFreq != continuousFreq) {
      tone(PIN_SPK, (unsigned int)continuousFreq);
      currentToneFreq = continuousFreq;
    }
  }
}

// ================= HELPERS =================
uint32_t wheel(byte p) {
  p = 255 - p;
  if (p < 85)  return stripL.Color(255 - p * 3, 0, p * 3);
  if (p < 170) { p -= 85; return stripL.Color(0, p * 3, 255 - p * 3); }
  p -= 170;    return stripL.Color(p * 3, 255 - p * 3, 0);
}

void showBoth() {
  if (freezeLeds) return;
  stripL.show();
  stripR.show();
}

void clearPlayfield() {
  for (int i = 0; i < PLAY_LEDS; i++) {
    stripL.setPixelColor(i, OFF);
    stripR.setPixelColor(i, OFF);
  }
}

void clearGap() {
  for (int i = GAP_BOTTOM; i <= GAP_TOP; i++) {
    stripL.setPixelColor(i, OFF);
    stripR.setPixelColor(i, OFF);
  }
}

void setPlayfield(uint32_t c) {
  for (int i = 0; i < PLAY_LEDS; i++) {
    stripL.setPixelColor(i, c);
    stripR.setPixelColor(i, c);
  }
}

void drawScore() {
  clearGap();
  for (int i = SCORE_BOTTOM; i <= SCORE_TOP; i++) {
    stripL.setPixelColor(i, OFF);
    stripR.setPixelColor(i, OFF);
  }
  for (byte i = 0; i < scoreL && i < SCORE_LEDS; i++) stripL.setPixelColor(SCORE_TOP - i, L_COL);
  for (byte i = 0; i < scoreR && i < SCORE_LEDS; i++) stripR.setPixelColor(SCORE_TOP - i, R_COL);
}

unsigned long readZoom() {
  int v = analogRead(PIN_ZOOM);
  return 10UL + (unsigned long)((v * 990UL) / 1023UL);
}

int mapAbs(unsigned long ms) {
  if (ms > ABS_SCALE_MS) ms = ABS_SCALE_MS;
  unsigned long bar = (ms * (unsigned long)PLAY_LEDS + (ABS_SCALE_MS / 2)) / ABS_SCALE_MS;
  if (bar > (unsigned long)PLAY_LEDS) bar = PLAY_LEDS;
  return (int)bar;
}

int mapDiff(unsigned long diff, unsigned long zoom, int maxExtra) {
  if (zoom < 10) zoom = 10;
  if (diff > zoom) diff = zoom;
  unsigned long extra = (diff * (unsigned long)maxExtra + (zoom / 2)) / zoom;
  if ((int)extra > maxExtra) extra = maxExtra;
  return (int)extra;
}

void resetRoundVars() {
  lUsed = rUsed = false;
  gotL = gotR = false;
  reactL = reactR = 0;
  winner = 0;

  targetBarL = targetBarR = 0;
  curBarL = curBarR = 0;

  roundWinSoundPending = false;
  freezeLeds = false;
}

void startRoundKeepScore(unsigned long now) {
  state = BLINK;
  lastBlink = now;
  blinkToggle = false;
  redGoTime = now + random(RED_MIN_MS, RED_MAX_MS + 1);

  resetRoundVars();

  clearPlayfield();
  drawScore();
  showBoth();

  soundSetContinuous(BLINK_F1);
}

void startMatchFlash(uint32_t winColor) {
  matchWinnerColor = winColor;
  state = MATCHFLASH;
  lastFlashT = millis();
  flashOn = false;
  flashCount = 0;

  soundStartSeq(MATCH_FREQS, MATCH_DURS, sizeof(MATCH_FREQS) / sizeof(MATCH_FREQS[0]));
  soundSetContinuous(0);
}

// Draw bars once (used when freezing)
void drawBarsStatic(int barL, int barR) {
  clearPlayfield();
  for (int i = 0; i < barL && i < PLAY_LEDS; i++) stripL.setPixelColor(i, L_COL);
  for (int i = 0; i < barR && i < PLAY_LEDS; i++) stripR.setPixelColor(i, R_COL);
  drawScore();
  showBoth();
}

// ---------- setup ----------
void setup() {
  pinMode(PIN_START, INPUT);
  pinMode(PIN_BTN_L, INPUT);
  pinMode(PIN_BTN_R, INPUT);
  pinMode(PIN_SPK, OUTPUT);

  pinMode(PIN_BRIGHT, INPUT);
  pinMode(PIN_ZOOM, INPUT);

  stripL.begin();
  stripR.begin();

  OFF   = stripL.Color(0, 0, 0);
  RED   = stripL.Color(255, 0, 0);
  GREEN = stripL.Color(0, 255, 0);
  BLUE  = stripL.Color(0, 0, 255);

  L_COL = stripL.Color(255, 180, 0);
  R_COL = stripL.Color(255, 0, 180);

  randomSeed(analogRead(A0));

  state = IDLE;
  clearPlayfield();
  drawScore();
  showBoth();

  soundSetContinuous(IDLE_F_MIN);
}

// ---------- loop ----------
void loop() {
  unsigned long now = millis();

  // Brightness
// Brightness (filtered + capped + rate limited)
if (now - lastBrightUpdate >= BRIGHT_UPDATE_MS) {
  lastBrightUpdate = now;

  // map pot -> 0..BRIGHT_MAX
  uint16_t raw = analogRead(PIN_BRIGHT);                  // 0..1023
  uint16_t target = (raw * BRIGHT_MAX + 511) / 1023;      // 0..BRIGHT_MAX

  // IIR smoothing in fixed point (<<8)
  uint16_t targetFP = (uint16_t)(target << 8);
  brightFilt = brightFilt + ((int32_t)targetFP - (int32_t)brightFilt) / (1 << BRIGHT_SMOOTH_SHIFT);

  uint8_t b = (uint8_t)(brightFilt >> 8);

  // deadband to avoid flicker from tiny pot noise
  if (b > brightApplied + BRIGHT_DEADBAND || b + BRIGHT_DEADBAND < brightApplied) {
    brightApplied = b;
    stripL.setBrightness(brightApplied);
    stripR.setBrightness(brightApplied);
  }
}

  // ---- DEBOUNCE -> edges ----
  bool startEdge = false, lEdge = false, rEdge = false;

  bool readStart = digitalRead(PIN_START);
  if (readStart != lastReadStart) { lastChangeStart = now; lastReadStart = readStart; }
  if (now - lastChangeStart > DEBOUNCE_MS) {
    if (stableStart != readStart) { stableStart = readStart; if (stableStart == HIGH) startEdge = true; }
  }

  bool readL = digitalRead(PIN_BTN_L);
  if (readL != lastReadL) { lastChangeL = now; lastReadL = readL; }
  if (now - lastChangeL > DEBOUNCE_MS) {
    if (stableL != readL) { stableL = readL; if (stableL == HIGH) lEdge = true; }
  }

  bool readR = digitalRead(PIN_BTN_R);
  if (readR != lastReadR) { lastChangeR = now; lastReadR = readR; }
  if (now - lastChangeR > DEBOUNCE_MS) {
    if (stableR != readR) { stableR = readR; if (stableR == HIGH) rEdge = true; }
  }

  // Update sound engine
  soundUpdate(now);

  // When round-win sound is active, freeze LED updates completely for clean tone
  if (freezeLeds) {
    if (!soundSeqActive()) {
      // sequence ended -> unfreeze
      freezeLeds = false;
      // redraw once (so zoom changes etc. can resume)
      drawBarsStatic(curBarL, curBarR);
    }
  }

  // Trigger delayed round-win sound ONLY when time comes and nothing else is playing
  if (roundWinSoundPending && (long)(now - roundWinSoundAt) >= 0 && !soundSeqActive()) {
    // Freeze LEDs for the whole round-win sequence
    freezeLeds = true;
    // show the bars ONCE, then stop calling show()
    drawBarsStatic(curBarL, curBarR);

    soundStartSeq(ROUND_WIN_FREQS, ROUND_WIN_DURS, sizeof(ROUND_WIN_FREQS) / sizeof(ROUND_WIN_FREQS[0]));
    soundSetContinuous(0);
    roundWinSoundPending = false;
  }

  // ---- START behavior ----
  if (startEdge) {
    if (state == MATCH_HOLD || state == MATCH_IDLE) {
      scoreL = 0;
      scoreR = 0;
      startRoundKeepScore(now);
      return;
    }
    if (state != MATCHFLASH) {
      startRoundKeepScore(now);
      return;
    }
  }

  // ---- IDLE / MATCH_IDLE ----
  if (state == IDLE || state == MATCH_IDLE) {
    if (now - lastIdle >= IDLE_STEP_MS) {
      lastIdle = now;

      clearPlayfield();
      uint32_t c = wheel(chaseHue);
      stripL.setPixelColor(chasePos, c);
      stripR.setPixelColor(chasePos, c);

      drawScore();
      showBoth();

      uint16_t f = (uint16_t)(IDLE_F_MIN + (uint32_t)(IDLE_F_MAX - IDLE_F_MIN) * (uint32_t)chasePos / (uint32_t)(PLAY_LEDS - 1));
      soundSetContinuous(f);

      chasePos++;
      if (chasePos >= PLAY_LEDS) chasePos = 0;
      chaseHue += 3;
    }
    return;
  }

  // ---- BLINK ----
  if (state == BLINK) {
    // false start => lose + low buzzer
    if (lEdge && !lUsed) {
      lUsed = true;
      winner = 2;
      gotL = gotR = true;
      reactL = reactR = 0;

      soundStartSeq(FALSE_FREQS, FALSE_DURS, sizeof(FALSE_FREQS) / sizeof(FALSE_FREQS[0]));
      soundSetContinuous(0);

      if (scoreR < SCORE_LEDS) scoreR++;
      if (scoreR >= 5) { startMatchFlash(R_COL); return; }

      unsigned long zoom = readZoom();
      int base = mapAbs(0);
      int extra = mapDiff(0, zoom, PLAY_LEDS - base);
      targetBarR = base;
      targetBarL = base + extra;

      curBarL = curBarR = 0;
      lastBarStep = now;
      state = SHOWBARS_ANIM;
      return;
    }

    if (rEdge && !rUsed) {
      rUsed = true;
      winner = 1;
      gotL = gotR = true;
      reactL = reactR = 0;

      soundStartSeq(FALSE_FREQS, FALSE_DURS, sizeof(FALSE_FREQS) / sizeof(FALSE_FREQS[0]));
      soundSetContinuous(0);

      if (scoreL < SCORE_LEDS) scoreL++;
      if (scoreL >= 5) { startMatchFlash(L_COL); return; }

      unsigned long zoom = readZoom();
      int base = mapAbs(0);
      int extra = mapDiff(0, zoom, PLAY_LEDS - base);
      targetBarL = base;
      targetBarR = base + extra;

      curBarL = curBarR = 0;
      lastBarStep = now;
      state = SHOWBARS_ANIM;
      return;
    }

    if (now - lastBlink >= BLINK_MS) {
      lastBlink = now;
      blinkToggle = !blinkToggle;

      clearPlayfield();
      uint32_t c = blinkToggle ? GREEN : BLUE;
      for (int i = 0; i < 3; i++) {
        stripL.setPixelColor(i, c);
        stripR.setPixelColor(i, c);
      }
      drawScore();
      showBoth();

      soundSetContinuous(blinkToggle ? BLINK_F1 : BLINK_F2);
    }

    if ((long)(now - redGoTime) >= 0) {
      tRed = now;

      clearPlayfield();
      for (int i = 0; i < 3; i++) {
        stripL.setPixelColor(i, RED);
        stripR.setPixelColor(i, RED);
      }
      drawScore();
      showBoth();

      state = REDGO;
      lUsed = rUsed = false;
      gotL = gotR = false;

      soundSetContinuous(RED_F);
      return;
    }
    return;
  }

  // ---- REDGO ----
  if (state == REDGO) {
    if (lEdge && !lUsed) { lUsed = true; gotL = true; reactL = now - tRed; if (winner == 0) winner = 1; }
    if (rEdge && !rUsed) { rUsed = true; gotR = true; reactR = now - tRed; if (winner == 0) winner = 2; }

    bool timeout = false;
    if (winner != 0 && (now - tRed >= SECOND_PRESS_TIMEOUT_MS)) timeout = true;

    if ((gotL && gotR) || timeout) {
      if (!gotL) reactL = SECOND_PRESS_TIMEOUT_MS;
      if (!gotR) reactR = SECOND_PRESS_TIMEOUT_MS;

      if (winner == 1 && scoreL < SCORE_LEDS) scoreL++;
      if (winner == 2 && scoreR < SCORE_LEDS) scoreR++;

      soundSetContinuous(0);

      if (scoreL >= 5) { startMatchFlash(L_COL); return; }
      if (scoreR >= 5) { startMatchFlash(R_COL); return; }

      unsigned long zoom = readZoom();
      unsigned long wMs = (winner == 1) ? reactL : reactR;
      unsigned long lMs = (winner == 1) ? reactR : reactL;

      int base = mapAbs(wMs);
      int maxExtra = PLAY_LEDS - base;
      if (maxExtra < 0) maxExtra = 0;

      int extra = mapDiff((lMs > wMs) ? (lMs - wMs) : 0, zoom, maxExtra);

      if (winner == 1) { targetBarL = base; targetBarR = base + extra; }
      else             { targetBarR = base; targetBarL = base + extra; }

      curBarL = curBarR = 0;
      lastBarStep = now;
      state = SHOWBARS_ANIM;
    }
    return;
  }

  // ---- SHOWBARS_ANIM ----
  if (state == SHOWBARS_ANIM) {
    if (winner != 0) {
      unsigned long zoom = readZoom();
      unsigned long wMs = (winner == 1) ? reactL : reactR;
      unsigned long lMs = (winner == 1) ? reactR : reactL;

      int base = mapAbs(wMs);
      int maxExtra = PLAY_LEDS - base;
      if (maxExtra < 0) maxExtra = 0;

      int extra = mapDiff((lMs > wMs) ? (lMs - wMs) : 0, zoom, maxExtra);

      if (winner == 1) { targetBarL = base; targetBarR = base + extra; }
      else             { targetBarR = base; targetBarL = base + extra; }
    }

    if (now - lastBarStep >= BAR_STEP_MS) {
      lastBarStep = now;

      if (curBarL < targetBarL) curBarL++;
      if (curBarR < targetBarR) curBarR++;

      clearPlayfield();
      for (int i = 0; i < curBarL && i < PLAY_LEDS; i++) stripL.setPixelColor(i, L_COL);
      for (int i = 0; i < curBarR && i < PLAY_LEDS; i++) stripR.setPixelColor(i, R_COL);

      drawScore();
      showBoth();

      if (curBarL >= targetBarL && curBarR >= targetBarR) {
        state = RESULT_WAIT_START;

        // schedule clean round win sound after bars shown
        roundWinSoundPending = true;
        roundWinSoundAt = now + ROUND_WIN_DELAY_MS;
      }
    }
    return;
  }

  // ---- RESULT_WAIT_START ----
  if (state == RESULT_WAIT_START) {
    // If LEDs are frozen due to sound -> do not update visuals
    if (freezeLeds) return;

    unsigned long zoom = readZoom();
    unsigned long wMs = (winner == 1) ? reactL : reactR;
    unsigned long lMs = (winner == 1) ? reactR : reactL;

    int base = mapAbs(wMs);
    int maxExtra = PLAY_LEDS - base;
    if (maxExtra < 0) maxExtra = 0;

    int extra = mapDiff((lMs > wMs) ? (lMs - wMs) : 0, zoom, maxExtra);

    if (winner == 1) { targetBarL = base; targetBarR = base + extra; }
    else             { targetBarR = base; targetBarL = base + extra; }

    static unsigned long lastT = 0;
    if (now - lastT >= BAR_STEP_MS) {
      lastT = now;

      if (curBarL < targetBarL) curBarL++;
      else if (curBarL > targetBarL) curBarL--;

      if (curBarR < targetBarR) curBarR++;
      else if (curBarR > targetBarR) curBarR--;

      clearPlayfield();
      for (int i = 0; i < curBarL && i < PLAY_LEDS; i++) stripL.setPixelColor(i, L_COL);
      for (int i = 0; i < curBarR && i < PLAY_LEDS; i++) stripR.setPixelColor(i, R_COL);

      drawScore();
      showBoth();
    }
    return;
  }

  // ---- MATCHFLASH ----
  if (state == MATCHFLASH) {
    if (now - lastFlashT >= MATCH_FLASH_PERIOD) {
      lastFlashT = now;
      flashOn = !flashOn;

      if (flashOn) setPlayfield(matchWinnerColor);
      else clearPlayfield();

      drawScore();
      showBoth();

      if (!flashOn) {
        flashCount++;
        if (flashCount >= MATCH_FLASH_TIMES) {
          setPlayfield(matchWinnerColor);
          drawScore();
          showBoth();

          finalHoldStart = now;
          state = MATCH_HOLD;
        }
      }
    }
    return;
  }

  // ---- MATCH_HOLD ----
  if (state == MATCH_HOLD) {
    if (now - finalHoldStart >= FINAL_HOLD_MS) {
      state = MATCH_IDLE;

      lastIdle = now;
      chasePos = 0;
      chaseHue = 0;

      clearPlayfield();
      drawScore();
      showBoth();

      soundSetContinuous(IDLE_F_MIN);
    }
    return;
  }
}

Credits

Mirko Pavleski
216 projects • 1571 followers

Comments