Components and supplies
1
Arduino Nano
2
Adressable WS2812B RGB LED light strip
3
Pushbutton
2
Potentiometers
3
10k Resistor
1
3watt speaker
Tools and machines
1
Soldering Iron Kit
Apps and platforms
1
Arduino IDE
Project description
Code
Code
cpp
...
1// by mircemk, 2026 2 3#include <Adafruit_NeoPixel.h> 4 5#define PIN_L 5 6#define PIN_R 6 7 8#define PIN_START 7 9#define PIN_BTN_L 8 10#define PIN_BTN_R 9 11 12#define PIN_SPK 10 13 14#define PIN_BRIGHT A4 15#define PIN_ZOOM A5 16 17const int LEDS = 50; 18 19const uint8_t BRIGHT_MAX = 100; // максимум наместо 255 20const unsigned long BRIGHT_UPDATE_MS = 25; 21const uint8_t BRIGHT_SMOOTH_SHIFT = 3; // 1/8 smoothing (поголемо = помазно) 22const uint8_t BRIGHT_DEADBAND = 1; // игнорирај +/-1 чекор 23 24// ---- LAYOUT ---- 25const int SCORE_LEDS = 5; // 45..49 26const int GAP_LEDS = 5; // 40..44 (always OFF) 27const int PLAY_LEDS = LEDS - SCORE_LEDS - GAP_LEDS; // 40 (0..39) 28 29const int GAP_BOTTOM = PLAY_LEDS; // 40 30const int GAP_TOP = PLAY_LEDS + GAP_LEDS - 1; // 44 31const int SCORE_BOTTOM = PLAY_LEDS + GAP_LEDS; // 45 32const int SCORE_TOP = LEDS - 1; // 49 33 34unsigned long lastBrightUpdate = 0; 35uint16_t brightFilt = 0; // работи во 0..(BRIGHT_MAX<<8) 36uint8_t brightApplied = 255; // force first apply 37 38// ---- TIMING ---- 39const unsigned long IDLE_STEP_MS = 30; 40const unsigned long BLINK_MS = 180; 41const unsigned long RED_MIN_MS = 4000; 42const unsigned long RED_MAX_MS = 8000; 43 44const unsigned long BAR_STEP_MS = 10; 45const unsigned long SECOND_PRESS_TIMEOUT_MS = 2000; 46 47// Winner baseline scale (fixed) 48const unsigned long ABS_SCALE_MS = 1000; // 0..1000ms -> 0..40 LEDs 49 50// MATCH FLASH (only first 40 LEDs) 51const byte MATCH_FLASH_TIMES = 3; 52const unsigned long MATCH_FLASH_PERIOD = 220; 53 54// After match flash: show solid winner color for 5 seconds, then go to idle animation 55const unsigned long FINAL_HOLD_MS = 5000; 56 57// Debounce 58const unsigned long DEBOUNCE_MS = 20; 59 60// ---- SOUND ---- 61const uint16_t IDLE_F_MIN = 100; 62const uint16_t IDLE_F_MAX = 1200; 63 64const uint16_t BLINK_F1 = 300; 65const uint16_t BLINK_F2 = 500; 66 67const uint16_t RED_F = 1000; 68 69const unsigned long ROUND_WIN_DELAY_MS = 500; 70 71// Round win (first 4 notes of match theme) + pause 72const uint16_t ROUND_WIN_FREQS[] = { 523, 659, 784, 1046, 0 }; 73const uint16_t ROUND_WIN_DURS[] = { 180, 180, 200, 260, 120 }; 74 75// False-start buzzer (low) 76const uint16_t FALSE_FREQS[] = { 160, 120, 90, 0, 90, 0 }; 77const uint16_t FALSE_DURS[] = { 180, 180, 220, 80, 220, 140 }; 78 79// Match win sequence (longer) 80const uint16_t MATCH_FREQS[] = { 523, 659, 784, 1046, 784, 659, 523, 0, 880, 0 }; 81const uint16_t MATCH_DURS[] = { 120, 120, 140, 200, 120, 120, 180, 90, 260, 150 }; 82 83Adafruit_NeoPixel stripL(LEDS, PIN_L, NEO_GRB + NEO_KHZ800); 84Adafruit_NeoPixel stripR(LEDS, PIN_R, NEO_GRB + NEO_KHZ800); 85 86uint32_t OFF, RED, GREEN, BLUE; 87uint32_t L_COL, R_COL; 88 89enum State { 90 IDLE, 91 BLINK, 92 REDGO, 93 SHOWBARS_ANIM, 94 RESULT_WAIT_START, 95 MATCHFLASH, 96 MATCH_HOLD, 97 MATCH_IDLE 98}; 99State state = IDLE; 100 101// ---- debounce state ---- 102bool stableStart = LOW, stableL = LOW, stableR = LOW; 103bool lastReadStart = LOW, lastReadL = LOW, lastReadR = LOW; 104unsigned long lastChangeStart = 0; 105unsigned long lastChangeL = 0; 106unsigned long lastChangeR = 0; 107 108// ---- idle chase ---- 109unsigned long lastIdle = 0; 110int chasePos = 0; 111byte chaseHue = 0; 112 113// ---- blink ---- 114unsigned long lastBlink = 0; 115bool blinkToggle = false; 116unsigned long redGoTime = 0; 117 118// ---- round ---- 119bool lUsed = false, rUsed = false; 120bool gotL = false, gotR = false; 121unsigned long tRed = 0; 122unsigned long reactL = 0, reactR = 0; 123 124byte winner = 0; // 1 left, 2 right 125 126int targetBarL = 0, targetBarR = 0; 127int curBarL = 0, curBarR = 0; 128unsigned long lastBarStep = 0; 129 130// ---- score ---- 131byte scoreL = 0, scoreR = 0; 132 133// ---- match flash ---- 134uint32_t matchWinnerColor = 0; 135unsigned long lastFlashT = 0; 136bool flashOn = false; 137byte flashCount = 0; 138 139// ---- final hold timer ---- 140unsigned long finalHoldStart = 0; 141 142// ---- delayed round-win sound trigger ---- 143bool roundWinSoundPending = false; 144unsigned long roundWinSoundAt = 0; 145 146// ---- LED FREEZE DURING ROUND WIN SOUND ---- 147bool freezeLeds = false; // when true: DO NOT call show() anywhere 148int frozenBarL = 0; 149int frozenBarR = 0; 150 151// ================= SOUND PLAYER (non-blocking) ================= 152struct BeepSeq { 153 const uint16_t* freqs = nullptr; 154 const uint16_t* durs = nullptr; // ms per tone 155 uint8_t len = 0; 156 uint8_t idx = 0; 157 bool active = false; 158 unsigned long nextT = 0; 159}; 160 161BeepSeq seq; 162int continuousFreq = 0; 163int currentToneFreq = -1; 164 165void soundStop() { 166 noTone(PIN_SPK); 167 currentToneFreq = -1; 168} 169 170void soundSetContinuous(int f) { 171 continuousFreq = f; 172} 173 174void soundStartSeq(const uint16_t* freqs, const uint16_t* durs, uint8_t len) { 175 seq.freqs = freqs; 176 seq.durs = durs; 177 seq.len = len; 178 seq.idx = 0; 179 seq.active = true; 180 seq.nextT = 0; 181} 182 183bool soundSeqActive() { return seq.active; } 184 185void soundUpdate(unsigned long now) { 186 if (seq.active) { 187 if (seq.nextT == 0 || (long)(now - seq.nextT) >= 0) { 188 if (seq.idx >= seq.len) { 189 seq.active = false; 190 } else { 191 uint16_t f = seq.freqs[seq.idx]; 192 uint16_t d = seq.durs[seq.idx]; 193 194 if (f == 0) { 195 noTone(PIN_SPK); 196 currentToneFreq = -1; 197 } else { 198 // fixed duration per note 199 tone(PIN_SPK, f, d); 200 currentToneFreq = (int)f; 201 } 202 203 seq.nextT = now + d; 204 seq.idx++; 205 } 206 } 207 return; 208 } 209 210 // No sequence active -> continuous tone 211 if (continuousFreq <= 0) { 212 if (currentToneFreq != -1) soundStop(); 213 } else { 214 if (currentToneFreq != continuousFreq) { 215 tone(PIN_SPK, (unsigned int)continuousFreq); 216 currentToneFreq = continuousFreq; 217 } 218 } 219} 220 221// ================= HELPERS ================= 222uint32_t wheel(byte p) { 223 p = 255 - p; 224 if (p < 85) return stripL.Color(255 - p * 3, 0, p * 3); 225 if (p < 170) { p -= 85; return stripL.Color(0, p * 3, 255 - p * 3); } 226 p -= 170; return stripL.Color(p * 3, 255 - p * 3, 0); 227} 228 229void showBoth() { 230 if (freezeLeds) return; 231 stripL.show(); 232 stripR.show(); 233} 234 235void clearPlayfield() { 236 for (int i = 0; i < PLAY_LEDS; i++) { 237 stripL.setPixelColor(i, OFF); 238 stripR.setPixelColor(i, OFF); 239 } 240} 241 242void clearGap() { 243 for (int i = GAP_BOTTOM; i <= GAP_TOP; i++) { 244 stripL.setPixelColor(i, OFF); 245 stripR.setPixelColor(i, OFF); 246 } 247} 248 249void setPlayfield(uint32_t c) { 250 for (int i = 0; i < PLAY_LEDS; i++) { 251 stripL.setPixelColor(i, c); 252 stripR.setPixelColor(i, c); 253 } 254} 255 256void drawScore() { 257 clearGap(); 258 for (int i = SCORE_BOTTOM; i <= SCORE_TOP; i++) { 259 stripL.setPixelColor(i, OFF); 260 stripR.setPixelColor(i, OFF); 261 } 262 for (byte i = 0; i < scoreL && i < SCORE_LEDS; i++) stripL.setPixelColor(SCORE_TOP - i, L_COL); 263 for (byte i = 0; i < scoreR && i < SCORE_LEDS; i++) stripR.setPixelColor(SCORE_TOP - i, R_COL); 264} 265 266unsigned long readZoom() { 267 int v = analogRead(PIN_ZOOM); 268 return 10UL + (unsigned long)((v * 990UL) / 1023UL); 269} 270 271int mapAbs(unsigned long ms) { 272 if (ms > ABS_SCALE_MS) ms = ABS_SCALE_MS; 273 unsigned long bar = (ms * (unsigned long)PLAY_LEDS + (ABS_SCALE_MS / 2)) / ABS_SCALE_MS; 274 if (bar > (unsigned long)PLAY_LEDS) bar = PLAY_LEDS; 275 return (int)bar; 276} 277 278int mapDiff(unsigned long diff, unsigned long zoom, int maxExtra) { 279 if (zoom < 10) zoom = 10; 280 if (diff > zoom) diff = zoom; 281 unsigned long extra = (diff * (unsigned long)maxExtra + (zoom / 2)) / zoom; 282 if ((int)extra > maxExtra) extra = maxExtra; 283 return (int)extra; 284} 285 286void resetRoundVars() { 287 lUsed = rUsed = false; 288 gotL = gotR = false; 289 reactL = reactR = 0; 290 winner = 0; 291 292 targetBarL = targetBarR = 0; 293 curBarL = curBarR = 0; 294 295 roundWinSoundPending = false; 296 freezeLeds = false; 297} 298 299void startRoundKeepScore(unsigned long now) { 300 state = BLINK; 301 lastBlink = now; 302 blinkToggle = false; 303 redGoTime = now + random(RED_MIN_MS, RED_MAX_MS + 1); 304 305 resetRoundVars(); 306 307 clearPlayfield(); 308 drawScore(); 309 showBoth(); 310 311 soundSetContinuous(BLINK_F1); 312} 313 314void startMatchFlash(uint32_t winColor) { 315 matchWinnerColor = winColor; 316 state = MATCHFLASH; 317 lastFlashT = millis(); 318 flashOn = false; 319 flashCount = 0; 320 321 soundStartSeq(MATCH_FREQS, MATCH_DURS, sizeof(MATCH_FREQS) / sizeof(MATCH_FREQS[0])); 322 soundSetContinuous(0); 323} 324 325// Draw bars once (used when freezing) 326void drawBarsStatic(int barL, int barR) { 327 clearPlayfield(); 328 for (int i = 0; i < barL && i < PLAY_LEDS; i++) stripL.setPixelColor(i, L_COL); 329 for (int i = 0; i < barR && i < PLAY_LEDS; i++) stripR.setPixelColor(i, R_COL); 330 drawScore(); 331 showBoth(); 332} 333 334// ---------- setup ---------- 335void setup() { 336 pinMode(PIN_START, INPUT); 337 pinMode(PIN_BTN_L, INPUT); 338 pinMode(PIN_BTN_R, INPUT); 339 pinMode(PIN_SPK, OUTPUT); 340 341 pinMode(PIN_BRIGHT, INPUT); 342 pinMode(PIN_ZOOM, INPUT); 343 344 stripL.begin(); 345 stripR.begin(); 346 347 OFF = stripL.Color(0, 0, 0); 348 RED = stripL.Color(255, 0, 0); 349 GREEN = stripL.Color(0, 255, 0); 350 BLUE = stripL.Color(0, 0, 255); 351 352 L_COL = stripL.Color(255, 180, 0); 353 R_COL = stripL.Color(255, 0, 180); 354 355 randomSeed(analogRead(A0)); 356 357 state = IDLE; 358 clearPlayfield(); 359 drawScore(); 360 showBoth(); 361 362 soundSetContinuous(IDLE_F_MIN); 363} 364 365// ---------- loop ---------- 366void loop() { 367 unsigned long now = millis(); 368 369 // Brightness 370// Brightness (filtered + capped + rate limited) 371if (now - lastBrightUpdate >= BRIGHT_UPDATE_MS) { 372 lastBrightUpdate = now; 373 374 // map pot -> 0..BRIGHT_MAX 375 uint16_t raw = analogRead(PIN_BRIGHT); // 0..1023 376 uint16_t target = (raw * BRIGHT_MAX + 511) / 1023; // 0..BRIGHT_MAX 377 378 // IIR smoothing in fixed point (<<8) 379 uint16_t targetFP = (uint16_t)(target << 8); 380 brightFilt = brightFilt + ((int32_t)targetFP - (int32_t)brightFilt) / (1 << BRIGHT_SMOOTH_SHIFT); 381 382 uint8_t b = (uint8_t)(brightFilt >> 8); 383 384 // deadband to avoid flicker from tiny pot noise 385 if (b > brightApplied + BRIGHT_DEADBAND || b + BRIGHT_DEADBAND < brightApplied) { 386 brightApplied = b; 387 stripL.setBrightness(brightApplied); 388 stripR.setBrightness(brightApplied); 389 } 390} 391 392 // ---- DEBOUNCE -> edges ---- 393 bool startEdge = false, lEdge = false, rEdge = false; 394 395 bool readStart = digitalRead(PIN_START); 396 if (readStart != lastReadStart) { lastChangeStart = now; lastReadStart = readStart; } 397 if (now - lastChangeStart > DEBOUNCE_MS) { 398 if (stableStart != readStart) { stableStart = readStart; if (stableStart == HIGH) startEdge = true; } 399 } 400 401 bool readL = digitalRead(PIN_BTN_L); 402 if (readL != lastReadL) { lastChangeL = now; lastReadL = readL; } 403 if (now - lastChangeL > DEBOUNCE_MS) { 404 if (stableL != readL) { stableL = readL; if (stableL == HIGH) lEdge = true; } 405 } 406 407 bool readR = digitalRead(PIN_BTN_R); 408 if (readR != lastReadR) { lastChangeR = now; lastReadR = readR; } 409 if (now - lastChangeR > DEBOUNCE_MS) { 410 if (stableR != readR) { stableR = readR; if (stableR == HIGH) rEdge = true; } 411 } 412 413 // Update sound engine 414 soundUpdate(now); 415 416 // When round-win sound is active, freeze LED updates completely for clean tone 417 if (freezeLeds) { 418 if (!soundSeqActive()) { 419 // sequence ended -> unfreeze 420 freezeLeds = false; 421 // redraw once (so zoom changes etc. can resume) 422 drawBarsStatic(curBarL, curBarR); 423 } 424 } 425 426 // Trigger delayed round-win sound ONLY when time comes and nothing else is playing 427 if (roundWinSoundPending && (long)(now - roundWinSoundAt) >= 0 && !soundSeqActive()) { 428 // Freeze LEDs for the whole round-win sequence 429 freezeLeds = true; 430 // show the bars ONCE, then stop calling show() 431 drawBarsStatic(curBarL, curBarR); 432 433 soundStartSeq(ROUND_WIN_FREQS, ROUND_WIN_DURS, sizeof(ROUND_WIN_FREQS) / sizeof(ROUND_WIN_FREQS[0])); 434 soundSetContinuous(0); 435 roundWinSoundPending = false; 436 } 437 438 // ---- START behavior ---- 439 if (startEdge) { 440 if (state == MATCH_HOLD || state == MATCH_IDLE) { 441 scoreL = 0; 442 scoreR = 0; 443 startRoundKeepScore(now); 444 return; 445 } 446 if (state != MATCHFLASH) { 447 startRoundKeepScore(now); 448 return; 449 } 450 } 451 452 // ---- IDLE / MATCH_IDLE ---- 453 if (state == IDLE || state == MATCH_IDLE) { 454 if (now - lastIdle >= IDLE_STEP_MS) { 455 lastIdle = now; 456 457 clearPlayfield(); 458 uint32_t c = wheel(chaseHue); 459 stripL.setPixelColor(chasePos, c); 460 stripR.setPixelColor(chasePos, c); 461 462 drawScore(); 463 showBoth(); 464 465 uint16_t f = (uint16_t)(IDLE_F_MIN + (uint32_t)(IDLE_F_MAX - IDLE_F_MIN) * (uint32_t)chasePos / (uint32_t)(PLAY_LEDS - 1)); 466 soundSetContinuous(f); 467 468 chasePos++; 469 if (chasePos >= PLAY_LEDS) chasePos = 0; 470 chaseHue += 3; 471 } 472 return; 473 } 474 475 // ---- BLINK ---- 476 if (state == BLINK) { 477 // false start => lose + low buzzer 478 if (lEdge && !lUsed) { 479 lUsed = true; 480 winner = 2; 481 gotL = gotR = true; 482 reactL = reactR = 0; 483 484 soundStartSeq(FALSE_FREQS, FALSE_DURS, sizeof(FALSE_FREQS) / sizeof(FALSE_FREQS[0])); 485 soundSetContinuous(0); 486 487 if (scoreR < SCORE_LEDS) scoreR++; 488 if (scoreR >= 5) { startMatchFlash(R_COL); return; } 489 490 unsigned long zoom = readZoom(); 491 int base = mapAbs(0); 492 int extra = mapDiff(0, zoom, PLAY_LEDS - base); 493 targetBarR = base; 494 targetBarL = base + extra; 495 496 curBarL = curBarR = 0; 497 lastBarStep = now; 498 state = SHOWBARS_ANIM; 499 return; 500 } 501 502 if (rEdge && !rUsed) { 503 rUsed = true; 504 winner = 1; 505 gotL = gotR = true; 506 reactL = reactR = 0; 507 508 soundStartSeq(FALSE_FREQS, FALSE_DURS, sizeof(FALSE_FREQS) / sizeof(FALSE_FREQS[0])); 509 soundSetContinuous(0); 510 511 if (scoreL < SCORE_LEDS) scoreL++; 512 if (scoreL >= 5) { startMatchFlash(L_COL); return; } 513 514 unsigned long zoom = readZoom(); 515 int base = mapAbs(0); 516 int extra = mapDiff(0, zoom, PLAY_LEDS - base); 517 targetBarL = base; 518 targetBarR = base + extra; 519 520 curBarL = curBarR = 0; 521 lastBarStep = now; 522 state = SHOWBARS_ANIM; 523 return; 524 } 525 526 if (now - lastBlink >= BLINK_MS) { 527 lastBlink = now; 528 blinkToggle = !blinkToggle; 529 530 clearPlayfield(); 531 uint32_t c = blinkToggle ? GREEN : BLUE; 532 for (int i = 0; i < 3; i++) { 533 stripL.setPixelColor(i, c); 534 stripR.setPixelColor(i, c); 535 } 536 drawScore(); 537 showBoth(); 538 539 soundSetContinuous(blinkToggle ? BLINK_F1 : BLINK_F2); 540 } 541 542 if ((long)(now - redGoTime) >= 0) { 543 tRed = now; 544 545 clearPlayfield(); 546 for (int i = 0; i < 3; i++) { 547 stripL.setPixelColor(i, RED); 548 stripR.setPixelColor(i, RED); 549 } 550 drawScore(); 551 showBoth(); 552 553 state = REDGO; 554 lUsed = rUsed = false; 555 gotL = gotR = false; 556 557 soundSetContinuous(RED_F); 558 return; 559 } 560 return; 561 } 562 563 // ---- REDGO ---- 564 if (state == REDGO) { 565 if (lEdge && !lUsed) { lUsed = true; gotL = true; reactL = now - tRed; if (winner == 0) winner = 1; } 566 if (rEdge && !rUsed) { rUsed = true; gotR = true; reactR = now - tRed; if (winner == 0) winner = 2; } 567 568 bool timeout = false; 569 if (winner != 0 && (now - tRed >= SECOND_PRESS_TIMEOUT_MS)) timeout = true; 570 571 if ((gotL && gotR) || timeout) { 572 if (!gotL) reactL = SECOND_PRESS_TIMEOUT_MS; 573 if (!gotR) reactR = SECOND_PRESS_TIMEOUT_MS; 574 575 if (winner == 1 && scoreL < SCORE_LEDS) scoreL++; 576 if (winner == 2 && scoreR < SCORE_LEDS) scoreR++; 577 578 soundSetContinuous(0); 579 580 if (scoreL >= 5) { startMatchFlash(L_COL); return; } 581 if (scoreR >= 5) { startMatchFlash(R_COL); return; } 582 583 unsigned long zoom = readZoom(); 584 unsigned long wMs = (winner == 1) ? reactL : reactR; 585 unsigned long lMs = (winner == 1) ? reactR : reactL; 586 587 int base = mapAbs(wMs); 588 int maxExtra = PLAY_LEDS - base; 589 if (maxExtra < 0) maxExtra = 0; 590 591 int extra = mapDiff((lMs > wMs) ? (lMs - wMs) : 0, zoom, maxExtra); 592 593 if (winner == 1) { targetBarL = base; targetBarR = base + extra; } 594 else { targetBarR = base; targetBarL = base + extra; } 595 596 curBarL = curBarR = 0; 597 lastBarStep = now; 598 state = SHOWBARS_ANIM; 599 } 600 return; 601 } 602 603 // ---- SHOWBARS_ANIM ---- 604 if (state == SHOWBARS_ANIM) { 605 if (winner != 0) { 606 unsigned long zoom = readZoom(); 607 unsigned long wMs = (winner == 1) ? reactL : reactR; 608 unsigned long lMs = (winner == 1) ? reactR : reactL; 609 610 int base = mapAbs(wMs); 611 int maxExtra = PLAY_LEDS - base; 612 if (maxExtra < 0) maxExtra = 0; 613 614 int extra = mapDiff((lMs > wMs) ? (lMs - wMs) : 0, zoom, maxExtra); 615 616 if (winner == 1) { targetBarL = base; targetBarR = base + extra; } 617 else { targetBarR = base; targetBarL = base + extra; } 618 } 619 620 if (now - lastBarStep >= BAR_STEP_MS) { 621 lastBarStep = now; 622 623 if (curBarL < targetBarL) curBarL++; 624 if (curBarR < targetBarR) curBarR++; 625 626 clearPlayfield(); 627 for (int i = 0; i < curBarL && i < PLAY_LEDS; i++) stripL.setPixelColor(i, L_COL); 628 for (int i = 0; i < curBarR && i < PLAY_LEDS; i++) stripR.setPixelColor(i, R_COL); 629 630 drawScore(); 631 showBoth(); 632 633 if (curBarL >= targetBarL && curBarR >= targetBarR) { 634 state = RESULT_WAIT_START; 635 636 // schedule clean round win sound after bars shown 637 roundWinSoundPending = true; 638 roundWinSoundAt = now + ROUND_WIN_DELAY_MS; 639 } 640 } 641 return; 642 } 643 644 // ---- RESULT_WAIT_START ---- 645 if (state == RESULT_WAIT_START) { 646 // If LEDs are frozen due to sound -> do not update visuals 647 if (freezeLeds) return; 648 649 unsigned long zoom = readZoom(); 650 unsigned long wMs = (winner == 1) ? reactL : reactR; 651 unsigned long lMs = (winner == 1) ? reactR : reactL; 652 653 int base = mapAbs(wMs); 654 int maxExtra = PLAY_LEDS - base; 655 if (maxExtra < 0) maxExtra = 0; 656 657 int extra = mapDiff((lMs > wMs) ? (lMs - wMs) : 0, zoom, maxExtra); 658 659 if (winner == 1) { targetBarL = base; targetBarR = base + extra; } 660 else { targetBarR = base; targetBarL = base + extra; } 661 662 static unsigned long lastT = 0; 663 if (now - lastT >= BAR_STEP_MS) { 664 lastT = now; 665 666 if (curBarL < targetBarL) curBarL++; 667 else if (curBarL > targetBarL) curBarL--; 668 669 if (curBarR < targetBarR) curBarR++; 670 else if (curBarR > targetBarR) curBarR--; 671 672 clearPlayfield(); 673 for (int i = 0; i < curBarL && i < PLAY_LEDS; i++) stripL.setPixelColor(i, L_COL); 674 for (int i = 0; i < curBarR && i < PLAY_LEDS; i++) stripR.setPixelColor(i, R_COL); 675 676 drawScore(); 677 showBoth(); 678 } 679 return; 680 } 681 682 // ---- MATCHFLASH ---- 683 if (state == MATCHFLASH) { 684 if (now - lastFlashT >= MATCH_FLASH_PERIOD) { 685 lastFlashT = now; 686 flashOn = !flashOn; 687 688 if (flashOn) setPlayfield(matchWinnerColor); 689 else clearPlayfield(); 690 691 drawScore(); 692 showBoth(); 693 694 if (!flashOn) { 695 flashCount++; 696 if (flashCount >= MATCH_FLASH_TIMES) { 697 setPlayfield(matchWinnerColor); 698 drawScore(); 699 showBoth(); 700 701 finalHoldStart = now; 702 state = MATCH_HOLD; 703 } 704 } 705 } 706 return; 707 } 708 709 // ---- MATCH_HOLD ---- 710 if (state == MATCH_HOLD) { 711 if (now - finalHoldStart >= FINAL_HOLD_MS) { 712 state = MATCH_IDLE; 713 714 lastIdle = now; 715 chasePos = 0; 716 chaseHue = 0; 717 718 clearPlayfield(); 719 drawScore(); 720 showBoth(); 721 722 soundSetContinuous(IDLE_F_MIN); 723 } 724 return; 725 } 726}
Documentation
Schematic
...
Schematic.jpg

Comments
Only logged in users can leave comments