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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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 07b8250781edca3b636b70cb0c4c0b33bb107d10 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Tue, 25 Nov 2025 21:21:45 -0700 Subject: [PATCH 10/16] PacMan: changed powerdot eating code requested by coderabbitai --- wled00/FX.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d4a77692da..098931af35 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3186,7 +3186,7 @@ static uint16_t mode_pacman(void) { constexpr unsigned WHITEISH = 0x999999; constexpr unsigned PACMAN = 0; // PacMan is character[0] - const unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 pixels + 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); @@ -3218,7 +3218,7 @@ static uint16_t mode_pacman(void) { character[PACMAN].blue = false; // Initialize ghosts with alternating colors - const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; + 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); @@ -3280,7 +3280,8 @@ static uint16_t mode_pacman(void) { // 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) { + auto &dot = character[j + numGhosts + 1]; + if (character[PACMAN].pos == dot.pos && !dot.eaten) { // Reverse all characters - PacMan now chases ghosts for (int i = 0; i <= numGhosts; i++) { character[i].direction = false; @@ -3290,7 +3291,8 @@ static uint16_t mode_pacman(void) { character[i].color = BLUE; character[i].blue = true; } - character[j + numGhosts + 1].eaten = true; + dot.eaten = true; + break; // only one power dot per frame } } @@ -3301,7 +3303,7 @@ static uint16_t mode_pacman(void) { character[i].direction = true; } // Reset ghost colors - const uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; + 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; From 8a59e12b6a62aa13721bb63c01a3bb33e4733c00 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 29 Nov 2025 15:54:50 -0700 Subject: [PATCH 11/16] PacMan: added Blur and Overlay options --- wled00/FX.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 098931af35..0df66f7af5 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3164,9 +3164,11 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * 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. +* custom2 slider is for blurring the LEDs in the segment. * 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. +* check2 is for Overlay mode (enabled is Overlay, disabled is no overlay). +* check3 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. */ @@ -3189,6 +3191,7 @@ static uint16_t mode_pacman(void) { 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); + bool overlayMode = SEGMENT.check2; // Pack two values into one unsigned int (SEGENV.aux0) unsigned short combined_value = (numPowerDots << 8) | numGhosts; @@ -3239,11 +3242,12 @@ static uint16_t mode_pacman(void) { SEGENV.aux1++; } - SEGMENT.fill(BLACK); + // Clear background if not in overlay mode + if (!overlayMode) 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 + int step = SEGMENT.check3 ? 1 : 2; // Compact or spaced dots for (int i = SEGLEN - 1; i > character[PACMAN].topPos; i -= step) { SEGMENT.setPixelColor(i, WHITEISH); } @@ -3345,9 +3349,10 @@ static uint16_t mode_pacman(void) { character[PACMAN].topPos = character[PACMAN].pos; } + SEGMENT.blur(SEGMENT.custom2>>1); 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_PACMAN[] PROGMEM = "PacMan@Speed,# of PowerDots,Blink distance,Blur,# of Ghosts,Dots,Overlay,Compact;;!;1;m12=0,sx=192,ix=64,c1=64,c2=0,c3=12,o1=1,o2=0"; /* From ac5a934a55f4fbaaa3f262e56497c71ec30bd829 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 29 Nov 2025 20:35:11 -0700 Subject: [PATCH 12/16] PacMan: changed from maxPowerDots to numPowerDots in a couple loops --- wled00/FX.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0df66f7af5..f58b2605df 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3230,7 +3230,7 @@ static uint16_t mode_pacman(void) { } // Initialize power dots - for (int i = 0; i < maxPowerDots; i++) { + for (int i = 0; i < numPowerDots; i++) { character[i + numGhosts + 1].color = ORANGEYELLOW; character[i + numGhosts + 1].eaten = false; } @@ -3255,14 +3255,14 @@ static uint16_t mode_pacman(void) { // Update power dot positions dynamically unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; - for (int i = 1; i < maxPowerDots; i++) { + for (int i = 1; i < numPowerDots; 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; - for (int i = 0; i < maxPowerDots; i++) { + for (int i = 0; i < numPowerDots; i++) { character[i + numGhosts + 1].color = dotColor; } } From 6674bc24745e688d8ea731188af754613fe27084 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 29 Nov 2025 23:30:43 -0700 Subject: [PATCH 13/16] PacMan: changed everyXLeds to uint32_t to void 16-bit overflow in power-dot spacing --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f58b2605df..1885aee3d7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3254,7 +3254,7 @@ static uint16_t mode_pacman(void) { } // Update power dot positions dynamically - unsigned everyXLeds = ((SEGLEN - 10) << 8) / numPowerDots; + uint32_t everyXLeds = (((uint32_t)SEGLEN - 10U) << 8) / numPowerDots; // Fixed-point spacing for power dots: use 32-bit math to avoid overflow on long segments. for (int i = 1; i < numPowerDots; i++) { character[i + numGhosts + 1].pos = 10 + ((i * everyXLeds) >> 8); } From 154e8dfeebf50342a3acd88a4290a6f01f9d6bda Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 29 Nov 2025 23:59:35 -0700 Subject: [PATCH 14/16] PacMan: cap the maxPowerDots at 255 so packed state fits in 8 bits --- wled00/FX.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 1885aee3d7..3fa0fb4e59 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3188,13 +3188,13 @@ static uint16_t mode_pacman(void) { constexpr unsigned WHITEISH = 0x999999; constexpr unsigned PACMAN = 0; // PacMan is character[0] - unsigned maxPowerDots = SEGLEN / 10; // max is 1 every 10 pixels + unsigned maxPowerDots = min(SEGLEN / 10U, 255U); // cap the max so packed state fits in 8 bits unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8); bool overlayMode = SEGMENT.check2; - // Pack two values into one unsigned int (SEGENV.aux0) - unsigned short combined_value = (numPowerDots << 8) | numGhosts; + // Pack two 8-bit values into one 16-bit field (stored in SEGENV.aux0) + uint16_t combined_value = uint16_t(((numPowerDots & 0xFF) << 8) | (numGhosts & 0xFF)); if (combined_value != SEGENV.aux0) SEGENV.call = 0; // Reinitialize on setting change SEGENV.aux0 = combined_value; From 58b66dd8fd5cb0d37bc0b9668715b8a741337b5a Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 30 Nov 2025 09:00:19 -0700 Subject: [PATCH 15/16] PacMan: changed Overlay to Smear --- wled00/FX.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 3fa0fb4e59..1077031a64 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3167,7 +3167,7 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * custom2 slider is for blurring the LEDs in the segment. * 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 Overlay mode (enabled is Overlay, disabled is no overlay). +* check2 is for Smear mode (enabled will smear/persist the LED colors, disabled will not). * check3 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. @@ -3191,7 +3191,7 @@ static uint16_t mode_pacman(void) { unsigned maxPowerDots = min(SEGLEN / 10U, 255U); // cap the max so packed state fits in 8 bits unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8); - bool overlayMode = SEGMENT.check2; + bool smearMode = SEGMENT.check2; // Pack two 8-bit values into one 16-bit field (stored in SEGENV.aux0) uint16_t combined_value = uint16_t(((numPowerDots & 0xFF) << 8) | (numGhosts & 0xFF)); @@ -3242,8 +3242,8 @@ static uint16_t mode_pacman(void) { SEGENV.aux1++; } - // Clear background if not in overlay mode - if (!overlayMode) SEGMENT.fill(BLACK); + // Clear background if not in smear mode + if (!smearMode) SEGMENT.fill(BLACK); // Draw white dots in front of PacMan if option selected if (SEGMENT.check1) { @@ -3352,7 +3352,7 @@ static uint16_t mode_pacman(void) { SEGMENT.blur(SEGMENT.custom2>>1); return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of PowerDots,Blink distance,Blur,# of Ghosts,Dots,Overlay,Compact;;!;1;m12=0,sx=192,ix=64,c1=64,c2=0,c3=12,o1=1,o2=0"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of PowerDots,Blink distance,Blur,# of Ghosts,Dots,Smear,Compact;;!;1;m12=0,sx=192,ix=64,c1=64,c2=0,c3=12,o1=1,o2=0"; /* From 19a8c69d60c19cc3ca5c0e82b8e3e0c70759d787 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 30 Nov 2025 09:53:34 -0700 Subject: [PATCH 16/16] made ghostColors[] a constexpr --- wled00/FX.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 1077031a64..2f068e231f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3187,6 +3187,7 @@ static uint16_t mode_pacman(void) { constexpr unsigned ORANGEISH = 0xFF8800; constexpr unsigned WHITEISH = 0x999999; constexpr unsigned PACMAN = 0; // PacMan is character[0] + constexpr uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH}; unsigned maxPowerDots = min(SEGLEN / 10U, 255U); // cap the max so packed state fits in 8 bits unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); @@ -3221,7 +3222,6 @@ static uint16_t mode_pacman(void) { character[PACMAN].blue = false; // Initialize ghosts with alternating colors - 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); @@ -3307,7 +3307,6 @@ static uint16_t mode_pacman(void) { character[i].direction = true; } // Reset ghost colors - 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;