Skip to content

Instantly share code, notes, and snippets.

@pardeike
Created September 1, 2025 00:18
Show Gist options
  • Save pardeike/2ec06a660e49ab937828573c6979c20a to your computer and use it in GitHub Desktop.
Save pardeike/2ec06a660e49ab937828573c6979c20a to your computer and use it in GitHub Desktop.
LED Animation for 6x8 board
#include "SPI.h"
#include "Adafruit_WS2801.h"
const int numStrands = 1; // number of LED strands to address
const int strandLEDs = 50; //Number of LEDs per strand
const int numLED = strandLEDs*numStrands; //Total number of LEDs
uint8_t analogPin = 14; // A0 - Potentiometer wiper (middle terminal) connected to analog pin 0
uint8_t buttonPin = 15; // A1
uint8_t dataPin = 17; // A3 - Analog pins used to simplify wiring. By using Analog pins we only need to have wires on one side of the Arduino Nano
uint8_t clockPin = 19; // A5
Adafruit_WS2801 strip = Adafruit_WS2801(numLED, dataPin, clockPin);
int nx = 0, ny = 0;
int pCount = strandLEDs - 2;
int xMax = 6, yMax = 8;
int mapping[] = { 7, 6, 5, 4, 3, 2,
8, 9, 10, 11, 12, 13,
19, 18, 17, 16, 15, 14,
20, 21, 22, 23, 24, 25,
31, 30, 29, 28, 27, 26,
32, 33, 34, 35, 36, 37,
43, 42, 41, 40, 39, 38,
44, 45, 46, 47, 48, 49,
};
// input color components 0 to 255 to get a color value
uint32_t color(byte r, byte g, byte b) {
uint32_t c; c = r; c <<= 8; c |= g; c <<= 8; c |= b;
return c;
}
// input a value 0 to 255 to get a color value
// the colours are a transition r - g -b - back to r
uint32_t wheel(byte WheelPos)
{
if (WheelPos < 85) {
return color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
else if (WheelPos < 170) {
WheelPos -= 85;
return color(255 - WheelPos * 3, 0, WheelPos * 3);
}
else {
WheelPos -= 170;
return color(0, WheelPos * 3, 255 - WheelPos * 3);
}
}
// sets a pixel at x/y to a specific rgb color
void pixel(int x, int y, byte r, byte g, byte b) {
strip.setPixelColor(mapping[x + y * 6], color(r, g, b));
}
// sets a pixel at x/y to a color value
void pixel(int x, int y, uint32_t c) {
strip.setPixelColor(mapping[x + y * 6], c);
}
// ---------------------------------------------------------------------------------------------------
const uint8_t W = 6, H = 8;
inline int IDX(int x, int y) { return x + y * W; }
// ---- Small framebuffer (for trail-based effects)
uint8_t fbR[W * H], fbG[W * H], fbB[W * H];
inline uint8_t u8satAdd(uint8_t a, uint8_t b) {
uint16_t s = a + b; return (s > 255) ? 255 : s;
}
inline uint8_t clampU8(int v) { return v < 0 ? 0 : (v > 255 ? 255 : v); }
void fbClear() { for (int i = 0; i < W * H; i++) fbR[i] = fbG[i] = fbB[i] = 0; }
void fbFade(uint8_t amt) {
for (int i = 0; i < W * H; i++) {
fbR[i] = fbR[i] > amt ? fbR[i] - amt : 0;
fbG[i] = fbG[i] > amt ? fbG[i] - amt : 0;
fbB[i] = fbB[i] > amt ? fbB[i] - amt : 0;
}
}
void fbAddRGB(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
if ((unsigned)x >= W || (unsigned)y >= H) return;
int i = IDX(x, y);
fbR[i] = u8satAdd(fbR[i], r);
fbG[i] = u8satAdd(fbG[i], g);
fbB[i] = u8satAdd(fbB[i], b);
}
void fbAddColorScaled(int x, int y, uint32_t c, uint8_t scale) {
uint8_t r = (c >> 16) & 0xFF, g = (c >> 8) & 0xFF, b = c & 0xFF;
r = (uint16_t)r * scale / 255;
g = (uint16_t)g * scale / 255;
b = (uint16_t)b * scale / 255;
fbAddRGB(x, y, r, g, b);
}
void fbBlit() {
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++) {
int i = IDX(x, y);
pixel(x, y, fbR[i], fbG[i], fbB[i]);
}
}
uint32_t dimColor(uint32_t c, uint8_t scale) {
uint8_t r = (c >> 16) & 0xFF, g = (c >> 8) & 0xFF, b = c & 0xFF;
r = (uint16_t)r * scale / 255;
g = (uint16_t)g * scale / 255;
b = (uint16_t)b * scale / 255;
return color(r, g, b);
}
void clearPixels() {
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
pixel(x, y, 0, 0, 0);
}
bool myButtonPressed() { // robust rising-edge detector
static uint8_t last = LOW;
uint8_t cur = digitalRead(buttonPin);
bool pressed = (cur == HIGH && last == LOW);
last = cur;
return pressed;
}
// ---- Epoch lets modes re-init on switch
static uint16_t modeEpoch = 0;
// ---- Plasma rainbow
void renderPlasma(int pot) {
static uint16_t tEpoch = 0; static uint16_t phase = 0;
if (tEpoch != modeEpoch) { phase = 0; tEpoch = modeEpoch; }
phase += map(pot, 0, 1023, 1, 6);
float tt = phase * 0.06f;
for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) {
float xx = x - 2.5f, yy = y - 3.5f;
float v = sin(xx * 0.90f + tt)
+ sin(yy * 0.62f - tt * 0.8f)
+ sin((xx + yy) * 0.48f + tt * 1.3f);
uint8_t hue = (uint8_t)(v * 42.5f + 128.0f);
float rad = sqrt(xx * xx + yy * yy);
uint8_t br = clampU8(255 - (int)(rad * 60.0f));
pixel(x, y, dimColor(wheel(hue), br));
}
}
// ---- Rotating swirl
void renderSwirl(int pot) {
static uint16_t tEpoch = 0; static uint16_t rot = 0;
if (tEpoch != modeEpoch) { rot = 0; tEpoch = modeEpoch; }
rot += map(pot, 0, 1023, 1, 4);
float t = rot * 0.055f;
const float invTau = 255.0f / (6.2831853f);
for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) {
float dx = x - 2.5f, dy = y - 3.5f;
float ang = atan2(dy, dx) + t;
int hue = (int)(ang * invTau) & 255;
float r = sqrt(dx * dx + dy * dy);
float pulse = 0.5f + 0.5f * sin(r * 3.2f - t * 2.0f);
uint8_t br = clampU8(40 + (int)(pulse * 215.0f));
pixel(x, y, dimColor(wheel((uint8_t)hue), br));
}
}
// ---- Lissajous comet (trails)
void renderComet(int pot) {
static uint16_t tEpoch = 0; static uint16_t t = 0;
if (tEpoch != modeEpoch) { fbClear(); t = 0; tEpoch = modeEpoch; }
fbFade(map(pot, 0, 1023, 10, 35));
t += map(pot, 0, 1023, 2, 8);
float ft = t * 0.05f;
float fx = (sin(ft * 1.71f) + 1.0f) * 0.5f * (W - 1);
float fy = (sin(ft * 2.37f + 1.1f) + 1.0f) * 0.5f * (H - 1);
int hx = (int)(fx + 0.5f), hy = (int)(fy + 0.5f);
uint8_t hue = (uint8_t)((int)(ft * 40.0f) & 255);
uint32_t headCol = wheel(hue);
for (int dy = -1; dy <= 1; dy++)
for (int dx = -1; dx <= 1; dx++) {
int d2 = dx * dx + dy * dy;
uint8_t s = (d2 == 0) ? 255 : (d2 == 1 ? 140 : 60);
fbAddColorScaled(hx + dx, hy + dy, headCol, s);
}
fbAddColorScaled(hx - 2, hy - 1, headCol, 30);
fbAddColorScaled(hx + 2, hy + 1, headCol, 30);
if (random(6) == 0) fbAddRGB(random(W), random(H), 20, 20, 20);
}
// ---- Matrix rain (trails)
void renderMatrix(int pot) {
static uint16_t tEpoch = 0; static int8_t headY[W]; static uint8_t wait[W];
if (tEpoch != modeEpoch) {
for (int x = 0; x < W; x++) { headY[x] = -1; wait[x] = 0; }
fbClear(); tEpoch = modeEpoch;
}
fbFade(map(pot, 0, 1023, 14, 40));
uint8_t spawn = map(pot, 0, 1023, 2, 18);
for (int x = 0; x < W; x++) {
if (headY[x] < 0) {
if (random(20) < spawn) { headY[x] = H - 1; wait[x] = random(1, 4); }
continue;
}
fbAddColorScaled(x, headY[x], color(255,255,255), 255);
fbAddColorScaled(x, headY[x] + 1, color(0,220,90), 200);
fbAddColorScaled(x, headY[x] + 2, color(0,160,60), 120);
fbAddColorScaled(x, headY[x] + 3, color(0,100,30), 70);
if (wait[x] > 0) wait[x]--;
else {
headY[x]--;
wait[x] = random(1, 4);
if (headY[x] < 0) headY[x] = -1;
}
}
if (random(3) == 0) fbAddColorScaled(random(W), random(H), color(0,120,40), 40);
}
// ---- Fireplace (slow-burn, dancing flames)
void renderFireplace(int pot) {
static uint16_t tEpoch = 0;
static uint8_t heat[W][H];
// New: slow-changing wind per row and an ember bed
static int8_t windRow[H];
static int8_t globalWind = 0;
static uint8_t frame = 0;
static uint8_t emberX[3];
static int8_t emberDX[3];
if (tEpoch != modeEpoch) {
memset(heat, 0, sizeof(heat));
memset(windRow, 0, sizeof(windRow));
globalWind = 0; frame = 0;
for (int i = 0; i < 3; ++i) { emberX[i] = random(W); emberDX[i] = 0; }
tEpoch = modeEpoch;
}
// Tuned for a "slow burn"
uint8_t cool = map(pot, 0, 1023, 90, 55); // more cooling -> calmer, more contrast
uint8_t sparkiness = map(pot, 0, 1023, 20, 55); // fewer frequent sparks
uint8_t emberPower = map(pot, 0, 1023, 60, 140); // moderate base heat injections
// Cool the field
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
heat[x][y] = clampU8((int)heat[x][y] - (int)random(0, cool));
// Update slow wind (global bias + row jitter)
if ((frame & 0x03) == 0) { // every 4 frames
globalWind += (int8_t)random(-1, 2); // -1..+1
if (globalWind < -3) globalWind = -3;
if (globalWind > 3) globalWind = 3;
for (int y = 1; y < H - 1; ++y) {
if (windRow[y] < globalWind) windRow[y]++;
else if (windRow[y] > globalWind) windRow[y]--;
if (random(9) == 0) { // occasional local turbulence
windRow[y] += (int8_t)random(-1, 2);
if (windRow[y] < -2) windRow[y] = -2;
if (windRow[y] > 2) windRow[y] = 2;
}
}
}
frame++;
// Diffuse upward with wind bias + small lateral swirl
for (int y = H - 1; y >= 1; --y) {
int8_t w = windRow[y];
for (int x = 0; x < W; ++x) {
// sample from below and below-neighbors (same as before)
uint8_t a = heat[x][y - 1];
uint8_t b = heat[(x + W - 1) % W][y - 1];
uint8_t c = heat[(x + 1) % W][y - 1];
uint8_t d = (y >= 2) ? heat[x][y - 2] : heat[x][y - 1];
// Wind bias: prefer b or c depending on sign
uint16_t sum = (uint16_t)a * 3 + (uint16_t)d * 3 + b + c;
if (w < 0) sum += b;
else if (w > 0) sum += c;
heat[x][y] = sum / (8 + (w != 0));
}
// Gentle lateral swirl on the row (cheap advection)
int s = (windRow[y] > 0) - (windRow[y] < 0); // -1,0,+1
if (s != 0) {
uint8_t row[W];
for (int x = 0; x < W; ++x) row[x] = heat[x][y];
for (int x = 0; x < W; ++x) {
int src = (x - s + W) % W;
heat[x][y] = (uint16_t)row[x] * 3 / 4 + (uint16_t)row[src] / 4;
}
}
}
// Ember bed: a few moving hotspots that feed the flame
for (int i = 0; i < 3; ++i) {
if (random(5) == 0) { // slow drift
emberDX[i] += (int8_t)random(-1, 2);
if (emberDX[i] < -1) emberDX[i] = -1;
if (emberDX[i] > 1) emberDX[i] = 1;
}
emberX[i] = (uint8_t)((emberX[i] + W + emberDX[i]) % W);
int x0 = emberX[i];
uint8_t p = emberPower + random(40);
heat[x0][0] = u8satAdd(heat[x0][0], p);
heat[(x0 + 1) % W][0] = u8satAdd(heat[(x0 + 1) % W][0], p / 2);
heat[(x0 + W - 1) % W][0] = u8satAdd(heat[(x0 + W - 1) % W][0], p / 2);
// occasional tiny cinder into row 1 (rare, and not too hot)
if (random(100) < sparkiness)
heat[x0][1] = u8satAdd(heat[x0][1], random(30, 80));
}
// Very rare bonus spark anywhere on bottom (adds surprise but stays warm)
for (int x = 0; x < W; ++x)
if (random(200) < 2)
heat[x][0] = u8satAdd(heat[x][0], random(80, 160));
// Warm, low-white palette + highlight compression for contrast
auto heatColorSlow = [](uint8_t h) -> uint32_t {
// gamma-ish: compress highlights, emphasize midtones
uint8_t hg = (uint8_t)(((uint16_t)h * (uint16_t)h) >> 8); // ~gamma 2.0
uint8_t r, g, b;
if (hg < 96) { // deep red -> red
r = hg * 2 + (hg >> 2);
g = hg >> 3;
b = 0;
} else if (hg < 170) { // red -> amber
r = 220 + (hg - 96) / 2; // cap near 255
g = (hg - 96) * 2;
b = 0;
} else { // amber -> warm yellow (little blue; avoid "white")
r = 255;
g = 160 + (hg - 170); // tops ~245
b = (hg - 170) / 3; // tiny blue component
}
return color(r, g, b);
};
// Draw with a soft, smoky ceiling (no longer forced-black top)
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
uint32_t c = heatColorSlow(heat[x][y]);
// Vignette the ceiling so tongues can occasionally peek through
if (y >= H - 2) {
if (random(5) == 0) c = color(0, 0, 0); // smoke holes
else c = dimColor(c, (y == H - 1) ? 70 : 100); // heavy dim on top row
} else if (y >= H - 3) {
if (random(8) == 0) c = color(0, 0, 0);
else c = dimColor(c, 140); // slightly darker near ceiling
}
pixel(x, y, c);
}
}
}
// ---- Endless Zoom (square tunnel)
void renderZoom(int pot) {
static uint16_t tEpoch = 0; static uint16_t tick = 0;
if (tEpoch != modeEpoch) { tick = 0; tEpoch = modeEpoch; }
tick += map(pot, 0, 1023, 1, 6);
float t = tick * 0.05f;
float bands = 4.0f;
for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) {
float dx = fabs(x - 2.5f), dy = fabs(y - 3.5f);
float s = (dx > dy ? dx : dy); // Chebyshev radius -> square rings
float z = s * bands - t;
float f = z - floor(z);
uint8_t br = clampU8((int)(pow(1.0f - f, 3.0f) * 255.0f) + 10);
uint8_t hue = ((int)(z * 40.0f) & 255);
pixel(x, y, dimColor(wheel(hue), br));
}
}
// ---- Tetris (auto-play, line clears)
namespace TET {
struct Rot { int8_t dx[4], dy[4]; uint8_t w, h; };
struct Piece { const Rot* r; uint8_t nrot; uint8_t hue; };
// Define rotations (dx/dy from piece bottom-left)
const Rot I[2] = {
{{0,1,2,3}, {0,0,0,0}, 4,1},
{{0,0,0,0}, {0,1,2,3}, 1,4}
};
const Rot O[1] = {
{{0,1,0,1}, {0,0,1,1}, 2,2}
};
const Rot T[4] = {
{{0,1,2,1}, {0,0,0,1}, 3,2},
{{1,0,1,1}, {0,1,1,2}, 2,3},
{{1,0,1,2}, {0,1,1,1}, 3,2},
{{0,0,0,1}, {0,1,2,1}, 2,3}
};
const Rot L[4] = {
{{0,0,0,1}, {0,1,2,0}, 2,3},
{{0,1,2,0}, {0,0,0,1}, 3,2},
{{0,1,1,1}, {2,0,1,2}, 2,3},
{{2,0,1,2}, {1,1,1,0}, 3,2}
};
const Rot S[2] = {
{{1,2,0,1}, {0,0,1,1}, 3,2},
{{0,0,1,1}, {0,1,1,2}, 2,3}
};
const Piece P[5] = {
{I,2, 10}, {O,1, 32}, {T,4, 64}, {L,4, 110}, {S,2, 170}
};
static uint32_t board[W*H];
static int curId, curRot, px, py; // bottom-left anchor
static uint32_t curCol;
static uint32_t lastDrop; static uint16_t dropMs;
static bool inited;
inline uint32_t& B(int x, int y) { return board[IDX(x,y)]; }
void reset() {
memset(board, 0, sizeof(board));
inited = true; lastDrop = 0;
}
static inline bool topRowReached() {
for (int x = 0; x < W; ++x) if (B(x, H - 1) != 0) return true;
return false;
}
bool collideAt(int id, int rot, int ax, int ay) {
const Rot& R = P[id].r[rot];
for (int i = 0; i < 4; i++) {
int x = ax + R.dx[i], y = ay + R.dy[i];
if (x < 0 || x >= W) return true; // wall
if (y < 0) return true; // floor
if (y >= H) continue; // above top: ignore
if (B(x,y)) return true; // hit stack
}
return false;
}
void stamp(int id, int rot, int ax, int ay, uint32_t col) {
const Rot& R = P[id].r[rot];
for (int i = 0; i < 4; i++) {
int x = ax + R.dx[i], y = ay + R.dy[i];
if ((unsigned)x < W && (unsigned)y < H) B(x,y) = col;
}
}
void spawn() {
curId = random(5);
curRot = random(P[curId].nrot);
const Rot& R = P[curId].r[curRot];
px = (W - R.w) / 2;
py = H + 2; // start above top
curCol = dimColor(wheel(P[curId].hue), 200);
if (collideAt(curId, curRot, px, py)) { // no space: reset
reset();
}
}
void clearLines() {
for (int y = 0; y < H; y++) {
bool full = true;
for (int x = 0; x < W; x++) if (!B(x,y)) { full = false; break; }
if (full) {
for (int yy = y; yy < H - 1; yy++)
for (int x = 0; x < W; x++) B(x,yy) = B(x,yy+1);
for (int x = 0; x < W; x++) B(x,H-1) = 0;
y--; // recheck same row after collapse
}
}
}
void tick(int pot) {
if (!inited) { reset(); spawn(); }
dropMs = map(pot, 0, 1023, 800, 200);
uint32_t now = millis();
// occasional auto-rotate if possible
if ((now & 511U) == 0) {
int nr = (curRot + 1) % P[curId].nrot;
if (!collideAt(curId, nr, px, py)) curRot = nr;
}
if (now - lastDrop >= dropMs) {
lastDrop = now;
if (!collideAt(curId, curRot, px, py - 1)) py--;
else { // lock
stamp(curId, curRot, px, py, curCol);
// Reset if the stack touches the top row (y == H-1)
if (topRowReached()) { reset(); spawn(); return; }
clearLines();
// Safety: if still touching after a line clear, reset anyway
if (topRowReached()) { reset(); spawn(); return; }
spawn();
}
// gentle left/right drift if room
int dir = random(3) - 1;
if (!collideAt(curId, curRot, px + dir, py)) px += dir;
}
// render
clearPixels();
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
if (B(x,y)) pixel(x,y,B(x,y));
// draw current piece over board
const Rot& R = P[curId].r[curRot];
for (int i = 0; i < 4; i++) {
int x = px + R.dx[i], y = py + R.dy[i];
if ((unsigned)x < W && (unsigned)y < H) pixel(x, y, curCol);
}
}
} // namespace TET
void renderTetris(int pot) { TET::tick(pot); }
// ---- Pong (AI vs AI)
void renderPong(int pot) {
static uint16_t tEpoch = 0;
static float bx, by, vx, vy, lY, rY;
if (tEpoch != modeEpoch) {
bx = W * 0.5f; by = H * 0.5f;
vx = (random(2) ? 0.25f : -0.25f);
vy = (random(2) ? 0.18f : -0.18f);
lY = rY = by; tEpoch = modeEpoch;
}
float ai = 0.35f + map(pot, 0, 1023, 0, 30) / 100.0f; // paddle agility
float maxStep = 0.7f + map(pot, 0, 1023, 0, 20) / 100.0f;
int padH = 3; float half = padH * 0.5f;
// paddles follow ball with limited speed
float dl = by - lY; if (dl > maxStep) dl = maxStep; if (dl < -maxStep) dl = -maxStep; lY += dl * ai;
float dr = by - rY; if (dr > maxStep) dr = maxStep; if (dr < -maxStep) dr = -maxStep; rY += dr * ai;
// move ball
bx += vx; by += vy;
// wall bounce
if (by < 0) { by = 0; vy = -vy; }
if (by > H - 1) { by = H - 1; vy = -vy; }
auto resetBall = [&]() {
bx = W * 0.5f; by = H * 0.5f;
vx = (random(2) ? 0.25f : -0.25f);
vy = (random(2) ? 0.18f : -0.18f);
};
// paddle collisions / scoring
if (bx <= 0) {
if (fabs(by - lY) <= half + 0.6f) {
bx = 0; vx = fabs(vx); vy += (by - lY) * 0.06f; // add spin
} else { resetBall(); }
}
if (bx >= W - 1) {
if (fabs(by - rY) <= half + 0.6f) {
bx = W - 1; vx = -fabs(vx); vy += (by - rY) * 0.06f;
} else { resetBall(); }
}
// render
clearPixels();
// center dash
for (int y = 0; y < H; y += 2) pixel(W/2, y, 30, 30, 30);
// paddles
uint32_t pCol = dimColor(wheel(120), 220);
for (int dy = -(int)half; dy <= (int)half; dy++) {
int ly = (int)(lY + 0.5f) + dy, ry = (int)(rY + 0.5f) + dy;
if ((unsigned)ly < H) pixel(0, ly, pCol);
if ((unsigned)ry < H) pixel(W - 1, ry, pCol);
}
// ball
uint32_t bCol = dimColor(wheel((millis()>>3) & 255), 255);
pixel((int)(bx + 0.5f), (int)(by + 0.5f), bCol);
}
// ---- Color Pumping (breathing hue + shimmer)
void renderColorPump(int pot) {
static uint16_t tEpoch = 0; static uint16_t tick = 0;
if (tEpoch != modeEpoch) { tick = 0; tEpoch = modeEpoch; }
tick += map(pot, 0, 1023, 1, 6);
float t = tick * 0.05f;
uint8_t baseHue = ((int)(t * 20.0f) & 255);
float pump = 0.5f + 0.5f * sin(t * 1.6f); // 0..1
uint8_t baseBr = 40 + (uint8_t)(pump * 215.0f);
for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) {
float shimmer = 0.5f + 0.5f * sin(t * 2.7f + x * 0.9f + y * 0.6f);
uint8_t br = clampU8((int)(baseBr * (0.75f + 0.25f * shimmer)));
uint8_t hue = baseHue + (x * 18 + y * 12);
pixel(x, y, dimColor(wheel(hue), br));
}
}
// ---- Text Scroll
static const char TEXT_MSG[] = " LEVERANSKOORDINERING ";
static const uint8_t FONT_W = 5, FONT_H = 7, FONT_SP = 1;
// 5x7 glyph columns for the letters we use (bit0 = bottom .. bit6 = top)
static const uint8_t GL_A[5] = {0x3F,0x48,0x48,0x48,0x3F};
static const uint8_t GL_D[5] = {0x7F,0x41,0x22,0x14,0x08};
static const uint8_t GL_E[5] = {0x7F,0x49,0x49,0x49,0x49};
static const uint8_t GL_G[5] = {0x3E,0x41,0x49,0x09,0x0E};
static const uint8_t GL_I[5] = {0x41,0x41,0x7F,0x41,0x41};
static const uint8_t GL_K[5] = {0x7F,0x18,0x24,0x42,0x00};
static const uint8_t GL_L[5] = {0x7F,0x01,0x01,0x01,0x01};
static const uint8_t GL_N[5] = {0x7F,0x60,0x18,0x06,0x7F};
static const uint8_t GL_O[5] = {0x3E,0x41,0x41,0x41,0x3E};
static const uint8_t GL_R[5] = {0x7F,0x48,0x4C,0x4A,0x31};
static const uint8_t GL_S[5] = {0x79,0x49,0x49,0x49,0x4F};
static const uint8_t GL_V[5] = {0x70,0x0C,0x03,0x0C,0x70};
static const uint8_t GL_SP[5]= {0x00,0x00,0x00,0x00,0x00};
static inline uint8_t glyphCol(char ch, uint8_t col) {
if (col >= FONT_W) return 0;
if (ch >= 'a' && ch <= 'z') ch -= 32; // uppercase
switch (ch) {
case 'A': return GL_A[col]; case 'D': return GL_D[col];
case 'E': return GL_E[col]; case 'G': return GL_G[col];
case 'I': return GL_I[col]; case 'K': return GL_K[col];
case 'L': return GL_L[col]; case 'N': return GL_N[col];
case 'O': return GL_O[col]; case 'R': return GL_R[col];
case 'S': return GL_S[col]; case 'V': return GL_V[col];
case ' ': default: return GL_SP[col];
}
}
void renderTextScroll(int pot) {
static uint16_t tEpoch = 0;
static int16_t scroll; // pixel column offset (leftwards)
static uint32_t lastStep;
static int16_t textPix;
if (tEpoch != modeEpoch) {
scroll = W; lastStep = 0; tEpoch = modeEpoch;
textPix = strlen(TEXT_MSG) * (FONT_W + FONT_SP);
}
// speed: lower ms -> faster scroll
uint16_t stepMs = map(pot, 0, 1023, 180, 35);
uint32_t now = millis();
if (now - lastStep >= stepMs) {
lastStep = now;
scroll--;
if (scroll < -(textPix + W)) scroll = W; // loop
}
clearPixels(); // black background
uint8_t baseHue = (uint8_t)((now >> 4) & 0xFF);
for (int y = 0; y < H; y++) {
if (y >= FONT_H) continue; // keep row 7 free (top black bar aesthetic)
for (int x = 0; x < W; x++) {
int mc = x - scroll;
if (mc < 0 || mc >= textPix) continue;
int chIdx = mc / (FONT_W + FONT_SP);
int colIn = mc % (FONT_W + FONT_SP);
if (colIn >= FONT_W) continue; // spacing column
char ch = TEXT_MSG[chIdx];
uint8_t colBits = glyphCol(ch, colIn);
if (colBits & (1 << y)) {
uint8_t hue = baseHue + chIdx * 17; // per-letter hue shift
pixel(x, y, dimColor(wheel(hue), 235));
}
}
}
}
void setup() {
pinMode(buttonPin, INPUT);
pinMode(analogPin, INPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
Serial.begin(57600);
strip.begin();
strip.show();
}
void loop() {
static bool seeded = false;
if (!seeded) { randomSeed(analogRead(analogPin)); seeded = true; }
int pot = analogRead(analogPin);
const uint8_t NUM_MODES = 10;
static uint8_t mode = 0, prevMode = 255;
static uint32_t lastAuto = 0;
static uint32_t autoTimeout = 20000UL;
if (myButtonPressed()) { mode = (mode + 1) % NUM_MODES; lastAuto = millis(); }
uint32_t now = millis();
if (now - lastAuto > autoTimeout) { mode = (mode + 1) % NUM_MODES; lastAuto = now; }
if (mode != prevMode) {
fbClear(); // reset any trails
prevMode = mode; modeEpoch++; // tell modes to re-init
}
switch (mode) {
case 0: renderPlasma(pot); break;
case 1: renderSwirl(pot); break;
case 2: renderTetris(pot); break;
case 3: renderComet(pot); fbBlit(); break;
case 4: renderPong(pot); break;
case 5: renderMatrix(pot); fbBlit(); break;
case 6: renderTextScroll(pot); break;
case 7: renderColorPump(pot); break;
case 8: renderFireplace(pot); break;
case 9: renderZoom(pot); break;
}
strip.show();
delay(map(pot, 0, 1023, 12, 60)); // ~16–83 FPS
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment