From a6111fbdf9b55f774be4af42fef6de79b8946325 Mon Sep 17 00:00:00 2001 From: "DESKTOP-BOB-DEL\\boblo" Date: Sat, 30 Aug 2025 12:36:41 -0700 Subject: [PATCH 01/20] PacMan effect added with colors fixed after gamma correction was fixed in the core --- wled00/FX.cpp | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++ wled00/FX.h | 1 + 2 files changed, 203 insertions(+) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a680de64de..cb3ad1c97e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3155,6 +3155,207 @@ static uint16_t rolling_balls(void) { static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collide,Overlay,Trails;!,!,!;!;1;m12=1"; //bar #endif // WLED_PS_DONT_REPLACE_FX + +/* +/ Pac-Man (created by making modifications to the Ants effect which was a +* modification of the Rolling Balls effect) - Bob Loeffler +* +* The first slider is for speed. +* The second slider is for selecting the number of power dots. +* Checkbox1 is for displaying White Dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black). +* Checkbox2 is for the Compact Dots mode of displaying white dots. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. + aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the second slider. +* aux1 is the main counter for timing +*/ +typedef struct PacManChars { + signed pos; // is for the LED position of the character (all characters) + signed topPos; // is for the LED position of the farthest that the character has moved (PacMan only) + uint32_t color; // is for the color of the character (all characters) + bool direction; // is for the direction of the character (true=away from first LED) (PacMan and ghosts) + bool blue; // is for whether the character should be blue color or not (ghosts only) + bool eaten; // is for whether the power dot was eaten or not (power dots only) +} pacmancharacters_t; + +static uint16_t mode_pacman(void) { + constexpr unsigned numGhosts = 4; + constexpr unsigned ORANGEYELLOW = 0xFFcc00; // was FF8800 but that was too dark-orange after gamma correction was fixed + constexpr unsigned PURPLEISH = 0x900090; // created after gamma correction was fixed + constexpr unsigned ORANGEISH = 0xFF8800; // created after gamma correction was fixed + constexpr unsigned WHITEISH = 0x999999; + constexpr unsigned PACMAN = 0; // PacMan is character[0] + unsigned maxPowerDots = SEGLEN / 10; // Maximum number of power dots depends on segment length, max is 1 every 10 Pixels + unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); // number of Power Dots (between 1 and x) based on intensity slider setting + + if (numPowerDots != SEGENV.aux0) // if the user selected a different number of power dots, reinitialize the animation. + SEGENV.call = 0; + SEGENV.aux0 = numPowerDots; + + //allocate segment data + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + maxPowerDots); // 4 ghosts + 1 PacMan + max number of Power dots + if (SEGLEN <= 20 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed or segment length is too short to have a nice display + pacmancharacters_t *character = reinterpret_cast(SEGENV.data); + + unsigned startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking + if (SEGLEN > 150) + startBlinkingGhostsLED = SEGLEN/4; // For longer strips, start blinking the ghosts when there is only 1/4th of the LEDs left + else + startBlinkingGhostsLED = SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left + + if (SEGENV.call == 0) { + for (int i = 0; i < 5; i++) { + character[i].direction = true; // initial direction of movement. true = ghosts chasing PacMan + character[i].blue = false; + } + // PacMan character[0] + character[PACMAN].color = YELLOW; + character[PACMAN].pos = 10; // initial LED position + character[PACMAN].topPos = character[PACMAN].pos; // Top position (highest LED on the segment) reached by the PacMan character + + // Ghost character + character[1].color = RED; // turns blue when the power dot is eaten; blinks just before it turns back to normal color + character[1].pos = 6; // initial LED position + + // Ghost character + character[2].color = PURPLEISH; // was PURPLE // turns blue when the power dot is eaten; blinks just before it turns back to normal color + character[2].pos = 4; // initial LED position + + // Ghost character + character[3].color = CYAN; // turns blue when the power dot is eaten; blinks just before it turns back to normal color + character[3].pos = 2; // initial LED position + + // Ghost character + character[4].color = ORANGEISH; // was ORANGE // turns blue when the power dot is eaten; blinks just before it turns back to normal color + character[4].pos = 0; // initial LED position + + // Power dot at end of Segment + character[5].pos = SEGLEN-1; // put the first power dot at the end of the segment + + // Power dots, position is set dynamically below + for (int i = 0; i < maxPowerDots; i++) { + character[i+5].color = ORANGEYELLOW; // orangeish yellow power dots + character[i+5].eaten = false; // initially not eaten yet, so set this to false + } + } + + if (strip.now > SEGENV.step) { + SEGENV.step = strip.now; // "+ 100" creates a very jerky movement as the characters jump ahead several pixels each time they move + SEGENV.aux1++; + } + + // fill all LEDs/pixels with black (off) + SEGMENT.fill(BLACK); + + // draw white dots (or black LEDs) so PacMan can start eating them + if (SEGMENT.check1) { // If White Dots option is selected, draw white dots in front of PacMan + for (int i = SEGLEN-1; i >= character[PACMAN].topPos+1; i--) { + SEGMENT.setPixelColor(i, WHITEISH); // white dots + if (!SEGMENT.check2) { // If Compact Dots option is NOT selected, draw black LEDs between the white dots (only works if White Dots is also selected) + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip the black LED before drawing the next white dot + } + } + } + + // update power dot positions: can change if user selects a different number of power dots + unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; //figure out how far apart the power dots will be + for (int i = 1; i < maxPowerDots; i++) { + character[i+5].pos = 10 + ((i * everyXLeds) >> 8); // additional power dots every X LEDs/pixels, character[5] is power dot at end of strip + } + + // blink power dots every 10 ticks of the ticker timer by changing their color between orangish color and black + if (SEGENV.aux1 % 10 == 0) { + if (character[5].color == ORANGEYELLOW) + character[5].color = BLACK; + else + character[5].color = ORANGEYELLOW; + for (int i = 1; i < maxPowerDots; i++) { + character[i+5].color = character[5].color; // blink in sync with the last power dot + } + } + + // if the ghosts are blue and nearing the beginning of the strip, blink them every 15 ticks of the ticker timer by changing their color between blue and black + if (SEGENV.aux1 % 15 == 0) { + if (character[1].blue && (character[PACMAN].pos <= startBlinkingGhostsLED)) { + if (character[1].color == BLUE) + character[1].color = BLACK; + else + character[1].color = BLUE; + + for (int i = 1; i < numGhosts; i++) { + character[i+1].color = character[1].color; // blink in sync with the first ghost + } + } + } + + // now draw the power dots in the segment only if they have not been eaten yet + for (int i = 0; i < numPowerDots; i++) { + if (!character[i+5].eaten) + SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); + } + + // PacMan ate one of the power dots! Chase the ghosts! + for (int j = 0; j < numPowerDots; j++) { + if ((character[PACMAN].pos >= character[j+5].pos)) { + if (!character[j+5].eaten) { // If it has not already been eaten, do the following... + for (int i = 0; i < numGhosts + 1; i++) // Reverse direction for all mobile characters + character[i].direction = false; // false = PacMan chasing ghosts + + for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... + character[i].color = BLUE; // change their color to blue + character[i].blue = true; // ghosts are now blue, so set to true + } + character[j+5].eaten = true; // powerdot was eaten, so set to true + } + } + } + + // when the ghosts are blue and PacMan gets to the beginning of the segment... + if (character[1].blue && (character[PACMAN].pos <= 0)) { + for (int i = 0; i < numGhosts + 1; i++) // reverse direction for all mobile characters (back to initial direction) + character[i].direction = true; // true = ghosts chasing PacMan + + character[1].color = RED; // change ghost 1 color back to red + character[2].color = PURPLEISH; // was PURPLE // change ghost 2 color back to purple + character[3].color = CYAN; // change ghost 3 color back to cyan + character[4].color = ORANGEISH; // was ORANGE // change ghost 4 color back to orange + + for (int i = 1; i < numGhosts + 1; i++) // For all 4 ghosts... + character[i].blue = false; // ghosts are not blue anymore, so set to false + + if (character[5].eaten) { // if the last power dot was eaten (and we are at the beginning of the segment) + for (int i = 0; i < numPowerDots; i++) { + character[i+5].eaten = false; + character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment) + } + } + } + + // display the characters + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts. Is it time to update their position? + character[PACMAN].pos += character[PACMAN].direction?1:-1; // Yes, it's time to update PacMan's position (forwards or backwards) + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan + + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts + character[i].pos += character[PACMAN].direction?1:-1; // update their positions (forwards or backwards) + SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw the ghosts in new positions + } + } + else { // No, it's NOT time to update the characters' positions yet + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan in same position + + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts + SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw ghosts in same positions + } + } + + if (character[PACMAN].topPos < character[PACMAN].pos) // keep track of the top (farthest) position of the PacMan character + character[PACMAN].topPos = character[PACMAN].pos; + + return FRAMETIME; +} +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,,,,White dots,Compact dots,;;!;1;m12=0,o1=1"; + + /* * Sinelon stolen from FASTLED examples */ @@ -10776,6 +10977,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); + addEffect(FX_MODE_PACMAN, &mode_pacman, _data_FX_MODE_PACMAN); // --- 1D audio effects --- addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS); diff --git a/wled00/FX.h b/wled00/FX.h index 097c857caf..57e4a91e3b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -307,6 +307,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_2DFIRENOISE 149 #define FX_MODE_2DSQUAREDSWIRL 150 // #define FX_MODE_2DFIRE2012 151 +#define FX_MODE_PACMAN 151 // gap fill - not SR #define FX_MODE_2DDNA 152 #define FX_MODE_2DMATRIX 153 #define FX_MODE_2DMETABALLS 154 From d6e8623d223a068d190b072a319b3b5e919f4346 Mon Sep 17 00:00:00 2001 From: "DESKTOP-BOB-DEL\\boblo" Date: Sat, 30 Aug 2025 20:31:10 -0700 Subject: [PATCH 02/20] A few modifications to PacMan suggested by CodeRabbit --- wled00/FX.cpp | 16 ++++++++++------ wled00/FX.h | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index cb3ad1c97e..7163b6079d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3332,19 +3332,23 @@ static uint16_t mode_pacman(void) { // display the characters if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts. Is it time to update their position? - character[PACMAN].pos += character[PACMAN].direction?1:-1; // Yes, it's time to update PacMan's position (forwards or backwards) - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan + character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; // Yes, it's time to update PacMan's position (forwards or backwards) + if ((unsigned)character[PACMAN].pos >= 0 && (unsigned)character[PACMAN].pos < SEGLEN) + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts - character[i].pos += character[PACMAN].direction?1:-1; // update their positions (forwards or backwards) - SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw the ghosts in new positions + character[i].pos += character[i].direction ? 1 : -1; // update their positions (forwards or backwards) + if ((unsigned)character[i].pos >= 0 && (unsigned)character[i].pos < SEGLEN) + SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw the ghosts in new positions } } else { // No, it's NOT time to update the characters' positions yet - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan in same position + if ((unsigned)character[PACMAN].pos >= 0 && (unsigned)character[PACMAN].pos < SEGLEN) + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan in same position for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts - SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw ghosts in same positions + if ((unsigned)character[i].pos >= 0 && (unsigned)character[i].pos < SEGLEN) + SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw ghosts in same positions } } diff --git a/wled00/FX.h b/wled00/FX.h index 57e4a91e3b..7059f8b7aa 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -307,7 +307,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_2DFIRENOISE 149 #define FX_MODE_2DSQUAREDSWIRL 150 // #define FX_MODE_2DFIRE2012 151 -#define FX_MODE_PACMAN 151 // gap fill - not SR +#define FX_MODE_PACMAN 151 // gap fill (non-SR). Do NOT renumber; SR-ID range must remain stable. #define FX_MODE_2DDNA 152 #define FX_MODE_2DMATRIX 153 #define FX_MODE_2DMETABALLS 154 From fc72aa72bdde1199167aa9c21a0d54d1b9ccef83 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 30 Aug 2025 21:13:37 -0700 Subject: [PATCH 03/20] A few more suggestions by CodeRabbit --- wled00/FX.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 7163b6079d..97b0f991c2 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3247,11 +3247,13 @@ static uint16_t mode_pacman(void) { // draw white dots (or black LEDs) so PacMan can start eating them if (SEGMENT.check1) { // If White Dots option is selected, draw white dots in front of PacMan - for (int i = SEGLEN-1; i >= character[PACMAN].topPos+1; i--) { + for (int i = SEGLEN-1; i >= character[PACMAN].topPos + 1; i--) { SEGMENT.setPixelColor(i, WHITEISH); // white dots if (!SEGMENT.check2) { // If Compact Dots option is NOT selected, draw black LEDs between the white dots (only works if White Dots is also selected) - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot - i--; // skip the black LED before drawing the next white dot + if (i > 0) { + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip the black LED before drawing the next white dot + } } } } @@ -3289,8 +3291,10 @@ static uint16_t mode_pacman(void) { // now draw the power dots in the segment only if they have not been eaten yet for (int i = 0; i < numPowerDots; i++) { - if (!character[i+5].eaten) - SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); + if (!character[i+5].eaten) { + if ((unsigned)character[i+5].pos < SEGLEN) + SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); + } } // PacMan ate one of the power dots! Chase the ghosts! @@ -3333,21 +3337,21 @@ static uint16_t mode_pacman(void) { // display the characters if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts. Is it time to update their position? character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; // Yes, it's time to update PacMan's position (forwards or backwards) - if ((unsigned)character[PACMAN].pos >= 0 && (unsigned)character[PACMAN].pos < SEGLEN) + if ((unsigned)character[PACMAN].pos < SEGLEN) SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts character[i].pos += character[i].direction ? 1 : -1; // update their positions (forwards or backwards) - if ((unsigned)character[i].pos >= 0 && (unsigned)character[i].pos < SEGLEN) + if ((unsigned)character[i].pos < SEGLEN) SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw the ghosts in new positions } } else { // No, it's NOT time to update the characters' positions yet - if ((unsigned)character[PACMAN].pos >= 0 && (unsigned)character[PACMAN].pos < SEGLEN) + if ((unsigned)character[PACMAN].pos < SEGLEN) SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan in same position for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts - if ((unsigned)character[i].pos >= 0 && (unsigned)character[i].pos < SEGLEN) + if ((unsigned)character[i].pos < SEGLEN) SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw ghosts in same positions } } From 01c7ea5bcc34b644440753f2f05106a6b65e090f Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 27 Oct 2025 22:05:16 -0700 Subject: [PATCH 04/20] Comments cleanup and StartBlinkingGhostsLED change --- wled00/FX.cpp | 187 +++++++++++++++++++++++--------------------------- 1 file changed, 87 insertions(+), 100 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 97b0f991c2..59ffcc2ae5 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3157,121 +3157,108 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b /* -/ Pac-Man (created by making modifications to the Ants effect which was a -* modification of the Rolling Balls effect) - Bob Loeffler -* -* The first slider is for speed. -* The second slider is for selecting the number of power dots. -* Checkbox1 is for displaying White Dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black). -* Checkbox2 is for the Compact Dots mode of displaying white dots. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. - aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the second slider. -* aux1 is the main counter for timing +/ Pac-Man by Bob Loeffler +* speed slider is for speed. +* intensity slider is for selecting the number of power dots. +* check1 is for displaying White Dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black). +* check2 is for the Compact Dots mode of displaying white dots. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. +* aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the intensity slider. +* aux1 is the main counter for timing. */ typedef struct PacManChars { - signed pos; // is for the LED position of the character (all characters) - signed topPos; // is for the LED position of the farthest that the character has moved (PacMan only) - uint32_t color; // is for the color of the character (all characters) - bool direction; // is for the direction of the character (true=away from first LED) (PacMan and ghosts) - bool blue; // is for whether the character should be blue color or not (ghosts only) - bool eaten; // is for whether the power dot was eaten or not (power dots only) + signed pos; + signed topPos; // the LED position of the farthest that the character has moved (PacMan only) + uint32_t color; + bool direction; // true = moving away from first LED, so ghosts chasing PacMan (used for PacMan and ghosts) + bool blue; // used for ghosts only + bool eaten; // used for power dots only } pacmancharacters_t; static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; - constexpr unsigned ORANGEYELLOW = 0xFFcc00; // was FF8800 but that was too dark-orange after gamma correction was fixed - constexpr unsigned PURPLEISH = 0x900090; // created after gamma correction was fixed - constexpr unsigned ORANGEISH = 0xFF8800; // created after gamma correction was fixed + constexpr unsigned ORANGEYELLOW = 0xFFCC00; + constexpr unsigned PURPLEISH = 0xB000B0; + constexpr unsigned ORANGEISH = 0xFF8800; constexpr unsigned WHITEISH = 0x999999; - constexpr unsigned PACMAN = 0; // PacMan is character[0] - unsigned maxPowerDots = SEGLEN / 10; // Maximum number of power dots depends on segment length, max is 1 every 10 Pixels - unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); // number of Power Dots (between 1 and x) based on intensity slider setting + constexpr unsigned PACMAN = 0; // PacMan is character[0] + const unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 Pixels + unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); - if (numPowerDots != SEGENV.aux0) // if the user selected a different number of power dots, reinitialize the animation. + if (numPowerDots != SEGENV.aux0) // if the user selected a different number of power dots, reinitialize the animation. SEGENV.call = 0; SEGENV.aux0 = numPowerDots; //allocate segment data - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + maxPowerDots); // 4 ghosts + 1 PacMan + max number of Power dots - if (SEGLEN <= 20 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed or segment length is too short to have a nice display + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character + if (SEGLEN <= 20 || !SEGENV.allocateData(dataSize)) return mode_static(); pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - unsigned startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking - if (SEGLEN > 150) - startBlinkingGhostsLED = SEGLEN/4; // For longer strips, start blinking the ghosts when there is only 1/4th of the LEDs left + int startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking + if (SEGLEN < 64) + startBlinkingGhostsLED = (int)SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left else - startBlinkingGhostsLED = SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left - + startBlinkingGhostsLED = map(SEGMENT.custom1, 0, 255, 20, SEGLEN/2); // allow the user to select where the ghosts should start blinking + if (SEGENV.call == 0) { for (int i = 0; i < 5; i++) { - character[i].direction = true; // initial direction of movement. true = ghosts chasing PacMan + character[i].direction = true; character[i].blue = false; } // PacMan character[0] character[PACMAN].color = YELLOW; - character[PACMAN].pos = 10; // initial LED position - character[PACMAN].topPos = character[PACMAN].pos; // Top position (highest LED on the segment) reached by the PacMan character - - // Ghost character - character[1].color = RED; // turns blue when the power dot is eaten; blinks just before it turns back to normal color - character[1].pos = 6; // initial LED position - - // Ghost character - character[2].color = PURPLEISH; // was PURPLE // turns blue when the power dot is eaten; blinks just before it turns back to normal color - character[2].pos = 4; // initial LED position - - // Ghost character - character[3].color = CYAN; // turns blue when the power dot is eaten; blinks just before it turns back to normal color - character[3].pos = 2; // initial LED position - - // Ghost character - character[4].color = ORANGEISH; // was ORANGE // turns blue when the power dot is eaten; blinks just before it turns back to normal color - character[4].pos = 0; // initial LED position - - // Power dot at end of Segment - character[5].pos = SEGLEN-1; // put the first power dot at the end of the segment - - // Power dots, position is set dynamically below + character[PACMAN].pos = 10; + character[PACMAN].topPos = character[PACMAN].pos; + // Ghost characters; turn blue in power dot mode + character[1].color = RED; + character[1].pos = 6; + character[2].color = PURPLEISH; + character[2].pos = 4; + character[3].color = CYAN; + character[3].pos = 2; + character[4].color = ORANGEISH; + character[4].pos = 0; + // Power dot at end of segment + character[5].pos = SEGLEN-1; + // Other power dots; position is set dynamically below for (int i = 0; i < maxPowerDots; i++) { - character[i+5].color = ORANGEYELLOW; // orangeish yellow power dots - character[i+5].eaten = false; // initially not eaten yet, so set this to false + character[i+5].color = ORANGEYELLOW; + character[i+5].eaten = false; } } if (strip.now > SEGENV.step) { - SEGENV.step = strip.now; // "+ 100" creates a very jerky movement as the characters jump ahead several pixels each time they move + SEGENV.step = strip.now; SEGENV.aux1++; } - // fill all LEDs/pixels with black (off) SEGMENT.fill(BLACK); - - // draw white dots (or black LEDs) so PacMan can start eating them - if (SEGMENT.check1) { // If White Dots option is selected, draw white dots in front of PacMan - for (int i = SEGLEN-1; i >= character[PACMAN].topPos + 1; i--) { - SEGMENT.setPixelColor(i, WHITEISH); // white dots - if (!SEGMENT.check2) { // If Compact Dots option is NOT selected, draw black LEDs between the white dots (only works if White Dots is also selected) + // if White Dots option is selected, draw white dots in front of PacMan + if (SEGMENT.check1) { + for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { + SEGMENT.setPixelColor(i, WHITEISH); + if (!SEGMENT.check2) { // If Compact Dots option is NOT selected, draw black LEDs between the white dots (only works if White Dots is also selected) if (i > 0) { - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot - i--; // skip the black LED before drawing the next white dot + SEGMENT.setPixelColor(i-1, BLACK); + i--; // skip the black LED before drawing the next white dot } } } } // update power dot positions: can change if user selects a different number of power dots - unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; //figure out how far apart the power dots will be + unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; // figure out how far apart the power dots will be for (int i = 1; i < maxPowerDots; i++) { - character[i+5].pos = 10 + ((i * everyXLeds) >> 8); // additional power dots every X LEDs/pixels, character[5] is power dot at end of strip + character[i+5].pos = 10 + ((i * everyXLeds) >> 8); // additional power dots every X pixels, character[5] is power dot at end of strip } - // blink power dots every 10 ticks of the ticker timer by changing their color between orangish color and black + // blink power dots every 10 ticks of the ticker timer by changing their color between orangish-yellow color and black if (SEGENV.aux1 % 10 == 0) { if (character[5].color == ORANGEYELLOW) character[5].color = BLACK; else character[5].color = ORANGEYELLOW; for (int i = 1; i < maxPowerDots; i++) { - character[i+5].color = character[5].color; // blink in sync with the last power dot + character[i+5].color = character[5].color; // blink in sync with the last power dot } } @@ -3279,12 +3266,12 @@ static uint16_t mode_pacman(void) { if (SEGENV.aux1 % 15 == 0) { if (character[1].blue && (character[PACMAN].pos <= startBlinkingGhostsLED)) { if (character[1].color == BLUE) - character[1].color = BLACK; + character[1].color = WHITEISH; else character[1].color = BLUE; for (int i = 1; i < numGhosts; i++) { - character[i+1].color = character[1].color; // blink in sync with the first ghost + character[i+1].color = character[1].color; // blink in sync with the first ghost } } } @@ -3300,68 +3287,68 @@ static uint16_t mode_pacman(void) { // PacMan ate one of the power dots! Chase the ghosts! for (int j = 0; j < numPowerDots; j++) { if ((character[PACMAN].pos >= character[j+5].pos)) { - if (!character[j+5].eaten) { // If it has not already been eaten, do the following... - for (int i = 0; i < numGhosts + 1; i++) // Reverse direction for all mobile characters - character[i].direction = false; // false = PacMan chasing ghosts + if (!character[j+5].eaten) { // If a power dot has not already been eaten... + for (int i = 0; i < numGhosts + 1; i++) // Reverse direction for all moving characters + character[i].direction = false; // false = PacMan chasing ghosts toward beginning of segment - for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... - character[i].color = BLUE; // change their color to blue - character[i].blue = true; // ghosts are now blue, so set to true + for (int i = 1; i < numGhosts + 1; i++) { + character[i].color = BLUE; + character[i].blue = true; } - character[j+5].eaten = true; // powerdot was eaten, so set to true + character[j+5].eaten = true; } } } // when the ghosts are blue and PacMan gets to the beginning of the segment... if (character[1].blue && (character[PACMAN].pos <= 0)) { - for (int i = 0; i < numGhosts + 1; i++) // reverse direction for all mobile characters (back to initial direction) - character[i].direction = true; // true = ghosts chasing PacMan + for (int i = 0; i < numGhosts + 1; i++) // reverse direction for all moving characters (back to initial direction) + character[i].direction = true; // true = ghosts chasing PacMan - character[1].color = RED; // change ghost 1 color back to red - character[2].color = PURPLEISH; // was PURPLE // change ghost 2 color back to purple - character[3].color = CYAN; // change ghost 3 color back to cyan - character[4].color = ORANGEISH; // was ORANGE // change ghost 4 color back to orange + character[1].color = RED; + character[2].color = PURPLEISH; + character[3].color = CYAN; + character[4].color = ORANGEISH; - for (int i = 1; i < numGhosts + 1; i++) // For all 4 ghosts... - character[i].blue = false; // ghosts are not blue anymore, so set to false + for (int i = 1; i < numGhosts + 1; i++) + character[i].blue = false; - if (character[5].eaten) { // if the last power dot was eaten (and we are at the beginning of the segment) + if (character[5].eaten) { // if the last power dot was eaten (and we are at the beginning of the segment) for (int i = 0; i < numPowerDots; i++) { character[i+5].eaten = false; - character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment) + character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment) } } } // display the characters - if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts. Is it time to update their position? - character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; // Yes, it's time to update PacMan's position (forwards or backwards) + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts. Is it time to update their position? + character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; // Yes, it's time to update PacMan's position (forwards or backwards) if ((unsigned)character[PACMAN].pos < SEGLEN) - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); - for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts - character[i].pos += character[i].direction ? 1 : -1; // update their positions (forwards or backwards) + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the ghosts in their updated positions (forwards or backwards) + character[i].pos += character[i].direction ? 1 : -1; if ((unsigned)character[i].pos < SEGLEN) - SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw the ghosts in new positions + SEGMENT.setPixelColor(character[i].pos, character[i].color); } } - else { // No, it's NOT time to update the characters' positions yet + else { // No, it's not time to update the characters' positions yet if ((unsigned)character[PACMAN].pos < SEGLEN) - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan in same position + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan in same position - for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the ghosts in the same positions if ((unsigned)character[i].pos < SEGLEN) - SEGMENT.setPixelColor(character[i].pos, character[i].color); // draw ghosts in same positions + SEGMENT.setPixelColor(character[i].pos, character[i].color); } } - if (character[PACMAN].topPos < character[PACMAN].pos) // keep track of the top (farthest) position of the PacMan character + if (character[PACMAN].topPos < character[PACMAN].pos) // keep track of the top (farthest) position of the PacMan character character[PACMAN].topPos = character[PACMAN].pos; return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,,,,White dots,Compact dots,;;!;1;m12=0,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,,White Dots,Compact Dots,;;!;1;m12=0,o1=1"; /* From eadc874fd70dbd1b5c3fe95e16d2fc68ca86140c Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Fri, 31 Oct 2025 07:44:07 -0700 Subject: [PATCH 05/20] Change to startBlinkingGhostsLED --- wled00/FX.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b7880ac2a5..7b59c20357 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3160,9 +3160,10 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b /* -/ Pac-Man by Bob Loeffler +/ Pac-Man by Bob Loeffler with help from @dedehai * speed slider is for speed. * intensity slider is for selecting the number of power dots. +* custom1 slider is for selecting the LED where the ghosts will start blinking blue. * check1 is for displaying White Dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black). * check2 is for the Compact Dots mode of displaying white dots. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. * aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the intensity slider. @@ -3200,8 +3201,8 @@ static uint16_t mode_pacman(void) { if (SEGLEN < 64) startBlinkingGhostsLED = (int)SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left else - startBlinkingGhostsLED = map(SEGMENT.custom1, 0, 255, 20, SEGLEN/2); // allow the user to select where the ghosts should start blinking - + startBlinkingGhostsLED = map(SEGMENT.custom1, 0, 255, 20, character[PACMAN].topPos); // allow the user to select where the ghosts should start blinking relative to the last powerdot eaten + if (SEGENV.call == 0) { for (int i = 0; i < 5; i++) { character[i].direction = true; @@ -3351,7 +3352,7 @@ static uint16_t mode_pacman(void) { return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,,White Dots,Compact Dots,;;!;1;m12=0,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,,White Dots,Compact Dots,;;!;1;m12=0,sx=192,ix=64,c1=64,o1=1"; /* From 42256f33b6d94ebeeeb5c501475fdf5319c10e16 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 22 Nov 2025 21:41:01 -0700 Subject: [PATCH 06/20] User can now select number of ghosts (between 2 and 8) --- wled00/FX.cpp | 111 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 38 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f3a4b895d3..45afa83374 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3160,10 +3160,11 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b /* -/ Pac-Man by Bob Loeffler with help from @dedehai +/ Pac-Man by Bob Loeffler with help from @dedehai and @blazoncek * speed slider is for speed. * intensity slider is for selecting the number of power dots. * custom1 slider is for selecting the LED where the ghosts will start blinking blue. +* custom3 slider is for selecting the # of ghosts (between 2 and 8). * check1 is for displaying White Dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black). * check2 is for the Compact Dots mode of displaying white dots. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. * aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the intensity slider. @@ -3179,7 +3180,6 @@ typedef struct PacManChars { } pacmancharacters_t; static uint16_t mode_pacman(void) { - constexpr unsigned numGhosts = 4; constexpr unsigned ORANGEYELLOW = 0xFFCC00; constexpr unsigned PURPLEISH = 0xB000B0; constexpr unsigned ORANGEISH = 0xFF8800; @@ -3187,46 +3187,77 @@ static uint16_t mode_pacman(void) { constexpr unsigned PACMAN = 0; // PacMan is character[0] const unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 Pixels unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); - - if (numPowerDots != SEGENV.aux0) // if the user selected a different number of power dots, reinitialize the animation. + unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8); + + // these are for packing two variable values into one unsigned int (SEGENV.aux0) + unsigned char c_numPowerDots = numPowerDots; + unsigned char c_numGhosts = numGhosts; + unsigned short combined_value = (c_numPowerDots << 8) | c_numGhosts; + + if (combined_value != SEGENV.aux0) // if the user selected a different number of power dots or ghosts, reinitialize the animation. SEGENV.call = 0; - SEGENV.aux0 = numPowerDots; + SEGENV.aux0 = combined_value; //allocate segment data unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character - if (SEGLEN <= 20 || !SEGENV.allocateData(dataSize)) return mode_static(); + if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static(); pacmancharacters_t *character = reinterpret_cast(SEGENV.data); int startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking if (SEGLEN < 64) startBlinkingGhostsLED = (int)SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left else - startBlinkingGhostsLED = map(SEGMENT.custom1, 0, 255, 20, character[PACMAN].topPos); // allow the user to select where the ghosts should start blinking relative to the last powerdot eaten + startBlinkingGhostsLED = map(SEGMENT.custom1, 0, 255, 20, character[PACMAN].topPos); // allow the user to select where the ghosts should start blinking relative to the last powerdot eaten if (SEGENV.call == 0) { - for (int i = 0; i < 5; i++) { + for (int i = 0; i < numGhosts+1; i++) { character[i].direction = true; character[i].blue = false; } // PacMan character[0] character[PACMAN].color = YELLOW; - character[PACMAN].pos = 10; + character[PACMAN].pos = 0; character[PACMAN].topPos = character[PACMAN].pos; + // Ghost characters; turn blue in power dot mode character[1].color = RED; - character[1].pos = 6; + character[1].pos = -4; + character[2].color = PURPLEISH; - character[2].pos = 4; - character[3].color = CYAN; - character[3].pos = 2; - character[4].color = ORANGEISH; - character[4].pos = 0; + character[2].pos = -6; + + if (numGhosts > 2) { + character[3].color = CYAN; + character[3].pos = -8; + } + if (numGhosts > 3) { + character[4].color = ORANGEISH; + character[4].pos = -10; + } + if (numGhosts > 4) { + character[5].color = RED; + character[5].pos = -12; + } + if (numGhosts > 5) { + character[6].color = PURPLEISH; + character[6].pos = -14; + } + if (numGhosts > 6) { + character[7].color = CYAN; + character[7].pos = -16; + } + if (numGhosts > 7) { + character[8].color = ORANGEISH; + character[8].pos = -18; + } + // Power dot at end of segment - character[5].pos = SEGLEN-1; + character[numGhosts+1].pos = SEGLEN-1; + // Other power dots; position is set dynamically below for (int i = 0; i < maxPowerDots; i++) { - character[i+5].color = ORANGEYELLOW; - character[i+5].eaten = false; + character[i+numGhosts+1].color = ORANGEYELLOW; + character[i+numGhosts+1].eaten = false; } } @@ -3252,17 +3283,17 @@ static uint16_t mode_pacman(void) { // update power dot positions: can change if user selects a different number of power dots unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; // figure out how far apart the power dots will be for (int i = 1; i < maxPowerDots; i++) { - character[i+5].pos = 10 + ((i * everyXLeds) >> 8); // additional power dots every X pixels, character[5] is power dot at end of strip + character[i+numGhosts+1].pos = 10 + ((i * everyXLeds) >> 8); // additional power dots every X pixels, character[5] is power dot at end of strip } // blink power dots every 10 ticks of the ticker timer by changing their color between orangish-yellow color and black if (SEGENV.aux1 % 10 == 0) { - if (character[5].color == ORANGEYELLOW) - character[5].color = BLACK; - else - character[5].color = ORANGEYELLOW; + if (character[numGhosts+1].color == ORANGEYELLOW) + character[numGhosts+1].color = BLACK; + else + character[numGhosts+1].color = ORANGEYELLOW; for (int i = 1; i < maxPowerDots; i++) { - character[i+5].color = character[5].color; // blink in sync with the last power dot + character[i+numGhosts+1].color = character[numGhosts+1].color; // blink in sync with the last power dot } } @@ -3282,16 +3313,16 @@ static uint16_t mode_pacman(void) { // now draw the power dots in the segment only if they have not been eaten yet for (int i = 0; i < numPowerDots; i++) { - if (!character[i+5].eaten) { - if ((unsigned)character[i+5].pos < SEGLEN) - SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); + if (!character[i+numGhosts+1].eaten) { + if ((unsigned)character[i+numGhosts+1].pos < SEGLEN) + SEGMENT.setPixelColor(character[i+numGhosts+1].pos, character[i+numGhosts+1].color); } } // PacMan ate one of the power dots! Chase the ghosts! for (int j = 0; j < numPowerDots; j++) { - if ((character[PACMAN].pos >= character[j+5].pos)) { - if (!character[j+5].eaten) { // If a power dot has not already been eaten... + if ((character[PACMAN].pos >= character[j+numGhosts+1].pos)) { + if (!character[j+numGhosts+1].eaten) { // If a power dot has not already been eaten... for (int i = 0; i < numGhosts + 1; i++) // Reverse direction for all moving characters character[i].direction = false; // false = PacMan chasing ghosts toward beginning of segment @@ -3299,7 +3330,7 @@ static uint16_t mode_pacman(void) { character[i].color = BLUE; character[i].blue = true; } - character[j+5].eaten = true; + character[j+numGhosts+1].eaten = true; } } } @@ -3311,15 +3342,19 @@ static uint16_t mode_pacman(void) { character[1].color = RED; character[2].color = PURPLEISH; - character[3].color = CYAN; - character[4].color = ORANGEISH; + if (numGhosts > 2) character[3].color = CYAN; + if (numGhosts > 3) character[4].color = ORANGEISH; + if (numGhosts > 4) character[5].color = RED; + if (numGhosts > 5) character[6].color = PURPLEISH; + if (numGhosts > 6) character[7].color = CYAN; + if (numGhosts > 7) character[8].color = ORANGEISH; for (int i = 1; i < numGhosts + 1; i++) character[i].blue = false; - if (character[5].eaten) { // if the last power dot was eaten (and we are at the beginning of the segment) + if (character[numGhosts+1].eaten) { // if the last power dot was eaten (and we are at the beginning of the segment) for (int i = 0; i < numPowerDots; i++) { - character[i+5].eaten = false; + character[i+numGhosts+1].eaten = false; character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment) } } @@ -3333,7 +3368,7 @@ static uint16_t mode_pacman(void) { for (int i = 1; i < numGhosts + 1; i++) { // ...draw the ghosts in their updated positions (forwards or backwards) character[i].pos += character[i].direction ? 1 : -1; - if ((unsigned)character[i].pos < SEGLEN) + if ((unsigned)character[i].pos < SEGLEN && (unsigned)character[i].pos >= 0) SEGMENT.setPixelColor(character[i].pos, character[i].color); } } @@ -3342,8 +3377,8 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan in same position for (int i = 1; i < numGhosts + 1; i++) { // ...draw the ghosts in the same positions - if ((unsigned)character[i].pos < SEGLEN) - SEGMENT.setPixelColor(character[i].pos, character[i].color); + if ((unsigned)character[i].pos < SEGLEN && (unsigned)character[i].pos >= 0) + SEGMENT.setPixelColor(character[i].pos, character[i].color); } } @@ -3352,7 +3387,7 @@ static uint16_t mode_pacman(void) { return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,,White Dots,Compact Dots,;;!;1;m12=0,sx=192,ix=64,c1=64,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,# of Ghosts,White Dots,Compact Dots,;;!;1;m12=0,sx=192,ix=64,c1=64,c3=12,o1=1"; /* From 223df3b4bfe079e988e6620e43f56d45ed2aaefc Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Nov 2025 08:55:33 -0700 Subject: [PATCH 07/20] PacMan code cleanup by claude.ai --- wled00/FX.cpp | 277 +++++++++++++++++++++----------------------------- 1 file changed, 117 insertions(+), 160 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 45afa83374..13e39f2935 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3170,13 +3170,14 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the intensity slider. * aux1 is the main counter for timing. */ + typedef struct PacManChars { signed pos; - signed topPos; // the LED position of the farthest that the character has moved (PacMan only) + signed topPos; // LED position of farthest PacMan has moved uint32_t color; - bool direction; // true = moving away from first LED, so ghosts chasing PacMan (used for PacMan and ghosts) - bool blue; // used for ghosts only - bool eaten; // used for power dots only + bool direction; // true = moving away from first LED + bool blue; // used for ghosts only + bool eaten; // used for power dots only } pacmancharacters_t; static uint16_t mode_pacman(void) { @@ -3184,207 +3185,163 @@ static uint16_t mode_pacman(void) { constexpr unsigned PURPLEISH = 0xB000B0; constexpr unsigned ORANGEISH = 0xFF8800; constexpr unsigned WHITEISH = 0x999999; - constexpr unsigned PACMAN = 0; // PacMan is character[0] - const unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 Pixels + constexpr unsigned PACMAN = 0; + + const unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 pixels unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8); - - // these are for packing two variable values into one unsigned int (SEGENV.aux0) - unsigned char c_numPowerDots = numPowerDots; - unsigned char c_numGhosts = numGhosts; - unsigned short combined_value = (c_numPowerDots << 8) | c_numGhosts; - - if (combined_value != SEGENV.aux0) // if the user selected a different number of power dots or ghosts, reinitialize the animation. - SEGENV.call = 0; + + // Pack two values into one unsigned int (SEGENV.aux0) + unsigned short combined_value = (numPowerDots << 8) | numGhosts; + if (combined_value != SEGENV.aux0) SEGENV.call = 0; // Reinitialize on setting change SEGENV.aux0 = combined_value; - - //allocate segment data - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character - if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static(); + + // Allocate segment data + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); + if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static(); pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - - int startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking - if (SEGLEN < 64) - startBlinkingGhostsLED = (int)SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left - else - startBlinkingGhostsLED = map(SEGMENT.custom1, 0, 255, 20, character[PACMAN].topPos); // allow the user to select where the ghosts should start blinking relative to the last powerdot eaten - + + // Calculate when blue ghosts start blinking + int startBlinkingGhostsLED = (SEGLEN < 64) + ? (int)SEGLEN/3 + : map(SEGMENT.custom1, 0, 255, 20, character[PACMAN].topPos); + + // Initialize characters on first call if (SEGENV.call == 0) { - for (int i = 0; i < numGhosts+1; i++) { - character[i].direction = true; - character[i].blue = false; - } - // PacMan character[0] + // Initialize PacMan character[PACMAN].color = YELLOW; character[PACMAN].pos = 0; - character[PACMAN].topPos = character[PACMAN].pos; - - // Ghost characters; turn blue in power dot mode - character[1].color = RED; - character[1].pos = -4; - - character[2].color = PURPLEISH; - character[2].pos = -6; - - if (numGhosts > 2) { - character[3].color = CYAN; - character[3].pos = -8; - } - if (numGhosts > 3) { - character[4].color = ORANGEISH; - character[4].pos = -10; - } - if (numGhosts > 4) { - character[5].color = RED; - character[5].pos = -12; - } - if (numGhosts > 5) { - character[6].color = PURPLEISH; - character[6].pos = -14; - } - if (numGhosts > 6) { - character[7].color = CYAN; - character[7].pos = -16; - } - if (numGhosts > 7) { - character[8].color = ORANGEISH; - character[8].pos = -18; + character[PACMAN].topPos = 0; + character[PACMAN].direction = true; + character[PACMAN].blue = false; + + // Initialize ghosts with alternating colors + const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; + for (int i = 1; i <= numGhosts; i++) { + character[i].color = ghostColors[(i-1) % 4]; + character[i].pos = -2 * (i + 1); + character[i].direction = true; + character[i].blue = false; } - - // Power dot at end of segment - character[numGhosts+1].pos = SEGLEN-1; - - // Other power dots; position is set dynamically below + + // Initialize power dots for (int i = 0; i < maxPowerDots; i++) { - character[i+numGhosts+1].color = ORANGEYELLOW; - character[i+numGhosts+1].eaten = false; + character[i + numGhosts + 1].color = ORANGEYELLOW; + character[i + numGhosts + 1].eaten = false; } + character[numGhosts + 1].pos = SEGLEN - 1; // Last power dot at end } - + if (strip.now > SEGENV.step) { SEGENV.step = strip.now; SEGENV.aux1++; } - + SEGMENT.fill(BLACK); - // if White Dots option is selected, draw white dots in front of PacMan + + // Draw white dots in front of PacMan if option selected if (SEGMENT.check1) { - for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { + int step = SEGMENT.check2 ? 1 : 2; // Compact or spaced dots + for (int i = SEGLEN - 1; i > character[PACMAN].topPos; i -= step) { SEGMENT.setPixelColor(i, WHITEISH); - if (!SEGMENT.check2) { // If Compact Dots option is NOT selected, draw black LEDs between the white dots (only works if White Dots is also selected) - if (i > 0) { - SEGMENT.setPixelColor(i-1, BLACK); - i--; // skip the black LED before drawing the next white dot - } - } } } - - // update power dot positions: can change if user selects a different number of power dots - unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; // figure out how far apart the power dots will be + + // Update power dot positions dynamically + unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; for (int i = 1; i < maxPowerDots; i++) { - character[i+numGhosts+1].pos = 10 + ((i * everyXLeds) >> 8); // additional power dots every X pixels, character[5] is power dot at end of strip + character[i + numGhosts + 1].pos = 10 + ((i * everyXLeds) >> 8); } - - // blink power dots every 10 ticks of the ticker timer by changing their color between orangish-yellow color and black + + // Blink power dots every 10 ticks if (SEGENV.aux1 % 10 == 0) { - if (character[numGhosts+1].color == ORANGEYELLOW) - character[numGhosts+1].color = BLACK; - else - character[numGhosts+1].color = ORANGEYELLOW; - for (int i = 1; i < maxPowerDots; i++) { - character[i+numGhosts+1].color = character[numGhosts+1].color; // blink in sync with the last power dot + uint32_t dotColor = (character[numGhosts + 1].color == ORANGEYELLOW) ? BLACK : ORANGEYELLOW; + for (int i = 0; i < maxPowerDots; i++) { + character[i + numGhosts + 1].color = dotColor; } } - - // if the ghosts are blue and nearing the beginning of the strip, blink them every 15 ticks of the ticker timer by changing their color between blue and black - if (SEGENV.aux1 % 15 == 0) { - if (character[1].blue && (character[PACMAN].pos <= startBlinkingGhostsLED)) { - if (character[1].color == BLUE) - character[1].color = WHITEISH; - else - character[1].color = BLUE; - - for (int i = 1; i < numGhosts; i++) { - character[i+1].color = character[1].color; // blink in sync with the first ghost - } + + // Blink blue ghosts when nearing start + if (SEGENV.aux1 % 15 == 0 && character[1].blue && character[PACMAN].pos <= startBlinkingGhostsLED) { + uint32_t ghostColor = (character[1].color == BLUE) ? WHITEISH : BLUE; + for (int i = 1; i <= numGhosts; i++) { + character[i].color = ghostColor; } } - - // now draw the power dots in the segment only if they have not been eaten yet + + // Draw uneaten power dots for (int i = 0; i < numPowerDots; i++) { - if (!character[i+numGhosts+1].eaten) { - if ((unsigned)character[i+numGhosts+1].pos < SEGLEN) - SEGMENT.setPixelColor(character[i+numGhosts+1].pos, character[i+numGhosts+1].color); + if (!character[i + numGhosts + 1].eaten && (unsigned)character[i + numGhosts + 1].pos < SEGLEN) { + SEGMENT.setPixelColor(character[i + numGhosts + 1].pos, character[i + numGhosts + 1].color); } } - - // PacMan ate one of the power dots! Chase the ghosts! + + // Check if PacMan ate a power dot for (int j = 0; j < numPowerDots; j++) { - if ((character[PACMAN].pos >= character[j+numGhosts+1].pos)) { - if (!character[j+numGhosts+1].eaten) { // If a power dot has not already been eaten... - for (int i = 0; i < numGhosts + 1; i++) // Reverse direction for all moving characters - character[i].direction = false; // false = PacMan chasing ghosts toward beginning of segment - - for (int i = 1; i < numGhosts + 1; i++) { - character[i].color = BLUE; - character[i].blue = true; - } - character[j+numGhosts+1].eaten = true; + if (character[PACMAN].pos >= character[j + numGhosts + 1].pos && !character[j + numGhosts + 1].eaten) { + // Reverse all characters - PacMan now chases ghosts + for (int i = 0; i <= numGhosts; i++) { + character[i].direction = false; + } + // Turn ghosts blue + for (int i = 1; i <= numGhosts; i++) { + character[i].color = BLUE; + character[i].blue = true; } + character[j + numGhosts + 1].eaten = true; } } - - // when the ghosts are blue and PacMan gets to the beginning of the segment... - if (character[1].blue && (character[PACMAN].pos <= 0)) { - for (int i = 0; i < numGhosts + 1; i++) // reverse direction for all moving characters (back to initial direction) - character[i].direction = true; // true = ghosts chasing PacMan - - character[1].color = RED; - character[2].color = PURPLEISH; - if (numGhosts > 2) character[3].color = CYAN; - if (numGhosts > 3) character[4].color = ORANGEISH; - if (numGhosts > 4) character[5].color = RED; - if (numGhosts > 5) character[6].color = PURPLEISH; - if (numGhosts > 6) character[7].color = CYAN; - if (numGhosts > 7) character[8].color = ORANGEISH; - - for (int i = 1; i < numGhosts + 1; i++) + + // Reset when PacMan reaches start with blue ghosts + if (character[1].blue && character[PACMAN].pos <= 0) { + // Reverse direction back + for (int i = 0; i <= numGhosts; i++) { + character[i].direction = true; + } + + // Reset ghost colors + const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; + for (int i = 1; i <= numGhosts; i++) { + character[i].color = ghostColors[(i-1) % 4]; character[i].blue = false; - - if (character[numGhosts+1].eaten) { // if the last power dot was eaten (and we are at the beginning of the segment) + } + + // Reset power dots if last one was eaten + if (character[numGhosts + 1].eaten) { for (int i = 0; i < numPowerDots; i++) { - character[i+numGhosts+1].eaten = false; - character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment) + character[i + numGhosts + 1].eaten = false; } + character[PACMAN].topPos = 0; } } - - // display the characters - if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts. Is it time to update their position? - character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; // Yes, it's time to update PacMan's position (forwards or backwards) - if ((unsigned)character[PACMAN].pos < SEGLEN) - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); - - for (int i = 1; i < numGhosts + 1; i++) { // ...draw the ghosts in their updated positions (forwards or backwards) + + // Update and draw characters based on speed setting + bool updatePositions = (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0); + + if (updatePositions) { + character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; + for (int i = 1; i <= numGhosts; i++) { character[i].pos += character[i].direction ? 1 : -1; - if ((unsigned)character[i].pos < SEGLEN && (unsigned)character[i].pos >= 0) - SEGMENT.setPixelColor(character[i].pos, character[i].color); } } - else { // No, it's not time to update the characters' positions yet - if ((unsigned)character[PACMAN].pos < SEGLEN) - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan in same position - - for (int i = 1; i < numGhosts + 1; i++) { // ...draw the ghosts in the same positions - if ((unsigned)character[i].pos < SEGLEN && (unsigned)character[i].pos >= 0) + + // Draw PacMan + if ((unsigned)character[PACMAN].pos < SEGLEN) { + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); + } + + // Draw ghosts + for (int i = 1; i <= numGhosts; i++) { + if ((unsigned)character[i].pos < SEGLEN) { SEGMENT.setPixelColor(character[i].pos, character[i].color); } } - - if (character[PACMAN].topPos < character[PACMAN].pos) // keep track of the top (farthest) position of the PacMan character - character[PACMAN].topPos = character[PACMAN].pos; - + + // Track farthest position of PacMan + if (character[PACMAN].topPos < character[PACMAN].pos) { + character[PACMAN].topPos = character[PACMAN].pos; + } + return FRAMETIME; } static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,# of Ghosts,White Dots,Compact Dots,;;!;1;m12=0,sx=192,ix=64,c1=64,c3=12,o1=1"; From 89d8e05f951f5354fc575fe7ad46c7c7a39e0eec Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Nov 2025 10:27:16 -0700 Subject: [PATCH 08/20] Small change that was strongly recommended by CodeRabbitAI regarding PacMan topPos at beginning of code --- wled00/FX.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 13e39f2935..bd4f240099 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3201,11 +3201,14 @@ static uint16_t mode_pacman(void) { if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static(); pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - // Calculate when blue ghosts start blinking - int startBlinkingGhostsLED = (SEGLEN < 64) - ? (int)SEGLEN/3 - : map(SEGMENT.custom1, 0, 255, 20, character[PACMAN].topPos); - + // Calculate when blue ghosts start blinking. + // On first call (or after settings change), `topPos` is not known yet, so fall back to the full segment length in that case. + int maxBlinkPos = (SEGENV.call == 0) ? (int)SEGLEN - 1 : character[PACMAN].topPos; + if (maxBlinkPos < 20) maxBlinkPos = 20; + int startBlinkingGhostsLED = (SEGLEN < 64) + ? (int)SEGLEN / 3 + : map(SEGMENT.custom1, 0, 255, 20, maxBlinkPos); + // Initialize characters on first call if (SEGENV.call == 0) { // Initialize PacMan From 69dc9a6309e9351f25782d76804415c9f02ad908 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Nov 2025 15:25:42 -0700 Subject: [PATCH 09/20] PacMan: Removed extra whitespace and added back a couple comments that claude.ai removed --- wled00/FX.cpp | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index bd4f240099..d4a77692da 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3170,7 +3170,6 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the intensity slider. * aux1 is the main counter for timing. */ - typedef struct PacManChars { signed pos; signed topPos; // LED position of farthest PacMan has moved @@ -3185,22 +3184,22 @@ static uint16_t mode_pacman(void) { constexpr unsigned PURPLEISH = 0xB000B0; constexpr unsigned ORANGEISH = 0xFF8800; constexpr unsigned WHITEISH = 0x999999; - constexpr unsigned PACMAN = 0; - + constexpr unsigned PACMAN = 0; // PacMan is character[0] + const unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 pixels unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8); - + // Pack two values into one unsigned int (SEGENV.aux0) unsigned short combined_value = (numPowerDots << 8) | numGhosts; if (combined_value != SEGENV.aux0) SEGENV.call = 0; // Reinitialize on setting change SEGENV.aux0 = combined_value; - + // Allocate segment data - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static(); pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - + // Calculate when blue ghosts start blinking. // On first call (or after settings change), `topPos` is not known yet, so fall back to the full segment length in that case. int maxBlinkPos = (SEGENV.call == 0) ? (int)SEGLEN - 1 : character[PACMAN].topPos; @@ -3217,7 +3216,7 @@ static uint16_t mode_pacman(void) { character[PACMAN].topPos = 0; character[PACMAN].direction = true; character[PACMAN].blue = false; - + // Initialize ghosts with alternating colors const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; for (int i = 1; i <= numGhosts; i++) { @@ -3226,7 +3225,7 @@ static uint16_t mode_pacman(void) { character[i].direction = true; character[i].blue = false; } - + // Initialize power dots for (int i = 0; i < maxPowerDots; i++) { character[i + numGhosts + 1].color = ORANGEYELLOW; @@ -3234,14 +3233,14 @@ static uint16_t mode_pacman(void) { } character[numGhosts + 1].pos = SEGLEN - 1; // Last power dot at end } - + if (strip.now > SEGENV.step) { SEGENV.step = strip.now; SEGENV.aux1++; } - + SEGMENT.fill(BLACK); - + // Draw white dots in front of PacMan if option selected if (SEGMENT.check1) { int step = SEGMENT.check2 ? 1 : 2; // Compact or spaced dots @@ -3249,13 +3248,13 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(i, WHITEISH); } } - + // Update power dot positions dynamically unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; for (int i = 1; i < maxPowerDots; i++) { character[i + numGhosts + 1].pos = 10 + ((i * everyXLeds) >> 8); } - + // Blink power dots every 10 ticks if (SEGENV.aux1 % 10 == 0) { uint32_t dotColor = (character[numGhosts + 1].color == ORANGEYELLOW) ? BLACK : ORANGEYELLOW; @@ -3263,7 +3262,7 @@ static uint16_t mode_pacman(void) { character[i + numGhosts + 1].color = dotColor; } } - + // Blink blue ghosts when nearing start if (SEGENV.aux1 % 15 == 0 && character[1].blue && character[PACMAN].pos <= startBlinkingGhostsLED) { uint32_t ghostColor = (character[1].color == BLUE) ? WHITEISH : BLUE; @@ -3271,14 +3270,14 @@ static uint16_t mode_pacman(void) { character[i].color = ghostColor; } } - + // Draw uneaten power dots for (int i = 0; i < numPowerDots; i++) { if (!character[i + numGhosts + 1].eaten && (unsigned)character[i + numGhosts + 1].pos < SEGLEN) { SEGMENT.setPixelColor(character[i + numGhosts + 1].pos, character[i + numGhosts + 1].color); } } - + // Check if PacMan ate a power dot for (int j = 0; j < numPowerDots; j++) { if (character[PACMAN].pos >= character[j + numGhosts + 1].pos && !character[j + numGhosts + 1].eaten) { @@ -3294,57 +3293,56 @@ static uint16_t mode_pacman(void) { character[j + numGhosts + 1].eaten = true; } } - + // Reset when PacMan reaches start with blue ghosts if (character[1].blue && character[PACMAN].pos <= 0) { // Reverse direction back for (int i = 0; i <= numGhosts; i++) { character[i].direction = true; } - // Reset ghost colors const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; for (int i = 1; i <= numGhosts; i++) { character[i].color = ghostColors[(i-1) % 4]; character[i].blue = false; } - // Reset power dots if last one was eaten if (character[numGhosts + 1].eaten) { for (int i = 0; i < numPowerDots; i++) { character[i + numGhosts + 1].eaten = false; } - character[PACMAN].topPos = 0; + character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment) } } - + // Update and draw characters based on speed setting bool updatePositions = (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0); - + + // update positions of characters if it's time to do so if (updatePositions) { character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; for (int i = 1; i <= numGhosts; i++) { character[i].pos += character[i].direction ? 1 : -1; } } - + // Draw PacMan if ((unsigned)character[PACMAN].pos < SEGLEN) { SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); } - + // Draw ghosts for (int i = 1; i <= numGhosts; i++) { if ((unsigned)character[i].pos < SEGLEN) { SEGMENT.setPixelColor(character[i].pos, character[i].color); } } - + // Track farthest position of PacMan if (character[PACMAN].topPos < character[PACMAN].pos) { character[PACMAN].topPos = character[PACMAN].pos; } - + return FRAMETIME; } static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,# of Ghosts,White Dots,Compact Dots,;;!;1;m12=0,sx=192,ix=64,c1=64,c3=12,o1=1"; From d3952490e2c14fecfb74dee0c18107dd4b34054f Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Nov 2025 17:47:12 -0700 Subject: [PATCH 10/20] Optimized Ants effect (ran through claude.ai) --- wled00/FX.cpp | 331 +++++++++++++++++++++++++------------------------- wled00/FX.h | 4 +- 2 files changed, 168 insertions(+), 167 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d4a77692da..a8a9460312 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3160,192 +3160,193 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b /* -/ Pac-Man by Bob Loeffler with help from @dedehai and @blazoncek -* speed slider is for speed. -* intensity slider is for selecting the number of power dots. -* custom1 slider is for selecting the LED where the ghosts will start blinking blue. -* custom3 slider is for selecting the # of ghosts (between 2 and 8). -* check1 is for displaying White Dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black). -* check2 is for the Compact Dots mode of displaying white dots. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. -* aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the intensity slider. -* aux1 is the main counter for timing. +/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - Jan-Feb 2025 +* bouncing balls on a track track Effect modified from Aircoookie's bouncing balls +* Courtesy of pjhatch (https://github.com/pjhatch) +* https://github.com/Aircoookie/WLED/pull/1039 +* +* First slider is for the ants' speed. +* Second slider is for the # of ants. +* Third slider is for the Ants' size. +* Checkbox1 is for Gathering food (enabled if you want the ants to gather food, disabled if they are just walking). +* We will switch directions when they get to the beginning or end of the segment. +* When they have food, we will enable the Pass By option so they can drop off their food easier (and look for more food). +* Checkbox2 is for Overlay mode (enabled is Overlay, disabled is no overlay) +* Checkbox3 is for whether the ants will bump into each other (disabled) or just pass by each other (enabled) */ -typedef struct PacManChars { - signed pos; - signed topPos; // LED position of farthest PacMan has moved - uint32_t color; - bool direction; // true = moving away from first LED - bool blue; // used for ghosts only - bool eaten; // used for power dots only -} pacmancharacters_t; - -static uint16_t mode_pacman(void) { - constexpr unsigned ORANGEYELLOW = 0xFFCC00; - constexpr unsigned PURPLEISH = 0xB000B0; - constexpr unsigned ORANGEISH = 0xFF8800; - constexpr unsigned WHITEISH = 0x999999; - constexpr unsigned PACMAN = 0; // PacMan is character[0] - - const unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 pixels - unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); - unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8); - - // Pack two values into one unsigned int (SEGENV.aux0) - unsigned short combined_value = (numPowerDots << 8) | numGhosts; - if (combined_value != SEGENV.aux0) SEGENV.call = 0; // Reinitialize on setting change - SEGENV.aux0 = combined_value; - - // Allocate segment data - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character - if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static(); - pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - - // Calculate when blue ghosts start blinking. - // On first call (or after settings change), `topPos` is not known yet, so fall back to the full segment length in that case. - int maxBlinkPos = (SEGENV.call == 0) ? (int)SEGLEN - 1 : character[PACMAN].topPos; - if (maxBlinkPos < 20) maxBlinkPos = 20; - int startBlinkingGhostsLED = (SEGLEN < 64) - ? (int)SEGLEN / 3 - : map(SEGMENT.custom1, 0, 255, 20, maxBlinkPos); - - // Initialize characters on first call - if (SEGENV.call == 0) { - // Initialize PacMan - character[PACMAN].color = YELLOW; - character[PACMAN].pos = 0; - character[PACMAN].topPos = 0; - character[PACMAN].direction = true; - character[PACMAN].blue = false; +// Ant structure representing each ant's state +struct Ant { + unsigned long lastBumpUpdate; // the last time the ant bumped into another ant + bool hasFood; + float velocity; + float position; // (0.0 to 1.0 range) +}; - // Initialize ghosts with alternating colors - const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; - for (int i = 1; i <= numGhosts; i++) { - character[i].color = ghostColors[(i-1) % 4]; - character[i].pos = -2 * (i + 1); - character[i].direction = true; - character[i].blue = false; +constexpr unsigned MAX_ANTS = 32; +constexpr unsigned DEFAULT_ANT_SIZE = 1; +constexpr float MIN_COLLISION_TIME_MS = 2.0f; +constexpr float VELOCITY_MIN = 2.0f; +constexpr float VELOCITY_MAX = 10.0f; + +// Helper function to get food pixel color based on ant and background colors +static uint32_t getFoodColor(uint32_t antColor, uint32_t backgroundColor) { + if (antColor == WHITE) + return (backgroundColor == YELLOW) ? GRAY : YELLOW; + return (backgroundColor == WHITE) ? YELLOW : WHITE; +} + +// Helper function to handle ant boundary wrapping or bouncing +static void handleBoundary(Ant& ant, float& position, bool gatherFood, bool atStart, unsigned long currentTime) { + if (gatherFood) { + // Bounce mode: reverse direction and update food status + position = atStart ? 0.0f : 1.0f; + ant.velocity = -ant.velocity; + ant.lastBumpUpdate = currentTime; + ant.position = position; + ant.hasFood = atStart; // Has food when leaving start, drops it at end + } else { + // Wrap mode: teleport to opposite end + position = atStart ? 1.0f : 0.0f; + ant.lastBumpUpdate = currentTime; + ant.position = position; } +} + +// Helper function to calculate ant color +static uint32_t getAntColor(int antIndex, int numAnts, bool usePalette) { + if (usePalette) + return SEGMENT.color_from_palette(antIndex * 255 / numAnts, false, PALETTE_SOLID_WRAP, 255); + // Alternate between two colors for default palette + return (antIndex % 3 == 1) ? SEGCOLOR(0) : SEGCOLOR(2); +} - // Initialize power dots - for (int i = 0; i < maxPowerDots; i++) { - character[i + numGhosts + 1].color = ORANGEYELLOW; - character[i + numGhosts + 1].eaten = false; +// Helper function to render a single ant pixel with food handling +static void renderAntPixel(int pixelIndex, int pixelOffset, int antSize, const Ant& ant, uint32_t antColor, uint32_t backgroundColor, bool gatherFood) { + bool isMovingBackward = (ant.velocity < 0); + bool isFoodPixel = gatherFood && ant.hasFood && ((isMovingBackward && pixelOffset == 0) || (!isMovingBackward && pixelOffset == antSize - 1)); + if (isFoodPixel) { + SEGMENT.setPixelColor(pixelIndex, getFoodColor(antColor, backgroundColor)); + } else { + SEGMENT.setPixelColor(pixelIndex, antColor); } - character[numGhosts + 1].pos = SEGLEN - 1; // Last power dot at end - } +} - if (strip.now > SEGENV.step) { - SEGENV.step = strip.now; - SEGENV.aux1++; - } +static uint16_t mode_ants(void) { + // Allocate memory for ant data + const uint32_t backgroundColor = SEGCOLOR(1); + const unsigned dataSize = sizeof(Ant) * MAX_ANTS; + if (!SEGENV.allocateData(dataSize)) return mode_static(); // Allocation failed - SEGMENT.fill(BLACK); + Ant* ants = reinterpret_cast(SEGENV.data); - // Draw white dots in front of PacMan if option selected - if (SEGMENT.check1) { - int step = SEGMENT.check2 ? 1 : 2; // Compact or spaced dots - for (int i = SEGLEN - 1; i > character[PACMAN].topPos; i -= step) { - SEGMENT.setPixelColor(i, WHITEISH); + // Extract configuration from segment settings + const unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS); + const bool gatherFood = SEGMENT.check1; + const bool overlayMode = SEGMENT.check2; + const bool passBy = SEGMENT.check3 || gatherFood; // Always pass by when gathering food + const unsigned antSize = map(SEGMENT.custom1, 0, 255, 1, 20) + (gatherFood ? 1 : 0); + + // Initialize ants on first call + if (SEGENV.call == 0) { + const int confusedAntIndex = hw_random(0, numAnts - 1); // the first random ant to go backwards + + for (int i = 0; i < MAX_ANTS; i++) { + ants[i].lastBumpUpdate = strip.now; + + // Random velocity between 2.0 and 10.0 + float velocity = VELOCITY_MIN + (VELOCITY_MAX - VELOCITY_MIN) * hw_random16(1000, 5000) / 5000.0f; + // One random ant moves in opposite direction + ants[i].velocity = (i == confusedAntIndex) ? -velocity : velocity; + // Random starting position (0.0 to 1.0) + ants[i].position = hw_random16(0, 10000) / 10000.0f; + // Ants don't have food yet + ants[i].hasFood = false; + } } - } - // Update power dot positions dynamically - unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; - for (int i = 1; i < maxPowerDots; i++) { - character[i + numGhosts + 1].pos = 10 + ((i * everyXLeds) >> 8); - } + // Calculate time conversion factor based on speed slider + const float timeConversionFactor = float(scale8(8, 255 - SEGMENT.speed) + 1) * 20000.0f; - // Blink power dots every 10 ticks - if (SEGENV.aux1 % 10 == 0) { - uint32_t dotColor = (character[numGhosts + 1].color == ORANGEYELLOW) ? BLACK : ORANGEYELLOW; - for (int i = 0; i < maxPowerDots; i++) { - character[i + numGhosts + 1].color = dotColor; - } - } + // Clear background if not in overlay mode + if (!overlayMode) SEGMENT.fill(backgroundColor); - // Blink blue ghosts when nearing start - if (SEGENV.aux1 % 15 == 0 && character[1].blue && character[PACMAN].pos <= startBlinkingGhostsLED) { - uint32_t ghostColor = (character[1].color == BLUE) ? WHITEISH : BLUE; - for (int i = 1; i <= numGhosts; i++) { - character[i].color = ghostColor; - } - } + // Update and render each ant + for (int i = 0; i < numAnts; i++) { + const float timeSinceLastUpdate = float(strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; + float newPosition = ants[i].position + ants[i].velocity * timeSinceLastUpdate; - // Draw uneaten power dots - for (int i = 0; i < numPowerDots; i++) { - if (!character[i + numGhosts + 1].eaten && (unsigned)character[i + numGhosts + 1].pos < SEGLEN) { - SEGMENT.setPixelColor(character[i + numGhosts + 1].pos, character[i + numGhosts + 1].color); - } - } + // Reset ants that wandered too far off-track (e.g., after intensity change) + if (newPosition < -0.5f || newPosition > 1.5f) { + newPosition = ants[i].position = hw_random16(0, 10000) / 10000.0f; + ants[i].lastBumpUpdate = strip.now; + } - // Check if PacMan ate a power dot - for (int j = 0; j < numPowerDots; j++) { - if (character[PACMAN].pos >= character[j + numGhosts + 1].pos && !character[j + numGhosts + 1].eaten) { - // Reverse all characters - PacMan now chases ghosts - for (int i = 0; i <= numGhosts; i++) { - character[i].direction = false; - } - // Turn ghosts blue - for (int i = 1; i <= numGhosts; i++) { - character[i].color = BLUE; - character[i].blue = true; - } - character[j + numGhosts + 1].eaten = true; - } - } + // Handle boundary conditions (bounce or wrap) + if (newPosition <= 0.0f && ants[i].velocity < 0.0f) { + handleBoundary(ants[i], newPosition, gatherFood, true, strip.now); + } else if (newPosition >= 1.0f && ants[i].velocity > 0.0f) { + handleBoundary(ants[i], newPosition, gatherFood, false, strip.now); + } - // Reset when PacMan reaches start with blue ghosts - if (character[1].blue && character[PACMAN].pos <= 0) { - // Reverse direction back - for (int i = 0; i <= numGhosts; i++) { - character[i].direction = true; - } - // Reset ghost colors - const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; - for (int i = 1; i <= numGhosts; i++) { - character[i].color = ghostColors[(i-1) % 4]; - character[i].blue = false; - } - // Reset power dots if last one was eaten - if (character[numGhosts + 1].eaten) { - for (int i = 0; i < numPowerDots; i++) { - character[i + numGhosts + 1].eaten = false; - } - character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment) - } - } + // Handle collisions between ants (if not passing by) + if (!passBy) { + for (int j = i + 1; j < numAnts; j++) { + if (ants[j].velocity == ants[i].velocity) continue; // Moving in same direction at same speed + + // Calculate collision time using physics + const float timeOffset = float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate); + const float collisionTime = + (timeConversionFactor * (ants[i].position - ants[j].position) + + ants[i].velocity * timeOffset) / + (ants[j].velocity - ants[i].velocity); + + // Check if collision occurred in valid time window + const float timeSinceJ = float(strip.now - ants[j].lastBumpUpdate); + if (collisionTime > MIN_COLLISION_TIME_MS && collisionTime < timeSinceJ) { + // Update positions to collision point + const float adjustedTime = (collisionTime + float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate)) / timeConversionFactor; + ants[i].position += ants[i].velocity * adjustedTime; + ants[j].position = ants[i].position; + + // Update collision time + const unsigned long collisionMoment = static_cast(collisionTime + 0.5f) + ants[j].lastBumpUpdate; + ants[i].lastBumpUpdate = collisionMoment; + ants[j].lastBumpUpdate = collisionMoment; + + // Reverse the faster ant's direction + if (ants[i].velocity > ants[j].velocity) { + ants[i].velocity = -ants[i].velocity; + } else { + ants[j].velocity = -ants[j].velocity; + } + + // Recalculate position after collision + newPosition = ants[i].position + ants[i].velocity * (strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; + } + } + } - // Update and draw characters based on speed setting - bool updatePositions = (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0); + // Clamp position to valid range + newPosition = constrain(newPosition, 0.0f, 1.0f); + const unsigned pixelPosition = round(newPosition * (SEGLEN - 1)); - // update positions of characters if it's time to do so - if (updatePositions) { - character[PACMAN].pos += character[PACMAN].direction ? 1 : -1; - for (int i = 1; i <= numGhosts; i++) { - character[i].pos += character[i].direction ? 1 : -1; - } - } + // Determine ant color + const uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0); - // Draw PacMan - if ((unsigned)character[PACMAN].pos < SEGLEN) { - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); - } + // Render ant pixels + for (int pixelOffset = 0; pixelOffset < antSize; pixelOffset++) { + const unsigned currentPixel = pixelPosition + pixelOffset; + renderAntPixel(currentPixel, pixelOffset, antSize, ants[i], antColor, backgroundColor, gatherFood); + } - // Draw ghosts - for (int i = 1; i <= numGhosts; i++) { - if ((unsigned)character[i].pos < SEGLEN) { - SEGMENT.setPixelColor(character[i].pos, character[i].color); + // Update ant state + ants[i].lastBumpUpdate = strip.now; + ants[i].position = newPosition; } - } - - // Track farthest position of PacMan - if (character[PACMAN].topPos < character[PACMAN].pos) { - character[PACMAN].topPos = character[PACMAN].pos; - } - return FRAMETIME; + return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,Start Blinking distance,,# of Ghosts,White Dots,Compact Dots,;;!;1;m12=0,sx=192,ix=64,c1=64,c3=12,o1=1"; +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Gathering food,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32,o1=1,o3=1"; /* @@ -11097,7 +11098,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); - addEffect(FX_MODE_PACMAN, &mode_pacman, _data_FX_MODE_PACMAN); + addEffect(FX_MODE_ANTS, &mode_ants, _data_FX_MODE_ANTS); // --- 1D audio effects --- addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS); diff --git a/wled00/FX.h b/wled00/FX.h index bb96ec5f42..f5c754f634 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -310,7 +310,6 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_2DFIRENOISE 149 #define FX_MODE_2DSQUAREDSWIRL 150 // #define FX_MODE_2DFIRE2012 151 -#define FX_MODE_PACMAN 151 // gap fill (non-SR). Do NOT renumber; SR-ID range must remain stable. #define FX_MODE_2DDNA 152 #define FX_MODE_2DMATRIX 153 #define FX_MODE_2DMETABALLS 154 @@ -379,7 +378,8 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_PS1DSONICBOOM 215 #define FX_MODE_PS1DSPRINGY 216 #define FX_MODE_PARTICLEGALAXY 217 -#define MODE_COUNT 218 +#define FX_MODE_ANTS 218 +#define MODE_COUNT 219 #define BLEND_STYLE_FADE 0x00 // universal From 8c4520a7e0ee6d91d8c462576270b8f436e5350d Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Nov 2025 19:23:39 -0700 Subject: [PATCH 11/20] Two small changes recommended by CodeRabbitAI --- wled00/FX.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a8a9460312..f15c21c871 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3232,6 +3232,8 @@ static void renderAntPixel(int pixelIndex, int pixelOffset, int antSize, const A } static uint16_t mode_ants(void) { + if (SEGLEN <= 1) return mode_static(); + // Allocate memory for ant data const uint32_t backgroundColor = SEGCOLOR(1); const unsigned dataSize = sizeof(Ant) * MAX_ANTS; @@ -3336,6 +3338,7 @@ static uint16_t mode_ants(void) { // Render ant pixels for (int pixelOffset = 0; pixelOffset < antSize; pixelOffset++) { const unsigned currentPixel = pixelPosition + pixelOffset; + if (currentPixel >= SEGLEN) break; renderAntPixel(currentPixel, pixelOffset, antSize, ants[i], antColor, backgroundColor, gatherFood); } From 7d5defd7554526d60b24d55f3d36bb95c49692b7 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Nov 2025 20:38:20 -0700 Subject: [PATCH 12/20] Ants: fixed indentation/tab issue found by coderabbitai --- wled00/FX.cpp | 256 +++++++++++++++++++++++++------------------------- 1 file changed, 126 insertions(+), 130 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f15c21c871..39d81c7535 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3176,178 +3176,174 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b */ // Ant structure representing each ant's state struct Ant { - unsigned long lastBumpUpdate; // the last time the ant bumped into another ant - bool hasFood; - float velocity; - float position; // (0.0 to 1.0 range) + unsigned long lastBumpUpdate; // the last time the ant bumped into another ant + bool hasFood; + float velocity; + float position; // (0.0 to 1.0 range) }; constexpr unsigned MAX_ANTS = 32; -constexpr unsigned DEFAULT_ANT_SIZE = 1; constexpr float MIN_COLLISION_TIME_MS = 2.0f; constexpr float VELOCITY_MIN = 2.0f; constexpr float VELOCITY_MAX = 10.0f; // Helper function to get food pixel color based on ant and background colors static uint32_t getFoodColor(uint32_t antColor, uint32_t backgroundColor) { - if (antColor == WHITE) - return (backgroundColor == YELLOW) ? GRAY : YELLOW; - return (backgroundColor == WHITE) ? YELLOW : WHITE; + if (antColor == WHITE) + return (backgroundColor == YELLOW) ? GRAY : YELLOW; + return (backgroundColor == WHITE) ? YELLOW : WHITE; } // Helper function to handle ant boundary wrapping or bouncing static void handleBoundary(Ant& ant, float& position, bool gatherFood, bool atStart, unsigned long currentTime) { - if (gatherFood) { - // Bounce mode: reverse direction and update food status - position = atStart ? 0.0f : 1.0f; - ant.velocity = -ant.velocity; - ant.lastBumpUpdate = currentTime; - ant.position = position; - ant.hasFood = atStart; // Has food when leaving start, drops it at end - } else { - // Wrap mode: teleport to opposite end - position = atStart ? 1.0f : 0.0f; - ant.lastBumpUpdate = currentTime; - ant.position = position; - } + if (gatherFood) { + // Bounce mode: reverse direction and update food status + position = atStart ? 0.0f : 1.0f; + ant.velocity = -ant.velocity; + ant.lastBumpUpdate = currentTime; + ant.position = position; + ant.hasFood = atStart; // Has food when leaving start, drops it at end + } else { + // Wrap mode: teleport to opposite end + position = atStart ? 1.0f : 0.0f; + ant.lastBumpUpdate = currentTime; + ant.position = position; + } } // Helper function to calculate ant color static uint32_t getAntColor(int antIndex, int numAnts, bool usePalette) { - if (usePalette) - return SEGMENT.color_from_palette(antIndex * 255 / numAnts, false, PALETTE_SOLID_WRAP, 255); - // Alternate between two colors for default palette - return (antIndex % 3 == 1) ? SEGCOLOR(0) : SEGCOLOR(2); + if (usePalette) + return SEGMENT.color_from_palette(antIndex * 255 / numAnts, false, PALETTE_SOLID_WRAP, 255); + // Alternate between two colors for default palette + return (antIndex % 3 == 1) ? SEGCOLOR(0) : SEGCOLOR(2); } // Helper function to render a single ant pixel with food handling static void renderAntPixel(int pixelIndex, int pixelOffset, int antSize, const Ant& ant, uint32_t antColor, uint32_t backgroundColor, bool gatherFood) { - bool isMovingBackward = (ant.velocity < 0); - bool isFoodPixel = gatherFood && ant.hasFood && ((isMovingBackward && pixelOffset == 0) || (!isMovingBackward && pixelOffset == antSize - 1)); - if (isFoodPixel) { - SEGMENT.setPixelColor(pixelIndex, getFoodColor(antColor, backgroundColor)); - } else { - SEGMENT.setPixelColor(pixelIndex, antColor); - } + bool isMovingBackward = (ant.velocity < 0); + bool isFoodPixel = gatherFood && ant.hasFood && ((isMovingBackward && pixelOffset == 0) || (!isMovingBackward && pixelOffset == antSize - 1)); + if (isFoodPixel) { + SEGMENT.setPixelColor(pixelIndex, getFoodColor(antColor, backgroundColor)); + } else { + SEGMENT.setPixelColor(pixelIndex, antColor); + } } static uint16_t mode_ants(void) { - if (SEGLEN <= 1) return mode_static(); + if (SEGLEN <= 1) return mode_static(); - // Allocate memory for ant data - const uint32_t backgroundColor = SEGCOLOR(1); - const unsigned dataSize = sizeof(Ant) * MAX_ANTS; - if (!SEGENV.allocateData(dataSize)) return mode_static(); // Allocation failed + // Allocate memory for ant data + const uint32_t backgroundColor = SEGCOLOR(1); + const unsigned dataSize = sizeof(Ant) * MAX_ANTS; + if (!SEGENV.allocateData(dataSize)) return mode_static(); // Allocation failed - Ant* ants = reinterpret_cast(SEGENV.data); + Ant* ants = reinterpret_cast(SEGENV.data); - // Extract configuration from segment settings - const unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS); - const bool gatherFood = SEGMENT.check1; - const bool overlayMode = SEGMENT.check2; - const bool passBy = SEGMENT.check3 || gatherFood; // Always pass by when gathering food - const unsigned antSize = map(SEGMENT.custom1, 0, 255, 1, 20) + (gatherFood ? 1 : 0); + // Extract configuration from segment settings + const unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS); + const bool gatherFood = SEGMENT.check1; + const bool overlayMode = SEGMENT.check2; + const bool passBy = SEGMENT.check3 || gatherFood; // Always pass by when gathering food + const unsigned antSize = map(SEGMENT.custom1, 0, 255, 1, 20) + (gatherFood ? 1 : 0); - // Initialize ants on first call - if (SEGENV.call == 0) { - const int confusedAntIndex = hw_random(0, numAnts - 1); // the first random ant to go backwards - - for (int i = 0; i < MAX_ANTS; i++) { - ants[i].lastBumpUpdate = strip.now; - - // Random velocity between 2.0 and 10.0 - float velocity = VELOCITY_MIN + (VELOCITY_MAX - VELOCITY_MIN) * hw_random16(1000, 5000) / 5000.0f; - // One random ant moves in opposite direction - ants[i].velocity = (i == confusedAntIndex) ? -velocity : velocity; - // Random starting position (0.0 to 1.0) - ants[i].position = hw_random16(0, 10000) / 10000.0f; - // Ants don't have food yet - ants[i].hasFood = false; - } + // Initialize ants on first call + if (SEGENV.call == 0) { + const int confusedAntIndex = hw_random(0, numAnts - 1); // the first random ant to go backwards + + for (int i = 0; i < MAX_ANTS; i++) { + ants[i].lastBumpUpdate = strip.now; + + // Random velocity between 2.0 and 10.0 + float velocity = VELOCITY_MIN + (VELOCITY_MAX - VELOCITY_MIN) * hw_random16(1000, 5000) / 5000.0f; + // One random ant moves in opposite direction + ants[i].velocity = (i == confusedAntIndex) ? -velocity : velocity; + // Random starting position (0.0 to 1.0) + ants[i].position = hw_random16(0, 10000) / 10000.0f; + // Ants don't have food yet + ants[i].hasFood = false; } + } - // Calculate time conversion factor based on speed slider - const float timeConversionFactor = float(scale8(8, 255 - SEGMENT.speed) + 1) * 20000.0f; + // Calculate time conversion factor based on speed slider + const float timeConversionFactor = float(scale8(8, 255 - SEGMENT.speed) + 1) * 20000.0f; - // Clear background if not in overlay mode - if (!overlayMode) SEGMENT.fill(backgroundColor); + // Clear background if not in overlay mode + if (!overlayMode) SEGMENT.fill(backgroundColor); - // Update and render each ant - for (int i = 0; i < numAnts; i++) { - const float timeSinceLastUpdate = float(strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; - float newPosition = ants[i].position + ants[i].velocity * timeSinceLastUpdate; + // Update and render each ant + for (int i = 0; i < numAnts; i++) { + const float timeSinceLastUpdate = float(strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; + float newPosition = ants[i].position + ants[i].velocity * timeSinceLastUpdate; - // Reset ants that wandered too far off-track (e.g., after intensity change) - if (newPosition < -0.5f || newPosition > 1.5f) { - newPosition = ants[i].position = hw_random16(0, 10000) / 10000.0f; - ants[i].lastBumpUpdate = strip.now; - } + // Reset ants that wandered too far off-track (e.g., after intensity change) + if (newPosition < -0.5f || newPosition > 1.5f) { + newPosition = ants[i].position = hw_random16(0, 10000) / 10000.0f; + ants[i].lastBumpUpdate = strip.now; + } - // Handle boundary conditions (bounce or wrap) - if (newPosition <= 0.0f && ants[i].velocity < 0.0f) { - handleBoundary(ants[i], newPosition, gatherFood, true, strip.now); - } else if (newPosition >= 1.0f && ants[i].velocity > 0.0f) { - handleBoundary(ants[i], newPosition, gatherFood, false, strip.now); - } + // Handle boundary conditions (bounce or wrap) + if (newPosition <= 0.0f && ants[i].velocity < 0.0f) { + handleBoundary(ants[i], newPosition, gatherFood, true, strip.now); + } else if (newPosition >= 1.0f && ants[i].velocity > 0.0f) { + handleBoundary(ants[i], newPosition, gatherFood, false, strip.now); + } - // Handle collisions between ants (if not passing by) - if (!passBy) { - for (int j = i + 1; j < numAnts; j++) { - if (ants[j].velocity == ants[i].velocity) continue; // Moving in same direction at same speed - - // Calculate collision time using physics - const float timeOffset = float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate); - const float collisionTime = - (timeConversionFactor * (ants[i].position - ants[j].position) + - ants[i].velocity * timeOffset) / - (ants[j].velocity - ants[i].velocity); - - // Check if collision occurred in valid time window - const float timeSinceJ = float(strip.now - ants[j].lastBumpUpdate); - if (collisionTime > MIN_COLLISION_TIME_MS && collisionTime < timeSinceJ) { - // Update positions to collision point - const float adjustedTime = (collisionTime + float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate)) / timeConversionFactor; - ants[i].position += ants[i].velocity * adjustedTime; - ants[j].position = ants[i].position; - - // Update collision time - const unsigned long collisionMoment = static_cast(collisionTime + 0.5f) + ants[j].lastBumpUpdate; - ants[i].lastBumpUpdate = collisionMoment; - ants[j].lastBumpUpdate = collisionMoment; - - // Reverse the faster ant's direction - if (ants[i].velocity > ants[j].velocity) { - ants[i].velocity = -ants[i].velocity; - } else { - ants[j].velocity = -ants[j].velocity; - } - - // Recalculate position after collision - newPosition = ants[i].position + ants[i].velocity * (strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; - } - } - } + // Handle collisions between ants (if not passing by) + if (!passBy) { + for (int j = i + 1; j < numAnts; j++) { + if (ants[j].velocity == ants[i].velocity) continue; // Moving in same direction at same speed - // Clamp position to valid range - newPosition = constrain(newPosition, 0.0f, 1.0f); - const unsigned pixelPosition = round(newPosition * (SEGLEN - 1)); + // Calculate collision time using physics + const float timeOffset = float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate); + const float collisionTime = (timeConversionFactor * (ants[i].position - ants[j].position) + ants[i].velocity * timeOffset) / (ants[j].velocity - ants[i].velocity); - // Determine ant color - const uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0); + // Check if collision occurred in valid time window + const float timeSinceJ = float(strip.now - ants[j].lastBumpUpdate); + if (collisionTime > MIN_COLLISION_TIME_MS && collisionTime < timeSinceJ) { + // Update positions to collision point + const float adjustedTime = (collisionTime + float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate)) / timeConversionFactor; + ants[i].position += ants[i].velocity * adjustedTime; + ants[j].position = ants[i].position; + + // Update collision time + const unsigned long collisionMoment = static_cast(collisionTime + 0.5f) + ants[j].lastBumpUpdate; + ants[i].lastBumpUpdate = collisionMoment; + ants[j].lastBumpUpdate = collisionMoment; + + // Reverse the faster ant's direction + if (ants[i].velocity > ants[j].velocity) { + ants[i].velocity = -ants[i].velocity; + } else { + ants[j].velocity = -ants[j].velocity; + } - // Render ant pixels - for (int pixelOffset = 0; pixelOffset < antSize; pixelOffset++) { - const unsigned currentPixel = pixelPosition + pixelOffset; - if (currentPixel >= SEGLEN) break; - renderAntPixel(currentPixel, pixelOffset, antSize, ants[i], antColor, backgroundColor, gatherFood); + // Recalculate position after collision + newPosition = ants[i].position + ants[i].velocity * (strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; } + } + } + + // Clamp position to valid range + newPosition = constrain(newPosition, 0.0f, 1.0f); + const unsigned pixelPosition = round(newPosition * (SEGLEN - 1)); + + // Determine ant color + const uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0); - // Update ant state - ants[i].lastBumpUpdate = strip.now; - ants[i].position = newPosition; + // Render ant pixels + for (int pixelOffset = 0; pixelOffset < antSize; pixelOffset++) { + const unsigned currentPixel = pixelPosition + pixelOffset; + if (currentPixel >= SEGLEN) break; + renderAntPixel(currentPixel, pixelOffset, antSize, ants[i], antColor, backgroundColor, gatherFood); } - return FRAMETIME; + // Update ant state + ants[i].lastBumpUpdate = strip.now; + ants[i].position = newPosition; + } + + return FRAMETIME; } static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Gathering food,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32,o1=1,o3=1"; From 7f6d63219bd41c548843c0321590b620ee48865c Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Nov 2025 20:58:36 -0700 Subject: [PATCH 13/20] Change from round() to roundf() suggested by coderabbitai --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 39d81c7535..60e4268c38 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3326,7 +3326,7 @@ static uint16_t mode_ants(void) { // Clamp position to valid range newPosition = constrain(newPosition, 0.0f, 1.0f); - const unsigned pixelPosition = round(newPosition * (SEGLEN - 1)); + const unsigned pixelPosition = roundf(newPosition * (SEGLEN - 1)); // Determine ant color const uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0); From 3c4081441ac616301105481698f98dc02a92fcc6 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Tue, 25 Nov 2025 20:50:02 -0700 Subject: [PATCH 14/20] Ants: removed const from several local variables --- wled00/FX.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 60e4268c38..ac8362ab90 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3234,22 +3234,22 @@ static uint16_t mode_ants(void) { if (SEGLEN <= 1) return mode_static(); // Allocate memory for ant data - const uint32_t backgroundColor = SEGCOLOR(1); - const unsigned dataSize = sizeof(Ant) * MAX_ANTS; + uint32_t backgroundColor = SEGCOLOR(1); + unsigned dataSize = sizeof(Ant) * MAX_ANTS; if (!SEGENV.allocateData(dataSize)) return mode_static(); // Allocation failed Ant* ants = reinterpret_cast(SEGENV.data); // Extract configuration from segment settings - const unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS); - const bool gatherFood = SEGMENT.check1; - const bool overlayMode = SEGMENT.check2; - const bool passBy = SEGMENT.check3 || gatherFood; // Always pass by when gathering food - const unsigned antSize = map(SEGMENT.custom1, 0, 255, 1, 20) + (gatherFood ? 1 : 0); + unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS); + bool gatherFood = SEGMENT.check1; + bool overlayMode = SEGMENT.check2; + bool passBy = SEGMENT.check3 || gatherFood; // Always pass by when gathering food + unsigned antSize = map(SEGMENT.custom1, 0, 255, 1, 20) + (gatherFood ? 1 : 0); // Initialize ants on first call if (SEGENV.call == 0) { - const int confusedAntIndex = hw_random(0, numAnts - 1); // the first random ant to go backwards + int confusedAntIndex = hw_random(0, numAnts - 1); // the first random ant to go backwards for (int i = 0; i < MAX_ANTS; i++) { ants[i].lastBumpUpdate = strip.now; @@ -3266,14 +3266,14 @@ static uint16_t mode_ants(void) { } // Calculate time conversion factor based on speed slider - const float timeConversionFactor = float(scale8(8, 255 - SEGMENT.speed) + 1) * 20000.0f; + float timeConversionFactor = float(scale8(8, 255 - SEGMENT.speed) + 1) * 20000.0f; // Clear background if not in overlay mode if (!overlayMode) SEGMENT.fill(backgroundColor); // Update and render each ant for (int i = 0; i < numAnts; i++) { - const float timeSinceLastUpdate = float(strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; + float timeSinceLastUpdate = float(strip.now - ants[i].lastBumpUpdate) / timeConversionFactor; float newPosition = ants[i].position + ants[i].velocity * timeSinceLastUpdate; // Reset ants that wandered too far off-track (e.g., after intensity change) @@ -3295,19 +3295,19 @@ static uint16_t mode_ants(void) { if (ants[j].velocity == ants[i].velocity) continue; // Moving in same direction at same speed // Calculate collision time using physics - const float timeOffset = float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate); - const float collisionTime = (timeConversionFactor * (ants[i].position - ants[j].position) + ants[i].velocity * timeOffset) / (ants[j].velocity - ants[i].velocity); + float timeOffset = float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate); + float collisionTime = (timeConversionFactor * (ants[i].position - ants[j].position) + ants[i].velocity * timeOffset) / (ants[j].velocity - ants[i].velocity); // Check if collision occurred in valid time window - const float timeSinceJ = float(strip.now - ants[j].lastBumpUpdate); + float timeSinceJ = float(strip.now - ants[j].lastBumpUpdate); if (collisionTime > MIN_COLLISION_TIME_MS && collisionTime < timeSinceJ) { // Update positions to collision point - const float adjustedTime = (collisionTime + float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate)) / timeConversionFactor; + float adjustedTime = (collisionTime + float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate)) / timeConversionFactor; ants[i].position += ants[i].velocity * adjustedTime; ants[j].position = ants[i].position; // Update collision time - const unsigned long collisionMoment = static_cast(collisionTime + 0.5f) + ants[j].lastBumpUpdate; + unsigned long collisionMoment = static_cast(collisionTime + 0.5f) + ants[j].lastBumpUpdate; ants[i].lastBumpUpdate = collisionMoment; ants[j].lastBumpUpdate = collisionMoment; @@ -3326,14 +3326,14 @@ static uint16_t mode_ants(void) { // Clamp position to valid range newPosition = constrain(newPosition, 0.0f, 1.0f); - const unsigned pixelPosition = roundf(newPosition * (SEGLEN - 1)); + unsigned pixelPosition = roundf(newPosition * (SEGLEN - 1)); // Determine ant color - const uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0); + uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0); // Render ant pixels for (int pixelOffset = 0; pixelOffset < antSize; pixelOffset++) { - const unsigned currentPixel = pixelPosition + pixelOffset; + unsigned currentPixel = pixelPosition + pixelOffset; if (currentPixel >= SEGLEN) break; renderAntPixel(currentPixel, pixelOffset, antSize, ants[i], antColor, backgroundColor, gatherFood); } From 1fe14d71f49e369960729626c693c9a1f3023b35 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Tue, 25 Nov 2025 20:59:33 -0700 Subject: [PATCH 15/20] Ants: removed 3 lines of comments from beginning of effect code --- wled00/FX.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ac8362ab90..aeb27783e4 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3160,11 +3160,7 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b /* -/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - Jan-Feb 2025 -* bouncing balls on a track track Effect modified from Aircoookie's bouncing balls -* Courtesy of pjhatch (https://github.com/pjhatch) -* https://github.com/Aircoookie/WLED/pull/1039 -* +/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler 2025 * First slider is for the ants' speed. * Second slider is for the # of ants. * Third slider is for the Ants' size. From f146eba89b1d4212b1a691ad80f96396791745ff Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Tue, 25 Nov 2025 21:49:02 -0700 Subject: [PATCH 16/20] Ants: minor change to confusedAntIndex --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index aeb27783e4..14abc7cc7f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3245,7 +3245,7 @@ static uint16_t mode_ants(void) { // Initialize ants on first call if (SEGENV.call == 0) { - int confusedAntIndex = hw_random(0, numAnts - 1); // the first random ant to go backwards + int confusedAntIndex = hw_random(0, numAnts); // the first random ant to go backwards for (int i = 0; i < MAX_ANTS; i++) { ants[i].lastBumpUpdate = strip.now; From b0f0cbf00000e70357aa1d1901011abafb317bb6 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Tue, 25 Nov 2025 22:22:46 -0700 Subject: [PATCH 17/20] Ants: 2 changes in collision code suggested by coderabbitai --- wled00/FX.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 14abc7cc7f..393b5a7121 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3240,7 +3240,7 @@ static uint16_t mode_ants(void) { unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS); bool gatherFood = SEGMENT.check1; bool overlayMode = SEGMENT.check2; - bool passBy = SEGMENT.check3 || gatherFood; // Always pass by when gathering food + bool passBy = SEGMENT.check3 || gatherFood; // global no‑collision when gathering food is enabled unsigned antSize = map(SEGMENT.custom1, 0, 255, 1, 20) + (gatherFood ? 1 : 0); // Initialize ants on first call @@ -3288,7 +3288,7 @@ static uint16_t mode_ants(void) { // Handle collisions between ants (if not passing by) if (!passBy) { for (int j = i + 1; j < numAnts; j++) { - if (ants[j].velocity == ants[i].velocity) continue; // Moving in same direction at same speed + if (fabsf(ants[j].velocity - ants[i].velocity) < 0.001f) continue; // Moving in same direction at same speed; avoids tiny denominators // Calculate collision time using physics float timeOffset = float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate); @@ -3307,11 +3307,11 @@ static uint16_t mode_ants(void) { ants[i].lastBumpUpdate = collisionMoment; ants[j].lastBumpUpdate = collisionMoment; - // Reverse the faster ant's direction - if (ants[i].velocity > ants[j].velocity) { - ants[i].velocity = -ants[i].velocity; + // Reverse the ant with greater speed magnitude + if (fabsf(ants[i].velocity) > fabsf(ants[j].velocity)) { + ants[i].velocity = -ants[i].velocity; } else { - ants[j].velocity = -ants[j].velocity; + ants[j].velocity = -ants[j].velocity; } // Recalculate position after collision From f35a33425dac237af6acb3f64e2cc9c684b50fd2 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Tue, 25 Nov 2025 23:26:29 -0700 Subject: [PATCH 18/20] Ants: changed 2 comments to clarify Checkbox1 behavior --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 393b5a7121..57fe2dfe19 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3165,8 +3165,8 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * Second slider is for the # of ants. * Third slider is for the Ants' size. * Checkbox1 is for Gathering food (enabled if you want the ants to gather food, disabled if they are just walking). -* We will switch directions when they get to the beginning or end of the segment. -* When they have food, we will enable the Pass By option so they can drop off their food easier (and look for more food). +* We will switch directions when they get to the beginning or end of the segment when gathering food. +* When gathering food, the Pass By option will automatically be enabled so they can drop off their food easier (and look for more food). * Checkbox2 is for Overlay mode (enabled is Overlay, disabled is no overlay) * Checkbox3 is for whether the ants will bump into each other (disabled) or just pass by each other (enabled) */ From 1c25650d847af551f471c4760dfe4a4e1a95db9b Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Wed, 26 Nov 2025 23:31:26 -0700 Subject: [PATCH 19/20] Ants: changed a comment --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 57fe2dfe19..c9564afdf5 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3250,7 +3250,7 @@ static uint16_t mode_ants(void) { for (int i = 0; i < MAX_ANTS; i++) { ants[i].lastBumpUpdate = strip.now; - // Random velocity between 2.0 and 10.0 + // Random velocity float velocity = VELOCITY_MIN + (VELOCITY_MAX - VELOCITY_MIN) * hw_random16(1000, 5000) / 5000.0f; // One random ant moves in opposite direction ants[i].velocity = (i == confusedAntIndex) ? -velocity : velocity; From 10b7860193153a8d5275a7bd7a870e89d569e4be Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 29 Nov 2025 15:28:36 -0700 Subject: [PATCH 20/20] Ants: added the Blur option --- wled00/FX.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c9564afdf5..ff8bc9054d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3164,6 +3164,7 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * First slider is for the ants' speed. * Second slider is for the # of ants. * Third slider is for the Ants' size. +* Fourth slider (custom2) is for blurring the LEDs in the segment. * Checkbox1 is for Gathering food (enabled if you want the ants to gather food, disabled if they are just walking). * We will switch directions when they get to the beginning or end of the segment when gathering food. * When gathering food, the Pass By option will automatically be enabled so they can drop off their food easier (and look for more food). @@ -3339,9 +3340,10 @@ static uint16_t mode_ants(void) { ants[i].position = newPosition; } + SEGMENT.blur(SEGMENT.custom2>>1); return FRAMETIME; } -static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Gathering food,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32,o1=1,o3=1"; +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,Blur,,Gathering food,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32,c2=0,o1=1,o3=1"; /*