From 34d0f17597f31af1f29e994c4dec0eaddc0b924e Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 3 Feb 2025 00:44:25 -0700 Subject: [PATCH 01/19] Initial commit. Added Ant and PacMan modes --- package-lock.json | 4 +- platformio.ini | 6 +- wled00/FX.cpp | 283 +++++++++++++++++++++++++++++++++++++++++++++- wled00/FX.h | 5 +- 4 files changed, 291 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0afeeaafd8..c81072cfc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.16.0-dev", + "version": "0.16.0-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.16.0-dev", + "version": "0.16.0-alpha", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/platformio.ini b/platformio.ini index e775c61f52..967ec6b6c6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +;default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -18,6 +18,8 @@ build_cache_dir = ~/.buildcache extra_configs = platformio_override.ini +default_envs = esp32dev + [common] # ------------------------------------------------------------------------------ # PLATFORM: @@ -125,7 +127,7 @@ framework = arduino board_build.flash_mode = dout monitor_speed = 115200 # slow upload speed but most compatible (use platformio_override.ini to use faster speed) -upload_speed = 115200 +upload_speed = 921600 # ------------------------------------------------------------------------------ # LIBRARIES: required dependencies diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b9ead1412c..6d30129c49 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3020,9 +3020,9 @@ static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravit * https://github.com/Aircoookie/WLED/pull/1039 */ // modified for balltrack mode -typedef struct RollingBall { +typedef struct RollingBall { // used for rolling_balls and Ants modes unsigned long lastBounceUpdate; - float mass; // could fix this to be = 1. if memory is an issue + float mass; // could fix this to be = 1. if memory is an issue // mass not used in ants mode float velocity; float height; } rball_t; @@ -3115,6 +3115,280 @@ static uint16_t rolling_balls(void) { static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collisions,Overlay,Trails;!,!,!;!;1;m12=1"; //bar +/* +/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - January 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 (1 to 16). +* Third slider is for the Ants' size (1 to x leds) +* Fourth slider is for the background color (black, green, yellow, brown, cyan or white) +* 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) +*/ +static uint16_t mode_ants(void) { + //allocate segment data + uint32_t bgcolor = BLACK; + uint8_t antSize = 1; + const uint16_t maxNumAnts = 32; // 255/16 + 1 ( * 2 ?????) + uint16_t dataSize = sizeof(rball_t) * maxNumAnts; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + int confusedAnt; // the first random ant to go backwards + rball_t *ants = reinterpret_cast(SEGENV.data); + + // number of ants based on intensity setting to max of 16 (32?) + uint8_t numAnts = SEGMENT.intensity/16 + 1; + if (SEGLEN > 127) + numAnts *= 2; // double the number of ants for longer strips/segments + + antSize = map(SEGMENT.custom1, 0, 255, 1, 5); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider + + uint8_t bgColorIdx = map(SEGMENT.custom2, 0, 255, 0, 5); // background color based on the what the user selects from the Background Color slider + switch (bgColorIdx) { + case 0: bgcolor = BLACK; break; // black + case 1: bgcolor = 0x007700; break; // med-dark green + case 2: bgcolor = 0x9c7a00; break; // dark yellow + case 3: bgcolor = 0x845000; break; // brown + case 4: bgcolor = 0x00aaaa; break; // cyan-ish + case 5: bgcolor = 0x999999; break; // light gray (whitish) + } + + if (SEGENV.call == 0) { + confusedAnt = random(0,numAnts-1); + for (int i = 0; i < maxNumAnts; i++) { + ants[i].lastBounceUpdate = strip.now; + ants[i].velocity = 10.0f * float(random16(1000, 5000))/5000.0f; // Random number from 1 to 5 + if (i == confusedAnt) // make ant[i] go in the opposite direction + ants[i].velocity = -ants[i].velocity; + ants[i].height = (float(random16(0, 10000)) / 10000.0f); // Random number from 0 to 1 (used for the position of the ant on the strip) + } + } + + float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider + + if (!SEGMENT.check2) SEGMENT.fill(bgcolor); // fill all LEDs with background color + + for (int i = 0; i < numAnts; i++) { // for each Ant, do this... + float timeSinceLastUpdate = float((strip.now - ants[i].lastBounceUpdate))/cfac; + float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution + // test if intensity level was increased and some ants are way off the track then put them back + if (thisHeight < -0.5f || thisHeight > 1.5f) { + thisHeight = ants[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 + ants[i].lastBounceUpdate = strip.now; + } + // check if reached past the beginning of the strip. If so, wrap around. + if (thisHeight <= 0.0f && ants[i].velocity < 0.0f) { + thisHeight = 1.0f; + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + } + // check if reached past the end of the strip. If so, wrap around. + if (thisHeight >= 1.0f && ants[i].velocity > 0.0f) { + thisHeight = 0.0f; + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + } + // check for "passing by" or "bumping into" + if (!SEGMENT.check3) { // Ants bump into each other and reverse direction if checkbox #3 is not "checked"; they pass each other if "checked" + for (int j = i+1; j < numAnts; j++) { + if (ants[j].velocity != ants[i].velocity) { + // tcollided + ants[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions) + float tcollided = (cfac*(ants[i].height - ants[j].height) + + ants[i].velocity*float(ants[j].lastBounceUpdate - ants[i].lastBounceUpdate))/(ants[j].velocity - ants[i].velocity); + + if ((tcollided > 2.0f) && (tcollided < float(strip.now - ants[j].lastBounceUpdate))) { // 2ms minimum to avoid duplicate bounces + ants[i].height = ants[i].height + ants[i].velocity*(tcollided + float(ants[j].lastBounceUpdate - ants[i].lastBounceUpdate))/cfac; + ants[j].height = ants[i].height; + ants[i].lastBounceUpdate = (unsigned long)(tcollided + 0.5f) + ants[j].lastBounceUpdate; + ants[j].lastBounceUpdate = ants[i].lastBounceUpdate; + + if (ants[i].velocity > ants[j].velocity) + ants[i].velocity = -ants[i].velocity; + else + ants[j].velocity = -ants[j].velocity; + thisHeight = ants[i].height + ants[i].velocity*(strip.now - ants[i].lastBounceUpdate)/cfac; + } + } + } + } + + uint32_t color = SEGCOLOR(0); + if (SEGMENT.palette) { + color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP, 0); + } else { + color = SEGCOLOR(i % NUM_COLORS); + } + + if (thisHeight < 0.0f) thisHeight = 0.0f; + if (thisHeight > 1.0f) thisHeight = 1.0f; + uint16_t pos = round(thisHeight * (SEGLEN - 1)); + + for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 5 pixels) + SEGMENT.setPixelColor(pos, color); + pos = pos + 1; + } + + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + } + + return FRAMETIME; +} +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,Background color,,,Overlay,Pass by;1,2,3;!;1;sx=192,ix=255,c1=80,c2=0,pal=0,m12=1"; + + +typedef struct PacManChars { + uint16_t pos; + uint8_t size; + uint32_t color; +} pacmancharacters_t; + +#define ORANGEYELLOW (uint32_t)0xFF8800 +#define WHITEISH (uint32_t)0x999999 + +/* +/ Pac-Man (created by making modifications to the Ants effect which was a +* modification of the Rolling Balls effect) - Bob Loeffler - January 2025 +* +* The first slider is for speed. +* 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 not used. +* Checkbox3 not implemented yet. Will be for switching the direction of travel. Enabled will start them going from LED 0 to SEGLEN; disabled will start them going from SEGLEN to 0. +* +*/ +static uint16_t mode_pacman(void) { + //allocate segment data + const uint16_t numGhosts = 4; + uint16_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot + if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed + pacmancharacters_t *character = reinterpret_cast(SEGENV.data); + + unsigned long blinkingPowerdotTimer = strip.now - SEGENV.step; // timer for the blinking power dot + uint8_t 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) { + SEGENV.aux0 = 1; // direction of movement for all characters. 0=ghosts chasing pacman; 1=pacman chasing ghosts + SEGENV.aux1 = 0; // are the ghosts blue? 0=No; 1=Yes + + SEGMENT.fill(WHITEISH); // fill all LEDs with white "dots" (but not bright WHITE dots) + + for (int i = 0; i < 6; i++) { // make all 6 characters (4 ghosts + 1 Pac-Man + 1 Power dot) the same size + character[i].size = 1; // Not used now, but maybe a different size (2 pixels?) in the future? + } + + character[0].color = YELLOW; // Pac-man character + character[0].pos = 10; + + character[1].color = RED; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) + character[1].pos = 6; + + character[2].color = PURPLE; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) + character[2].pos = 4; + + character[3].color = CYAN; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) + character[3].pos = 2; + + character[4].color = ORANGE; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) + character[4].pos = 0; + + character[5].color = ORANGEYELLOW; // orange-ish powerdot (always blinks until it is eaten) + character[5].pos = SEGLEN-1; + SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led + + SEGENV.aux1 = 0; // ghosts are initially not blue + } + + // blink the orange-ish powerdot pixel + if (blinkingPowerdotTimer > 250) { // every 250 milliseconds (1/4th of a sec) + if (character[5].color == ORANGEYELLOW) + character[5].color = BLACK; + else + character[5].color = ORANGEYELLOW; + SEGENV.step = strip.now; + } + + // PacMan is eating the power dot! + if (character[0].pos >= SEGLEN) { + SEGENV.aux0 = 0; // reverse direction for all characters + for (int i=1; i<5; i++) { // For all 4 ghosts... + character[i].color = BLUE; // change their color to blue + } + for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish powerdot + character[i].pos = character[i].pos - 1; + } + SEGENV.aux1 = 1; // ghosts need to be blue now = true/yes + } + + // When the ghosts are blue, and the last ghost is within the first 25% (or 33%) of LEDs in the segment... + if (SEGENV.aux1 == 1 && character[0].pos <= startBlinkingGhostsLED) { + for (int i=1; i<5; i++) { // For all 4 ghosts... + if (character[i].color == BLUE) + character[i].color = BLACK; + else + character[i].color = BLUE; + } + SEGENV.aux1 = 1; // ghosts still blue = true/yes + } + + // when the ghosts are blue and PacMan gets to the beginning of the segment... + if (SEGENV.aux1 == 1 && character[0].pos <= 0) { + for (int i = character[0].pos; i < SEGLEN-1; i++) { // set up the white dots (or not) so PacMan can start eating them again; start at the dot just ahead of PacMan + if (SEGMENT.check1) + SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots + else + SEGMENT.setPixelColor(i, BLACK); // black (no dots) + } + SEGMENT.setPixelColor(SEGLEN-1, ORANGEYELLOW); // draw the orange-ish powerdot in the last led + + SEGENV.aux0 = 1; // reverse direction for all characters (back to normal direction) + character[1].color = RED; // change ghost 1 color back to red + character[2].color = PURPLE; // change ghost 2 color back to purple + character[3].color = CYAN; // change ghost 3 color back to cyan + character[4].color = ORANGE; // change ghost 4 color back to orange + SEGENV.aux1 = 0; // ghosts should not be blue anymore, so set to false + } + + // display everything + if (SEGENV.aux0 == 1) { // going forward from the beginning + SEGMENT.setPixelColor(character[0].pos, character[0].color); // draw PacMan + SEGMENT.setPixelColor(character[0].pos-1, BLACK); + character[0].pos = character[0].pos+1; + + for (int i = 1; i < 5; i++) { // draw the 4 ghosts (and black dots surrounding each ghost) + SEGMENT.setPixelColor(character[i].pos, character[i].color); + SEGMENT.setPixelColor(character[i].pos-1, BLACK); + character[i].pos = character[i].pos+1; + } + + SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the orange-ish powerdot + } + else { // going backward (after PacMan ate the power dot) + SEGMENT.setPixelColor(character[0].pos+1, BLACK); + SEGMENT.setPixelColor(character[0].pos, character[0].color); + SEGMENT.setPixelColor(character[0].pos-1, BLACK); + character[0].pos = character[0].pos-1; + + for (int i = 1; i < 5; i++) { // draw the 4 ghosts (and black dots surrounding each ghost) + SEGMENT.setPixelColor(character[i].pos+1, BLACK); + SEGMENT.setPixelColor(character[i].pos, character[i].color); + SEGMENT.setPixelColor(character[i].pos-1, BLACK); + character[i].pos = character[i].pos-1; + } // do not draw the power dot since PacMan ate it + } + + return 20 + ((22 * (uint32_t)(255 - SEGMENT.speed)) / SEGLEN); // FRAMETIME and FRAMETIME_FIXED were too fast +} +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,Reverse;,,;!;1;m12=1,o1=1"; + + /* * Sinelon stolen from FASTLED examples */ @@ -7822,6 +8096,11 @@ void WS2812FX::setupEffectData() { 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); + // --- Bob L's 1D effects --- + addEffect(FX_MODE_ANTS, &mode_ants, _data_FX_MODE_ANTS); + addEffect(FX_MODE_PACMAN, &mode_pacman, _data_FX_MODE_PACMAN); + //addEffect(FX_MODE_RACERS, &mode_racers, _data_FX_MODE_RACERS); + // --- 1D audio effects --- addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS); addEffect(FX_MODE_PIXELWAVE, &mode_pixelwave, _data_FX_MODE_PIXELWAVE); diff --git a/wled00/FX.h b/wled00/FX.h index 3b1f8f8f15..70e3f6ba0b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -322,8 +322,11 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 +#define FX_MODE_ANTS 187 +#define FX_MODE_PACMAN 188 +//#define FX_MODE_RACERS 189 -#define MODE_COUNT 187 +#define MODE_COUNT 189 #define BLEND_STYLE_FADE 0x00 // universal From 3b22eda795370cfb6bb5a75a67dc6db18ad2e877 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 9 Feb 2025 21:10:18 -0700 Subject: [PATCH 02/19] Lots of things changed in Ants and PacMan, and reverted platformio.ini Ants ---- - Made the number of ants more dynamically user-selectable. - The size of ants can now be up to 20 pixels wide. - Changed random() and random16() to hw_random() and hw_random16(). - Fixed a few suggestions by CodeRabbit TODO: - Foreground and background color selection needs to be improved. The current way of setting the background color is very clunky (with slider #4). PacMan ------ - constexpr instead of const - return mode_static() on short segments (15 or less pixels) - made use of numGhosts intead of hard-coded integers - now using aux0 for binary flags (DIRECTION_FLAG and GHOSTSBLUE_FLAG) - now using aux1 for the timer tick count - reworked timing to draw moving characters and power dot - now returning FRAMETIME correctly TODO: - Speed adjustments? - Transition from previous FX is not looking good. It is supposed to wipe out any pixels by making them black. Then draw whitish dots every other pixel if the White Dots checkbox is enabled. But there are usually colored pixels still displayed from the previous FX. --- platformio.ini | 6 +- wled00/FX.cpp | 176 ++++++++++++++++++++++++++----------------------- 2 files changed, 95 insertions(+), 87 deletions(-) diff --git a/platformio.ini b/platformio.ini index 967ec6b6c6..e775c61f52 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -;default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -18,8 +18,6 @@ build_cache_dir = ~/.buildcache extra_configs = platformio_override.ini -default_envs = esp32dev - [common] # ------------------------------------------------------------------------------ # PLATFORM: @@ -127,7 +125,7 @@ framework = arduino board_build.flash_mode = dout monitor_speed = 115200 # slow upload speed but most compatible (use platformio_override.ini to use faster speed) -upload_speed = 921600 +upload_speed = 115200 # ------------------------------------------------------------------------------ # LIBRARIES: required dependencies diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 6d30129c49..c9955d554c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3122,29 +3122,29 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * https://github.com/Aircoookie/WLED/pull/1039 * * First slider is for the ants' speed. -* Second slider is for the # of ants (1 to 16). -* Third slider is for the Ants' size (1 to x leds) -* Fourth slider is for the background color (black, green, yellow, brown, cyan or white) +* Second slider is for the # of ants. +* Third slider is for the Ants' size. +* * 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) */ static uint16_t mode_ants(void) { //allocate segment data uint32_t bgcolor = BLACK; - uint8_t antSize = 1; - const uint16_t maxNumAnts = 32; // 255/16 + 1 ( * 2 ?????) - uint16_t dataSize = sizeof(rball_t) * maxNumAnts; + static constexpr uint16_t MAX_ANTS = 32; // Maximum number of ants + static constexpr uint8_t DEFAULT_ANT_SIZE = 1; + uint8_t antSize = DEFAULT_ANT_SIZE; + uint16_t dataSize = sizeof(rball_t) * MAX_ANTS; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed int confusedAnt; // the first random ant to go backwards rball_t *ants = reinterpret_cast(SEGENV.data); - // number of ants based on intensity setting to max of 16 (32?) - uint8_t numAnts = SEGMENT.intensity/16 + 1; - if (SEGLEN > 127) - numAnts *= 2; // double the number of ants for longer strips/segments - - antSize = map(SEGMENT.custom1, 0, 255, 1, 5); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider + // number of ants based on intensity setting to max of 32 + uint8_t numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); + if (numAnts > 32) numAnts = MAX_ANTS; + + antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider uint8_t bgColorIdx = map(SEGMENT.custom2, 0, 255, 0, 5); // background color based on the what the user selects from the Background Color slider switch (bgColorIdx) { @@ -3157,13 +3157,13 @@ static uint16_t mode_ants(void) { } if (SEGENV.call == 0) { - confusedAnt = random(0,numAnts-1); - for (int i = 0; i < maxNumAnts; i++) { + confusedAnt = hw_random(0,numAnts-1); + for (int i = 0; i < MAX_ANTS; i++) { ants[i].lastBounceUpdate = strip.now; - ants[i].velocity = 10.0f * float(random16(1000, 5000))/5000.0f; // Random number from 1 to 5 + ants[i].velocity = 10.0f * float(hw_random16(1000, 5000))/5000.0f; // Random number from 1 to 5 if (i == confusedAnt) // make ant[i] go in the opposite direction ants[i].velocity = -ants[i].velocity; - ants[i].height = (float(random16(0, 10000)) / 10000.0f); // Random number from 0 to 1 (used for the position of the ant on the strip) + ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // Random number from 0 to 1 (used for the position of the ant on the strip) } } @@ -3173,10 +3173,10 @@ static uint16_t mode_ants(void) { for (int i = 0; i < numAnts; i++) { // for each Ant, do this... float timeSinceLastUpdate = float((strip.now - ants[i].lastBounceUpdate))/cfac; - float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution + float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution // test if intensity level was increased and some ants are way off the track then put them back if (thisHeight < -0.5f || thisHeight > 1.5f) { - thisHeight = ants[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 + thisHeight = ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 ants[i].lastBounceUpdate = strip.now; } // check if reached past the beginning of the strip. If so, wrap around. @@ -3193,7 +3193,7 @@ static uint16_t mode_ants(void) { } // check for "passing by" or "bumping into" if (!SEGMENT.check3) { // Ants bump into each other and reverse direction if checkbox #3 is not "checked"; they pass each other if "checked" - for (int j = i+1; j < numAnts; j++) { + for (int j = i + 1; j < numAnts; j++) { if (ants[j].velocity != ants[i].velocity) { // tcollided + ants[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions) float tcollided = (cfac*(ants[i].height - ants[j].height) + @@ -3248,25 +3248,25 @@ typedef struct PacManChars { #define ORANGEYELLOW (uint32_t)0xFF8800 #define WHITEISH (uint32_t)0x999999 +#define DIRECTION_FLAG 0b01 +#define GHOSTSBLUE_FLAG 0b10 /* / Pac-Man (created by making modifications to the Ants effect which was a -* modification of the Rolling Balls effect) - Bob Loeffler - January 2025 +* modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 2025 * * The first slider is for speed. * 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 not used. -* Checkbox3 not implemented yet. Will be for switching the direction of travel. Enabled will start them going from LED 0 to SEGLEN; disabled will start them going from SEGLEN to 0. -* +* aux0 is for boolean flags: bit 1 is for the direction of character movement (0=ghosts chasing pacman; 1=pacman chasing ghosts); bit 2 is for whether the ghosts should be blue color (1=yes; 0=no) +* aux1 is the main counter for timing */ static uint16_t mode_pacman(void) { //allocate segment data - const uint16_t numGhosts = 4; - uint16_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot - if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed + constexpr uint16_t numGhosts = 4; + uint16_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot + if (SEGLEN <= 15 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - unsigned long blinkingPowerdotTimer = strip.now - SEGENV.step; // timer for the blinking power dot uint8_t startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking if (SEGLEN > 150) @@ -3275,10 +3275,9 @@ static uint16_t mode_pacman(void) { startBlinkingGhostsLED = SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left if (SEGENV.call == 0) { - SEGENV.aux0 = 1; // direction of movement for all characters. 0=ghosts chasing pacman; 1=pacman chasing ghosts - SEGENV.aux1 = 0; // are the ghosts blue? 0=No; 1=Yes + SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. 1=pacman chasing ghosts - SEGMENT.fill(WHITEISH); // fill all LEDs with white "dots" (but not bright WHITE dots) + SEGMENT.fill(BLACK); // black out all LEDs on the segment for (int i = 0; i < 6; i++) { // make all 6 characters (4 ghosts + 1 Pac-Man + 1 Power dot) the same size character[i].size = 1; // Not used now, but maybe a different size (2 pixels?) in the future? @@ -3299,94 +3298,105 @@ static uint16_t mode_pacman(void) { character[4].color = ORANGE; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) character[4].pos = 0; - character[5].color = ORANGEYELLOW; // orange-ish powerdot (always blinks until it is eaten) + character[5].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) character[5].pos = SEGLEN-1; + + if (SEGMENT.check1) { // set up the white dots (or not) so PacMan can start eating them; start at the dot just ahead of PacMan + for (int i = character[0].pos; i < SEGLEN-1; i++) { + SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots + i++; // skip a dot so the dots are not next to each other + } + } + SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led - - SEGENV.aux1 = 0; // ghosts are initially not blue + + SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // the ghosts are not blue yet, so set this to false (0) } - // blink the orange-ish powerdot pixel - if (blinkingPowerdotTimer > 250) { // every 250 milliseconds (1/4th of a sec) + 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++; + } + + // blink the orange-ish power dot pixel + if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer if (character[5].color == ORANGEYELLOW) character[5].color = BLACK; else character[5].color = ORANGEYELLOW; - SEGENV.step = strip.now; + if (SEGENV.aux0 & DIRECTION_FLAG) + SEGMENT.setPixelColor(character[5].pos, character[5].color); } - // PacMan is eating the power dot! + // PacMan ate the power dot! Chase the ghosts! if (character[0].pos >= SEGLEN) { - SEGENV.aux0 = 0; // reverse direction for all characters - for (int i=1; i<5; i++) { // For all 4 ghosts... - character[i].color = BLUE; // change their color to blue + SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman + for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... + character[i].color = BLUE; // change their color to blue } - for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish powerdot + for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dot character[i].pos = character[i].pos - 1; } - SEGENV.aux1 = 1; // ghosts need to be blue now = true/yes - } - - // When the ghosts are blue, and the last ghost is within the first 25% (or 33%) of LEDs in the segment... - if (SEGENV.aux1 == 1 && character[0].pos <= startBlinkingGhostsLED) { - for (int i=1; i<5; i++) { // For all 4 ghosts... - if (character[i].color == BLUE) - character[i].color = BLACK; - else - character[i].color = BLUE; - } - SEGENV.aux1 = 1; // ghosts still blue = true/yes + SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now = true/yes } // when the ghosts are blue and PacMan gets to the beginning of the segment... - if (SEGENV.aux1 == 1 && character[0].pos <= 0) { + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[0].pos <= 0) { for (int i = character[0].pos; i < SEGLEN-1; i++) { // set up the white dots (or not) so PacMan can start eating them again; start at the dot just ahead of PacMan if (SEGMENT.check1) SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots else SEGMENT.setPixelColor(i, BLACK); // black (no dots) + i++; // skip a dot so the dots are not next to each other } - SEGMENT.setPixelColor(SEGLEN-1, ORANGEYELLOW); // draw the orange-ish powerdot in the last led + SEGMENT.setPixelColor(character[5].pos, character[5].color); // ...draw the orange-ish power dot in the last led - SEGENV.aux0 = 1; // reverse direction for all characters (back to normal direction) + SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=pacman chasing ghosts) character[1].color = RED; // change ghost 1 color back to red character[2].color = PURPLE; // change ghost 2 color back to purple character[3].color = CYAN; // change ghost 3 color back to cyan character[4].color = ORANGE; // change ghost 4 color back to orange - SEGENV.aux1 = 0; // ghosts should not be blue anymore, so set to false + SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // ghosts should not be blue anymore, so set to false } // display everything - if (SEGENV.aux0 == 1) { // going forward from the beginning - SEGMENT.setPixelColor(character[0].pos, character[0].color); // draw PacMan - SEGMENT.setPixelColor(character[0].pos-1, BLACK); - character[0].pos = character[0].pos+1; - - for (int i = 1; i < 5; i++) { // draw the 4 ghosts (and black dots surrounding each ghost) - SEGMENT.setPixelColor(character[i].pos, character[i].color); - SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos = character[i].pos+1; + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts + if (SEGENV.aux0 & DIRECTION_FLAG) { // Going forward from the beginning... + SEGMENT.setPixelColor(character[0].pos, character[0].color); // ...draw PacMan + SEGMENT.setPixelColor(character[0].pos-1, BLACK); + character[0].pos = character[0].pos+1; + + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) + SEGMENT.setPixelColor(character[i].pos, character[i].color); + SEGMENT.setPixelColor(character[i].pos-1, BLACK); + character[i].pos = character[i].pos+1; + } + } + else { // Going backward (after PacMan ate the power dot)... + SEGMENT.setPixelColor(character[0].pos+1, BLACK); + SEGMENT.setPixelColor(character[0].pos, character[0].color); // ...draw PacMan + SEGMENT.setPixelColor(character[0].pos-1, BLACK); + character[0].pos = character[0].pos-1; + + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[0].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts + if (character[i].color == BLUE) + character[i].color = BLACK; + else + character[i].color = BLUE; + } + + SEGMENT.setPixelColor(character[i].pos+1, BLACK); + SEGMENT.setPixelColor(character[i].pos, character[i].color); + SEGMENT.setPixelColor(character[i].pos-1, BLACK); + character[i].pos = character[i].pos-1; + } } - - SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the orange-ish powerdot - } - else { // going backward (after PacMan ate the power dot) - SEGMENT.setPixelColor(character[0].pos+1, BLACK); - SEGMENT.setPixelColor(character[0].pos, character[0].color); - SEGMENT.setPixelColor(character[0].pos-1, BLACK); - character[0].pos = character[0].pos-1; - - for (int i = 1; i < 5; i++) { // draw the 4 ghosts (and black dots surrounding each ghost) - SEGMENT.setPixelColor(character[i].pos+1, BLACK); - SEGMENT.setPixelColor(character[i].pos, character[i].color); - SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos = character[i].pos-1; - } // do not draw the power dot since PacMan ate it } - return 20 + ((22 * (uint32_t)(255 - SEGMENT.speed)) / SEGLEN); // FRAMETIME and FRAMETIME_FIXED were too fast + return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,Reverse;,,;!;1;m12=1,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,;,,;!;1;m12=1,o1=1"; /* From 92fe943a1a6af8dd4b40e9a65d9e442e5b2b8664 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 10 Feb 2025 19:28:32 -0700 Subject: [PATCH 03/19] Removed changes in package-lock.json file --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c81072cfc6..0afeeaafd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.16.0-alpha", + "version": "0.16.0-dev", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.16.0-alpha", + "version": "0.16.0-dev", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", From 655ee452cb13889f1eb70a7cc474987e49a8cf31 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 10 Feb 2025 19:58:29 -0700 Subject: [PATCH 04/19] Re-used 2 old effect IDs. --- wled00/FX.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 70e3f6ba0b..b74b074cd2 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -209,6 +209,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_LAKE 75 #define FX_MODE_METEOR 76 //#define FX_MODE_METEOR_SMOOTH 77 // merged with meteor +#define FX_MODE_ANTS 77 // gap fill #define FX_MODE_RAILWAY 78 #define FX_MODE_RIPPLE 79 #define FX_MODE_TWINKLEFOX 80 @@ -287,6 +288,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 @@ -322,11 +324,9 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 -#define FX_MODE_ANTS 187 -#define FX_MODE_PACMAN 188 -//#define FX_MODE_RACERS 189 +//#define FX_MODE_RACERS 187 -#define MODE_COUNT 189 +#define MODE_COUNT 187 #define BLEND_STYLE_FADE 0x00 // universal From 697b70d75fc39097753cf3cc223541b18d04b677 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 10 Feb 2025 23:10:17 -0700 Subject: [PATCH 05/19] Changed int types in Ants and PacMan --- wled00/FX.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c9955d554c..c59960ddf6 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3131,17 +3131,17 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b static uint16_t mode_ants(void) { //allocate segment data uint32_t bgcolor = BLACK; - static constexpr uint16_t MAX_ANTS = 32; // Maximum number of ants - static constexpr uint8_t DEFAULT_ANT_SIZE = 1; - uint8_t antSize = DEFAULT_ANT_SIZE; - uint16_t dataSize = sizeof(rball_t) * MAX_ANTS; + static constexpr unsigned MAX_ANTS = 32; // Maximum number of ants + static constexpr unsigned DEFAULT_ANT_SIZE = 1; + unsigned antSize = DEFAULT_ANT_SIZE; + unsigned dataSize = sizeof(rball_t) * MAX_ANTS; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed int confusedAnt; // the first random ant to go backwards rball_t *ants = reinterpret_cast(SEGENV.data); // number of ants based on intensity setting to max of 32 - uint8_t numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); + unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); if (numAnts > 32) numAnts = MAX_ANTS; antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider @@ -3224,7 +3224,7 @@ static uint16_t mode_ants(void) { if (thisHeight < 0.0f) thisHeight = 0.0f; if (thisHeight > 1.0f) thisHeight = 1.0f; - uint16_t pos = round(thisHeight * (SEGLEN - 1)); + unsigned pos = round(thisHeight * (SEGLEN - 1)); for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 5 pixels) SEGMENT.setPixelColor(pos, color); @@ -3241,8 +3241,8 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s typedef struct PacManChars { - uint16_t pos; - uint8_t size; + unsigned pos; + unsigned size; uint32_t color; } pacmancharacters_t; @@ -3262,12 +3262,12 @@ typedef struct PacManChars { */ static uint16_t mode_pacman(void) { //allocate segment data - constexpr uint16_t numGhosts = 4; - uint16_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot + constexpr unsigned numGhosts = 4; + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot if (SEGLEN <= 15 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - uint8_t startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking + 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 From bf4e5eb3702624c4dc9b7a29f2aad6abe50d4a71 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Fri, 14 Feb 2025 00:20:51 -0700 Subject: [PATCH 06/19] Ant effect: color selection is much better --- wled00/FX.cpp | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c59960ddf6..30fd94b0c3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3124,38 +3124,27 @@ 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. -* +* Checkbox1 is for using the palettes (enabled) or the color slots (disabled) * 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) */ static uint16_t mode_ants(void) { //allocate segment data - uint32_t bgcolor = BLACK; static constexpr unsigned MAX_ANTS = 32; // Maximum number of ants static constexpr unsigned DEFAULT_ANT_SIZE = 1; unsigned antSize = DEFAULT_ANT_SIZE; unsigned dataSize = sizeof(rball_t) * MAX_ANTS; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed int confusedAnt; // the first random ant to go backwards rball_t *ants = reinterpret_cast(SEGENV.data); - // number of ants based on intensity setting to max of 32 + // number of ants based on intensity setting (max of 32) unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); if (numAnts > 32) numAnts = MAX_ANTS; - antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider + antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider - uint8_t bgColorIdx = map(SEGMENT.custom2, 0, 255, 0, 5); // background color based on the what the user selects from the Background Color slider - switch (bgColorIdx) { - case 0: bgcolor = BLACK; break; // black - case 1: bgcolor = 0x007700; break; // med-dark green - case 2: bgcolor = 0x9c7a00; break; // dark yellow - case 3: bgcolor = 0x845000; break; // brown - case 4: bgcolor = 0x00aaaa; break; // cyan-ish - case 5: bgcolor = 0x999999; break; // light gray (whitish) - } - if (SEGENV.call == 0) { confusedAnt = hw_random(0,numAnts-1); for (int i = 0; i < MAX_ANTS; i++) { @@ -3169,8 +3158,8 @@ static uint16_t mode_ants(void) { float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider - if (!SEGMENT.check2) SEGMENT.fill(bgcolor); // fill all LEDs with background color - + if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); // fill all LEDs with background color (Bg) if the user didn't select Overlay checkbox + for (int i = 0; i < numAnts; i++) { // for each Ant, do this... float timeSinceLastUpdate = float((strip.now - ants[i].lastBounceUpdate))/cfac; float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution @@ -3215,18 +3204,23 @@ static uint16_t mode_ants(void) { } } - uint32_t color = SEGCOLOR(0); - if (SEGMENT.palette) { - color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP, 0); - } else { - color = SEGCOLOR(i % NUM_COLORS); + uint32_t color; + if (SEGMENT.check1) { // if the Palette checkbox is selected, use palette colors + color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP,255); + } + else { // otherwise, use the 2 selectable color slots (Fx and Cs); not Bg as that's the background color + unsigned coloridx = i % 3; + if (coloridx == 1) + color = SEGCOLOR(0); // color Fx + else + color = SEGCOLOR(2); // color Cs } if (thisHeight < 0.0f) thisHeight = 0.0f; if (thisHeight > 1.0f) thisHeight = 1.0f; unsigned pos = round(thisHeight * (SEGLEN - 1)); - for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 5 pixels) + for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 20 pixels) SEGMENT.setPixelColor(pos, color); pos = pos + 1; } @@ -3237,7 +3231,7 @@ static uint16_t mode_ants(void) { return FRAMETIME; } -static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,Background color,,,Overlay,Pass by;1,2,3;!;1;sx=192,ix=255,c1=80,c2=0,pal=0,m12=1"; +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Palettes,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; typedef struct PacManChars { From 9107fae686e20d116d1a8225ce84a7dc5e76cbb6 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Fri, 14 Feb 2025 01:11:57 -0700 Subject: [PATCH 07/19] Ant effect: final change for color selection --- wled00/FX.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 30fd94b0c3..0be615b8e3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3124,7 +3124,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. -* Checkbox1 is for using the palettes (enabled) or the color slots (disabled) +* * 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) */ @@ -3205,10 +3205,10 @@ static uint16_t mode_ants(void) { } uint32_t color; - if (SEGMENT.check1) { // if the Palette checkbox is selected, use palette colors + if (SEGMENT.palette != 0 ) { // if a Palette is selected (besides the Default palette), use the palette's colors color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP,255); } - else { // otherwise, use the 2 selectable color slots (Fx and Cs); not Bg as that's the background color + else { // otherwise, Default palette selected; use the 2 selectable color slots (Fx and Cs) unsigned coloridx = i % 3; if (coloridx == 1) color = SEGCOLOR(0); // color Fx @@ -3231,7 +3231,7 @@ static uint16_t mode_ants(void) { return FRAMETIME; } -static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Palettes,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; typedef struct PacManChars { From 9ce00b66053e2de11044be0b4349cbbcbc430f2e Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 15 Feb 2025 20:56:05 -0700 Subject: [PATCH 08/19] Ant effect: new option to gather food --- wled00/FX.cpp | 115 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 29 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0be615b8e3..98c7a55ead 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3020,9 +3020,9 @@ static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravit * https://github.com/Aircoookie/WLED/pull/1039 */ // modified for balltrack mode -typedef struct RollingBall { // used for rolling_balls and Ants modes +typedef struct RollingBall { unsigned long lastBounceUpdate; - float mass; // could fix this to be = 1. if memory is an issue // mass not used in ants mode + float mass; // could fix this to be = 1. if memory is an issue float velocity; float height; } rball_t; @@ -3115,36 +3115,48 @@ static uint16_t rolling_balls(void) { static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collisions,Overlay,Trails;!,!,!;!;1;m12=1"; //bar +typedef struct Ants { + unsigned long lastBounceUpdate; + bool hasFood; + float velocity; + float height; +} ant_t; /* / Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - January 2025 -* bouncing balls on a track track Effect modified from Aircoookie's bouncing balls +* bouncing balls on a track track Effect modified from Aircoookie's bouncing ballsccccccccccccccccccccccccccc * 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. * 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) */ static uint16_t mode_ants(void) { //allocate segment data + uint32_t bgcolor = BLACK; static constexpr unsigned MAX_ANTS = 32; // Maximum number of ants static constexpr unsigned DEFAULT_ANT_SIZE = 1; unsigned antSize = DEFAULT_ANT_SIZE; - unsigned dataSize = sizeof(rball_t) * MAX_ANTS; + unsigned dataSize = sizeof(ant_t) * MAX_ANTS; if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed int confusedAnt; // the first random ant to go backwards - rball_t *ants = reinterpret_cast(SEGENV.data); + ant_t *ants = reinterpret_cast(SEGENV.data); - // number of ants based on intensity setting (max of 32) - unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); - if (numAnts > 32) numAnts = MAX_ANTS; + unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); // number of ants based on intensity setting + if (numAnts > 32) numAnts = MAX_ANTS; // max of 32 ants - antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider + bool passBy = SEGMENT.check3; // see if the user wants the ants to pass by each other without colliding with them + antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider + if (SEGMENT.check1) // if checkbox 1 (Gather food) is enabled, add one pixel to the ant size to make it look like food is in it's mouth. + antSize += 1; + if (SEGENV.call == 0) { confusedAnt = hw_random(0,numAnts-1); for (int i = 0; i < MAX_ANTS; i++) { @@ -3158,30 +3170,59 @@ static uint16_t mode_ants(void) { float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider - if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); // fill all LEDs with background color (Bg) if the user didn't select Overlay checkbox + if (!SEGMENT.check2) { + bgcolor = SEGCOLOR(1); + SEGMENT.fill(bgcolor); // fill all LEDs with background color (Bg) if the user didn't select Overlay checkbox + } - for (int i = 0; i < numAnts; i++) { // for each Ant, do this... + for (int i = 0; i < numAnts; i++) { // for each Ant, do this... float timeSinceLastUpdate = float((strip.now - ants[i].lastBounceUpdate))/cfac; - float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution + float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution + // test if intensity level was increased and some ants are way off the track then put them back if (thisHeight < -0.5f || thisHeight > 1.5f) { - thisHeight = ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 + thisHeight = ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 ants[i].lastBounceUpdate = strip.now; } - // check if reached past the beginning of the strip. If so, wrap around. + + // check if reached past the beginning of the strip. if (thisHeight <= 0.0f && ants[i].velocity < 0.0f) { - thisHeight = 1.0f; - ants[i].lastBounceUpdate = strip.now; - ants[i].height = thisHeight; + if (SEGMENT.check1) { // if looking for food, stop and go back the other way + thisHeight = 0.0f; + ants[i].velocity = -ants[i].velocity; // reverse direction + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + ants[i].hasFood = true; // found food + passBy = true; // when looking for food, pass by other ants without bumping into them + SEGMENT.check3 = true; // when looking for food, pass by other ants without bumping into them + } + else { // If not looking for food, wrap around + thisHeight = 1.0f; + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + } } - // check if reached past the end of the strip. If so, wrap around. + + // check if reached past the end of the strip. if (thisHeight >= 1.0f && ants[i].velocity > 0.0f) { - thisHeight = 0.0f; - ants[i].lastBounceUpdate = strip.now; - ants[i].height = thisHeight; + if (SEGMENT.check1) { // if looking for food, stop and go back the other way + thisHeight = 1.0f; + ants[i].velocity = -ants[i].velocity; // reverse direction + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + ants[i].hasFood = false; // dropped off the food, now going back for more + passBy = true; // when looking for food, pass by other ants without bumping into them + SEGMENT.check3 = true; // when looking for food, pass by other ants without bumping into them + } + else { // If not looking for food, wrap around + thisHeight = 0.0f; + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + } } - // check for "passing by" or "bumping into" - if (!SEGMENT.check3) { // Ants bump into each other and reverse direction if checkbox #3 is not "checked"; they pass each other if "checked" + + // check for "passing by" or "bumping into" other ants + if (!passBy) { // Ants bump into each other and reverse direction if checkbox #3 (Pass by) is not "checked"; they pass each other if "checked" for (int j = i + 1; j < numAnts; j++) { if (ants[j].velocity != ants[i].velocity) { // tcollided + ants[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions) @@ -3208,7 +3249,7 @@ static uint16_t mode_ants(void) { if (SEGMENT.palette != 0 ) { // if a Palette is selected (besides the Default palette), use the palette's colors color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP,255); } - else { // otherwise, Default palette selected; use the 2 selectable color slots (Fx and Cs) + else { // ...otherwise, Default palette selected; use the 2 selectable color slots (Fx and Cs) unsigned coloridx = i % 3; if (coloridx == 1) color = SEGCOLOR(0); // color Fx @@ -3220,8 +3261,24 @@ static uint16_t mode_ants(void) { if (thisHeight > 1.0f) thisHeight = 1.0f; unsigned pos = round(thisHeight * (SEGLEN - 1)); - for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 20 pixels) - SEGMENT.setPixelColor(pos, color); + for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 20 pixels) + if (ants[i].velocity < 0) { + if (z == 0 && SEGMENT.check1 && ants[i].hasFood) { // make the food pixel white, but if the ant is white, make the food pixel yellow; + if (color == WHITE) // ...but if the bg is yellow, make the food pixel gray. If the bg is white, make it yellow. + color = bgcolor==YELLOW?GRAY:YELLOW; + else + color = bgcolor==WHITE?YELLOW:WHITE; + } + } + else { // velocity > 0 + if (z == antSize-1 && SEGMENT.check1 && ants[i].hasFood) { // make the food pixel white, but if the ant is white, make the food pixel yellow; + if (color == WHITE) // ...but if the bg is yellow, make the food pixel gray. If the bg is white, make it yellow. + color = bgcolor==YELLOW?GRAY:YELLOW; + else + color = bgcolor==WHITE?YELLOW:WHITE; + } + } + SEGMENT.setPixelColor(pos, color); // draw the pixel with the correct color pos = pos + 1; } @@ -3231,12 +3288,12 @@ static uint16_t mode_ants(void) { return FRAMETIME; } -static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; +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"; typedef struct PacManChars { unsigned pos; - unsigned size; + unsigned size; uint32_t color; } pacmancharacters_t; From 63cc5c0618474b8f7eff1b5e30f95d5f6e3abef7 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 16 Feb 2025 14:04:46 -0700 Subject: [PATCH 09/19] PacMan effect: Transition from previous effect is fixed --- wled00/FX.cpp | 77 +++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 98c7a55ead..d9834bc1be 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3122,7 +3122,7 @@ typedef struct Ants { float height; } ant_t; /* -/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - January 2025 +/ 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 ballsccccccccccccccccccccccccccc * Courtesy of pjhatch (https://github.com/pjhatch) * https://github.com/Aircoookie/WLED/pull/1039 @@ -3132,7 +3132,7 @@ typedef struct 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. +* 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) */ @@ -3301,7 +3301,7 @@ typedef struct PacManChars { #define WHITEISH (uint32_t)0x999999 #define DIRECTION_FLAG 0b01 #define GHOSTSBLUE_FLAG 0b10 - +#define PACMAN 0 // the PacMan character is character[0] /* / Pac-Man (created by making modifications to the Ants effect which was a * modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 2025 @@ -3328,14 +3328,12 @@ static uint16_t mode_pacman(void) { if (SEGENV.call == 0) { SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. 1=pacman chasing ghosts - SEGMENT.fill(BLACK); // black out all LEDs on the segment - for (int i = 0; i < 6; i++) { // make all 6 characters (4 ghosts + 1 Pac-Man + 1 Power dot) the same size character[i].size = 1; // Not used now, but maybe a different size (2 pixels?) in the future? } - character[0].color = YELLOW; // Pac-man character - character[0].pos = 10; + character[PACMAN].color = YELLOW; // Pac-man character[0] + character[PACMAN].pos = 10; character[1].color = RED; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) character[1].pos = 6; @@ -3352,13 +3350,6 @@ static uint16_t mode_pacman(void) { character[5].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) character[5].pos = SEGLEN-1; - if (SEGMENT.check1) { // set up the white dots (or not) so PacMan can start eating them; start at the dot just ahead of PacMan - for (int i = character[0].pos; i < SEGLEN-1; i++) { - SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots - i++; // skip a dot so the dots are not next to each other - } - } - SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // the ghosts are not blue yet, so set this to false (0) @@ -3369,21 +3360,42 @@ static uint16_t mode_pacman(void) { SEGENV.aux1++; } + // draw white dots (or black LEDs) so PacMan can start eating them + if (SEGENV.aux0 & DIRECTION_FLAG && character[PACMAN].pos > 0) { + for (int i = character[4].pos; i > 0; i--) { // black out LEDs behind the last ghost (character[4]) in case they are on (transition from previous effect) + SEGMENT.setPixelColor(i-2, BLACK); + } + + if (SEGMENT.check1) { // White Dots option is selected, so draw white dots (and black LEDs between them) in front of PacMan + for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character + SEGMENT.setPixelColor(i, WHITEISH); // white dots + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip a dot so the dots are not next to each other + } + } + else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan + for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character + SEGMENT.setPixelColor(i, BLACK); // black LEDS only + } + }; + } + // blink the orange-ish power dot pixel if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer if (character[5].color == ORANGEYELLOW) character[5].color = BLACK; else character[5].color = ORANGEYELLOW; - if (SEGENV.aux0 & DIRECTION_FLAG) - SEGMENT.setPixelColor(character[5].pos, character[5].color); + + if (SEGENV.aux0 & DIRECTION_FLAG) + SEGMENT.setPixelColor(character[5].pos, character[5].color); } // PacMan ate the power dot! Chase the ghosts! - if (character[0].pos >= SEGLEN) { + if (character[PACMAN].pos >= SEGLEN) { SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... - character[i].color = BLUE; // change their color to blue + character[i].color = BLUE; // ...change their color to blue } for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dot character[i].pos = character[i].pos - 1; @@ -3392,15 +3404,8 @@ static uint16_t mode_pacman(void) { } // when the ghosts are blue and PacMan gets to the beginning of the segment... - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[0].pos <= 0) { - for (int i = character[0].pos; i < SEGLEN-1; i++) { // set up the white dots (or not) so PacMan can start eating them again; start at the dot just ahead of PacMan - if (SEGMENT.check1) - SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots - else - SEGMENT.setPixelColor(i, BLACK); // black (no dots) - i++; // skip a dot so the dots are not next to each other - } - SEGMENT.setPixelColor(character[5].pos, character[5].color); // ...draw the orange-ish power dot in the last led + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= 0) { + SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the orange-ish power dot in the last led SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=pacman chasing ghosts) character[1].color = RED; // change ghost 1 color back to red @@ -3410,12 +3415,12 @@ static uint16_t mode_pacman(void) { SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // ghosts should not be blue anymore, so set to false } - // display everything + // display the characters if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts if (SEGENV.aux0 & DIRECTION_FLAG) { // Going forward from the beginning... - SEGMENT.setPixelColor(character[0].pos, character[0].color); // ...draw PacMan - SEGMENT.setPixelColor(character[0].pos-1, BLACK); - character[0].pos = character[0].pos+1; + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan + SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); + character[0].pos = character[PACMAN].pos+1; for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) SEGMENT.setPixelColor(character[i].pos, character[i].color); @@ -3424,13 +3429,13 @@ static uint16_t mode_pacman(void) { } } else { // Going backward (after PacMan ate the power dot)... - SEGMENT.setPixelColor(character[0].pos+1, BLACK); - SEGMENT.setPixelColor(character[0].pos, character[0].color); // ...draw PacMan - SEGMENT.setPixelColor(character[0].pos-1, BLACK); + SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan + SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); character[0].pos = character[0].pos-1; for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[0].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts if (character[i].color == BLUE) character[i].color = BLACK; else @@ -3447,7 +3452,7 @@ static uint16_t mode_pacman(void) { return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,;,,;!;1;m12=1,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White dots,,;,,;!;1;m12=1,o1=1"; /* From 60cee95f1b678479ec71a5ad801afd4e3120cc8b Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 16 Mar 2025 17:41:28 -0700 Subject: [PATCH 10/19] PacMan effect: Added another power dot, but... --- wled00/FX.cpp | 97 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 17 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5b3739868c..f265f74e91 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3124,7 +3124,7 @@ typedef struct Ants { } ant_t; /* / 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 ballsccccccccccccccccccccccccccc +* 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 * @@ -3313,13 +3313,16 @@ typedef struct PacManChars { * aux1 is the main counter for timing */ static uint16_t mode_pacman(void) { - //allocate segment data constexpr unsigned numGhosts = 4; - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot + constexpr unsigned numPowerDots = 2; + //allocate segment data + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + numPowerDots); // 4 ghosts + 1 Pac-Man + 2 Power dots if (SEGLEN <= 15 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed pacmancharacters_t *character = reinterpret_cast(SEGENV.data); unsigned startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking + bool LastPowerDotEaten; + bool FirstPowerDotEaten; if (SEGLEN > 150) startBlinkingGhostsLED = SEGLEN/4; // For longer strips, start blinking the ghosts when there is only 1/4th of the LEDs left @@ -3329,7 +3332,7 @@ static uint16_t mode_pacman(void) { if (SEGENV.call == 0) { SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. 1=pacman chasing ghosts - for (int i = 0; i < 6; i++) { // make all 6 characters (4 ghosts + 1 Pac-Man + 1 Power dot) the same size + for (int i = 0; i < 7; i++) { // make all 6 characters (4 ghosts + 1 Pac-Man + 2 Power dots) the same size character[i].size = 1; // Not used now, but maybe a different size (2 pixels?) in the future? } @@ -3349,11 +3352,17 @@ static uint16_t mode_pacman(void) { character[4].pos = 0; character[5].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) - character[5].pos = SEGLEN-1; + character[5].pos = SEGLEN-1; // last pixel of the segment + + character[6].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) + character[6].pos = SEGLEN/2; // pixel in the center of the segment SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led + SEGMENT.setPixelColor(character[6].pos, character[6].color); // draw the second power dot in the middle of the segment SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // the ghosts are not blue yet, so set this to false (0) + FirstPowerDotEaten = false; + LastPowerDotEaten = false; } if (strip.now > SEGENV.step) { @@ -3367,20 +3376,33 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(i-2, BLACK); } - if (SEGMENT.check1) { // White Dots option is selected, so draw white dots (and black LEDs between them) in front of PacMan - for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character - SEGMENT.setPixelColor(i, WHITEISH); // white dots - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot - i--; // skip a dot so the dots are not next to each other + if (SEGMENT.check1) { // White Dots option is selected, so draw white dots (and black LEDs between them) in front of PacMan + if (!FirstPowerDotEaten) { + for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character + if (i != character[6].pos) // don't draw a white dot where the middle power dot is + SEGMENT.setPixelColor(i, WHITEISH); // white dots + if ((i-1) != character[6].pos) // don't draw a white dot if it's next to where the middle power dot is + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip a dot so the dots are not next to each other + } + } + else { + for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character + SEGMENT.setPixelColor(i, WHITEISH); // white dots + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip a dot so the dots are not next to each other + } } } else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character - SEGMENT.setPixelColor(i, BLACK); // black LEDS only + if (i != character[6].pos || i != character[6].pos+1) + SEGMENT.setPixelColor(i, BLACK); // black LEDS only, but not the power dot in the middle of the segment } }; } + /* // blink the orange-ish power dot pixel if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer if (character[5].color == ORANGEYELLOW) @@ -3391,22 +3413,58 @@ static uint16_t mode_pacman(void) { if (SEGENV.aux0 & DIRECTION_FLAG) SEGMENT.setPixelColor(character[5].pos, character[5].color); } + */ - // PacMan ate the power dot! Chase the ghosts! - if (character[PACMAN].pos >= SEGLEN) { + // blink the orange-ish power dot pixels + if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer + if (character[5].color == ORANGEYELLOW) + character[5].color = BLACK; + else + character[5].color = ORANGEYELLOW; + + if ((LastPowerDotEaten && SEGENV.aux0 & DIRECTION_FLAG) || (!LastPowerDotEaten && SEGENV.aux0 | ~DIRECTION_FLAG)) // draw/blink last power dot only if it has not been eaten yet + SEGMENT.setPixelColor(character[5].pos, character[5].color); + + if (character[6].color == ORANGEYELLOW) + character[6].color = BLACK; + else + character[6].color = ORANGEYELLOW; + + if ((SEGENV.aux0 & DIRECTION_FLAG) && !FirstPowerDotEaten) // draw/blink the first power dot (in the middle of the segment) only if it has not been eaten yet + SEGMENT.setPixelColor(character[6].pos, character[6].color); + } + + // PacMan ate the first power dot! Chase the ghosts! + if ((character[PACMAN].pos >= character[6].pos) && !FirstPowerDotEaten) { SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... character[i].color = BLUE; // ...change their color to blue } - for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dot + for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dots + character[i].pos = character[i].pos - 1; + } + SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now = true/yes + FirstPowerDotEaten = true; + } + + // PacMan ate the last power dot! Chase the ghosts! + if (character[PACMAN].pos >= SEGLEN) { + SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman + for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... + character[i].color = GREEN; // BLUE; // ...change their color to blue + } + for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dots character[i].pos = character[i].pos - 1; } SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now = true/yes + LastPowerDotEaten = true; } // when the ghosts are blue and PacMan gets to the beginning of the segment... if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= 0) { SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the orange-ish power dot in the last led + if (!FirstPowerDotEaten) + SEGMENT.setPixelColor(character[6].pos, character[6].color); // draw the orange-ish power dot in the middle led if it has not been eaten yet SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=pacman chasing ghosts) character[1].color = RED; // change ghost 1 color back to red @@ -3414,6 +3472,11 @@ static uint16_t mode_pacman(void) { character[3].color = CYAN; // change ghost 3 color back to cyan character[4].color = ORANGE; // change ghost 4 color back to orange SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // ghosts should not be blue anymore, so set to false + + if (LastPowerDotEaten) { // if the last power dot was eaten and we are at the beginning of the segment, + FirstPowerDotEaten = false; // set our power dot flags back to false (so neither has been eaten) + LastPowerDotEaten = false; // and then we will redraw all of the white dots and power dots. + } } // display the characters @@ -3421,7 +3484,7 @@ static uint16_t mode_pacman(void) { if (SEGENV.aux0 & DIRECTION_FLAG) { // Going forward from the beginning... SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); - character[0].pos = character[PACMAN].pos+1; + character[PACMAN].pos = character[PACMAN].pos+1; for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) SEGMENT.setPixelColor(character[i].pos, character[i].color); @@ -3433,7 +3496,7 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); - character[0].pos = character[0].pos-1; + character[PACMAN].pos = character[PACMAN].pos-1; for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts @@ -3453,7 +3516,7 @@ static uint16_t mode_pacman(void) { return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White dots,,;,,;!;1;m12=1,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White dots,,;,,;!;1;m12=0,o1=1"; /* From 8abf155cd0354e811945e5b971185f78e3267e18 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Mar 2025 19:34:46 -0700 Subject: [PATCH 11/19] PacMan effect: Second powerdot is working now, and other fixes. --- wled00/FX.cpp | 220 +++++++++++++++++++++++++------------------------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f265f74e91..f231a661bf 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3116,12 +3116,6 @@ 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 -typedef struct Ants { - unsigned long lastBounceUpdate; - bool hasFood; - float velocity; - float height; -} ant_t; /* / 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 @@ -3137,6 +3131,13 @@ typedef struct Ants { * 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 Ants { + unsigned long lastBounceUpdate; + bool hasFood; + float velocity; + float height; +} ant_t; + static uint16_t mode_ants(void) { //allocate segment data uint32_t bgcolor = BLACK; @@ -3291,38 +3292,44 @@ static uint16_t mode_ants(void) { } 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"; - +/* +/ Pac-Man (created by making modifications to the Ants effect which was a +* modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 2025 +* +* The first slider is for speed. +* 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). +* aux0 is for boolean flags: +* bit 1 is for the direction of character movement (0=ghosts chasing PacMan; 1=PacMan chasing ghosts) +* bit 2 is for whether the ghosts should be blue color (1=yes; 0=no) +* bit 3 is for whether the first power dot was eaten yet (1=yes; 0=no) +* bit 4 is for whether the last power dot was eaten yet (1=yes; 0=no) +* aux1 is the main counter for timing +*/ typedef struct PacManChars { unsigned pos; + unsigned topPos; unsigned size; uint32_t color; } pacmancharacters_t; #define ORANGEYELLOW (uint32_t)0xFF8800 #define WHITEISH (uint32_t)0x999999 -#define DIRECTION_FLAG 0b01 -#define GHOSTSBLUE_FLAG 0b10 + +#define DIRECTION_FLAG 0b0001 +#define GHOSTSBLUE_FLAG 0b0010 +#define FIRSTPWRDOTEATEN_FLAG 0b0100 +#define LASTPWRDOTEATEN_FLAG 0b1000 #define PACMAN 0 // the PacMan character is character[0] -/* -/ Pac-Man (created by making modifications to the Ants effect which was a -* modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 2025 -* -* The first slider is for speed. -* 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). -* aux0 is for boolean flags: bit 1 is for the direction of character movement (0=ghosts chasing pacman; 1=pacman chasing ghosts); bit 2 is for whether the ghosts should be blue color (1=yes; 0=no) -* aux1 is the main counter for timing -*/ + static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; constexpr unsigned numPowerDots = 2; //allocate segment data - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + numPowerDots); // 4 ghosts + 1 Pac-Man + 2 Power dots + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + numPowerDots); // 4 ghosts + 1 PacMan + 2 Power dots if (SEGLEN <= 15 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - unsigned startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking - bool LastPowerDotEaten; - bool FirstPowerDotEaten; + 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 @@ -3330,14 +3337,13 @@ static uint16_t mode_pacman(void) { startBlinkingGhostsLED = SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left if (SEGENV.call == 0) { - SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. 1=pacman chasing ghosts - - for (int i = 0; i < 7; i++) { // make all 6 characters (4 ghosts + 1 Pac-Man + 2 Power dots) the same size - character[i].size = 1; // Not used now, but maybe a different size (2 pixels?) in the future? + for (int i = 0; i < 7; i++) { // make all 6 characters (4 ghosts + 1 PacMan + 2 Power dots) the same size + character[i].size = 1; // Not actually used now, but maybe a different size (2 pixels?) in the future? } - character[PACMAN].color = YELLOW; // Pac-man character[0] - character[PACMAN].pos = 10; + character[PACMAN].color = YELLOW; // PacMan character[0] + character[PACMAN].pos = 10; + character[PACMAN].topPos = character[PACMAN].pos; // Top position (highest LED on the segment) reached by the PacMan character character[1].color = RED; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) character[1].pos = 6; @@ -3360,156 +3366,150 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led SEGMENT.setPixelColor(character[6].pos, character[6].color); // draw the second power dot in the middle of the segment + SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. bit=1 (true = PacMan chases ghosts) SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // the ghosts are not blue yet, so set this to false (0) - FirstPowerDotEaten = false; - LastPowerDotEaten = false; + SEGENV.aux0 = SEGENV.aux0 & ~FIRSTPWRDOTEATEN_FLAG; // the first powerdot has not been eaten yet, so set this to false (0) + SEGENV.aux0 = SEGENV.aux0 & ~LASTPWRDOTEATEN_FLAG; // the last powerdot has not been eaten yet, so set this to false (0) } 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; // "+ 100" creates a very jerky movement as the characters jump ahead several pixels each time they move SEGENV.aux1++; } // draw white dots (or black LEDs) so PacMan can start eating them - if (SEGENV.aux0 & DIRECTION_FLAG && character[PACMAN].pos > 0) { - for (int i = character[4].pos; i > 0; i--) { // black out LEDs behind the last ghost (character[4]) in case they are on (transition from previous effect) + if ((SEGENV.aux0 & DIRECTION_FLAG) && (character[PACMAN].pos > 0)) { + for (int i = character[4].pos; i > 1; i--) { // black out LEDs behind the last ghost (character[4]) in case they are on (transition from previous effect) SEGMENT.setPixelColor(i-2, BLACK); } - if (SEGMENT.check1) { // White Dots option is selected, so draw white dots (and black LEDs between them) in front of PacMan - if (!FirstPowerDotEaten) { - for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character - if (i != character[6].pos) // don't draw a white dot where the middle power dot is - SEGMENT.setPixelColor(i, WHITEISH); // white dots - if ((i-1) != character[6].pos) // don't draw a white dot if it's next to where the middle power dot is - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot - i--; // skip a dot so the dots are not next to each other - } - } - else { - for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character - SEGMENT.setPixelColor(i, WHITEISH); // white dots - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot - i--; // skip a dot so the dots are not next to each other - } + if (SEGMENT.check1) { // White Dots option is selected, so draw white dots (and black LEDs between them) in front of PacMan + for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { + SEGMENT.setPixelColor(i, WHITEISH); // white dots + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip a dot so the dots are not next to each other } } else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan - for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character - if (i != character[6].pos || i != character[6].pos+1) - SEGMENT.setPixelColor(i, BLACK); // black LEDS only, but not the power dot in the middle of the segment + for (int i = SEGLEN-1; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character + SEGMENT.setPixelColor(i, BLACK); // black LEDS only } }; } - /* - // blink the orange-ish power dot pixel - if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer - if (character[5].color == ORANGEYELLOW) - character[5].color = BLACK; - else - character[5].color = ORANGEYELLOW; - - if (SEGENV.aux0 & DIRECTION_FLAG) - SEGMENT.setPixelColor(character[5].pos, character[5].color); - } - */ - - // blink the orange-ish power dot pixels - if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer + // blink the orange-ish power dot LEDs + if (SEGENV.aux1 % 10 == 0) { // blink power dots every 10 ticks of the ticker timer + // blink last power dot (at the end of the segment) only if it has not been eaten yet if (character[5].color == ORANGEYELLOW) character[5].color = BLACK; else character[5].color = ORANGEYELLOW; - - if ((LastPowerDotEaten && SEGENV.aux0 & DIRECTION_FLAG) || (!LastPowerDotEaten && SEGENV.aux0 | ~DIRECTION_FLAG)) // draw/blink last power dot only if it has not been eaten yet - SEGMENT.setPixelColor(character[5].pos, character[5].color); + // blink the first power dot (in the middle of the segment) only if it has not been eaten yet if (character[6].color == ORANGEYELLOW) character[6].color = BLACK; else character[6].color = ORANGEYELLOW; - - if ((SEGENV.aux0 & DIRECTION_FLAG) && !FirstPowerDotEaten) // draw/blink the first power dot (in the middle of the segment) only if it has not been eaten yet - SEGMENT.setPixelColor(character[6].pos, character[6].color); } + /* + if (SEGENV.aux1 % 5 == 0) { // blink blue ghosts every 5 ticks of the ticker timer + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && (character[PACMAN].pos <= startBlinkingGhostsLED)) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) + if (character[i].color == BLUE) + character[i].color = BLACK; + else + character[i].color = BLUE; + } + } + } + */ + + // blink last power dot (at the end of the segment) only if it has not been eaten yet + if (((character[PACMAN].topPos < character[5].pos) && (SEGENV.aux0 & DIRECTION_FLAG)) || ((character[PACMAN].topPos < character[5].pos) && (SEGENV.aux0 | ~DIRECTION_FLAG))) + SEGMENT.setPixelColor(character[5].pos, character[5].color); + + // blink the first power dot (in the middle of the segment) only if it has not been eaten yet + if ((SEGENV.aux0 & DIRECTION_FLAG) && (character[PACMAN].topPos < character[6].pos)) + SEGMENT.setPixelColor(character[6].pos, character[6].color); + // PacMan ate the first power dot! Chase the ghosts! - if ((character[PACMAN].pos >= character[6].pos) && !FirstPowerDotEaten) { - SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman - for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... - character[i].color = BLUE; // ...change their color to blue + if ((character[PACMAN].pos >= character[6].pos) && (character[PACMAN].topPos <= character[6].pos)) { + if (SEGENV.aux0 & FIRSTPWRDOTEATEN_FLAG) { // TODO: not sure why I have to do it like this instead of: | ~FIRSTPWRDOTEATEN_FLAG + ; } - for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dots - character[i].pos = character[i].pos - 1; + else { + SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing PacMan + for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... + character[i].color = BLUE; // change their color to blue + } + SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now, so set to true/yes + SEGENV.aux0 = SEGENV.aux0 | FIRSTPWRDOTEATEN_FLAG; // first powerdot was eaten, so set to true/yes } - SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now = true/yes - FirstPowerDotEaten = true; } // PacMan ate the last power dot! Chase the ghosts! - if (character[PACMAN].pos >= SEGLEN) { - SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman + if (character[PACMAN].pos >= character[5].pos) { + SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing PacMan for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... - character[i].color = GREEN; // BLUE; // ...change their color to blue - } - for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dots - character[i].pos = character[i].pos - 1; + character[i].color = BLUE; // change their color to blue } - SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now = true/yes - LastPowerDotEaten = true; + SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now, so set to true/yes + SEGENV.aux0 = SEGENV.aux0 | LASTPWRDOTEATEN_FLAG; // last powerdot was eaten, so set to true/yes } // when the ghosts are blue and PacMan gets to the beginning of the segment... - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= 0) { - SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the orange-ish power dot in the last led - if (!FirstPowerDotEaten) - SEGMENT.setPixelColor(character[6].pos, character[6].color); // draw the orange-ish power dot in the middle led if it has not been eaten yet - - SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=pacman chasing ghosts) + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && (character[PACMAN].pos <= 0)) { + SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=PacMan chasing ghosts) character[1].color = RED; // change ghost 1 color back to red character[2].color = PURPLE; // change ghost 2 color back to purple character[3].color = CYAN; // change ghost 3 color back to cyan character[4].color = ORANGE; // change ghost 4 color back to orange SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // ghosts should not be blue anymore, so set to false - if (LastPowerDotEaten) { // if the last power dot was eaten and we are at the beginning of the segment, - FirstPowerDotEaten = false; // set our power dot flags back to false (so neither has been eaten) - LastPowerDotEaten = false; // and then we will redraw all of the white dots and power dots. + if (SEGENV.aux0 & LASTPWRDOTEATEN_FLAG) { // if the last power dot was eaten (and we are at the beginning of the segment) + SEGENV.aux0 = SEGENV.aux0 & ~FIRSTPWRDOTEATEN_FLAG; // set our power dot flags back to false (so neither has been eaten) + SEGENV.aux0 = SEGENV.aux0 & ~LASTPWRDOTEATEN_FLAG; // so we can redraw all of the white dots and power dots. + character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 } } // display the characters - if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts - if (SEGENV.aux0 & DIRECTION_FLAG) { // Going forward from the beginning... - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan + if (SEGENV.aux0 & DIRECTION_FLAG) { // Going forward from the beginning... + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); - character[PACMAN].pos = character[PACMAN].pos+1; + character[PACMAN].pos = character[PACMAN].pos+1; // update position for the next frame draw + + if (character[PACMAN].topPos < character[PACMAN].pos) // keep track of the top (farthest) position of the PacMan character + character[PACMAN].topPos = character[PACMAN].pos; for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) SEGMENT.setPixelColor(character[i].pos, character[i].color); SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos = character[i].pos+1; + character[i].pos = character[i].pos+1; // update their positions forwards for the next frame draw } } - else { // Going backward (after PacMan ate the power dot)... - SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan + } + else { // Going backward (after PacMan ate a power dot)... + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts + SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); // TODO: On odd-numbered segments, this will cause a white dot to be deleted next to the first powerdot when it's eaten; not an easy fix. + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); - character[PACMAN].pos = character[PACMAN].pos-1; + character[PACMAN].pos = character[PACMAN].pos-1; // update position for the next frame draw for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts + // if the ghost is blue and nearing the beginning of the strip, blink the ghosts. TODO: The ghosts blink too fast or too slow. Not sure how to fix it. + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && (character[PACMAN].pos <= startBlinkingGhostsLED)) { if (character[i].color == BLUE) character[i].color = BLACK; else character[i].color = BLUE; } - SEGMENT.setPixelColor(character[i].pos+1, BLACK); SEGMENT.setPixelColor(character[i].pos, character[i].color); SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos = character[i].pos-1; + character[i].pos = character[i].pos-1; // update their positions backwards for the next frame draw } } } From d8bbf261fa1a110583258fe09c8dd1b173594d46 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 23 Mar 2025 23:50:16 -0700 Subject: [PATCH 12/19] PacMan effect: Added the Compact Dots feature --- wled00/FX.cpp | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f231a661bf..a70f04c707 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3297,7 +3297,8 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s * modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 2025 * * The first slider is for speed. -* 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). +* 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 that PacMan eats. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. * aux0 is for boolean flags: * bit 1 is for the direction of character movement (0=ghosts chasing PacMan; 1=PacMan chasing ghosts) * bit 2 is for whether the ghosts should be blue color (1=yes; 0=no) @@ -3308,7 +3309,6 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s typedef struct PacManChars { unsigned pos; unsigned topPos; - unsigned size; uint32_t color; } pacmancharacters_t; @@ -3326,7 +3326,7 @@ static uint16_t mode_pacman(void) { constexpr unsigned numPowerDots = 2; //allocate segment data unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + numPowerDots); // 4 ghosts + 1 PacMan + 2 Power dots - if (SEGLEN <= 15 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed + if (SEGLEN <= 27 || !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 @@ -3337,10 +3337,6 @@ static uint16_t mode_pacman(void) { 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 < 7; i++) { // make all 6 characters (4 ghosts + 1 PacMan + 2 Power dots) the same size - character[i].size = 1; // Not actually used now, but maybe a different size (2 pixels?) in the future? - } - character[PACMAN].color = YELLOW; // PacMan character[0] character[PACMAN].pos = 10; character[PACMAN].topPos = character[PACMAN].pos; // Top position (highest LED on the segment) reached by the PacMan character @@ -3383,11 +3379,18 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(i-2, BLACK); } - if (SEGMENT.check1) { // White Dots option is selected, so draw white dots (and black LEDs between them) in front of PacMan - for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { - SEGMENT.setPixelColor(i, WHITEISH); // white dots - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot - i--; // skip a dot so the dots are not next to each other + if (SEGMENT.check1) { // If White Dots option is selected, draw white dots (and black LEDs between them) in front of PacMan + if (SEGMENT.check2) { // If Compact Dots option is selected, draw white dots without black LEDs between them (only works if White Dots is also selected) + for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { + SEGMENT.setPixelColor(i, WHITEISH); // white dots + } + } + else { // draw white dots with black LEDs between them + for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { + SEGMENT.setPixelColor(i, WHITEISH); // white dots + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip a dot so the dots are not next to each other + } } } else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan @@ -3412,19 +3415,6 @@ static uint16_t mode_pacman(void) { character[6].color = ORANGEYELLOW; } - /* - if (SEGENV.aux1 % 5 == 0) { // blink blue ghosts every 5 ticks of the ticker timer - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && (character[PACMAN].pos <= startBlinkingGhostsLED)) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts - for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) - if (character[i].color == BLUE) - character[i].color = BLACK; - else - character[i].color = BLUE; - } - } - } - */ - // blink last power dot (at the end of the segment) only if it has not been eaten yet if (((character[PACMAN].topPos < character[5].pos) && (SEGENV.aux0 & DIRECTION_FLAG)) || ((character[PACMAN].topPos < character[5].pos) && (SEGENV.aux0 | ~DIRECTION_FLAG))) SEGMENT.setPixelColor(character[5].pos, character[5].color); @@ -3516,7 +3506,7 @@ static uint16_t mode_pacman(void) { return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White dots,,;,,;!;1;m12=0,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White dots,Compact dots,;,,;!;1;m12=0,o1=1"; /* From d5cbb0ab2f8801ea4a8ed770d3b38a99cc0e60ac Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 30 Mar 2025 12:49:42 -0700 Subject: [PATCH 13/19] PacMan effect: User can select number of Power Dots now, but... --- wled00/FX.cpp | 248 ++++++++++++++++++++++++++++---------------------- 1 file changed, 139 insertions(+), 109 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a70f04c707..d6945076ac 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3116,6 +3116,7 @@ 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 + /* / 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 @@ -3224,7 +3225,7 @@ static uint16_t mode_ants(void) { } // check for "passing by" or "bumping into" other ants - if (!passBy) { // Ants bump into each other and reverse direction if checkbox #3 (Pass by) is not "checked"; they pass each other if "checked" + if (!passBy) { // Ants bump into each other and some reverse direction if checkbox #3 (Pass by) is not "checked"; they pass each other if "checked" for (int j = i + 1; j < numAnts; j++) { if (ants[j].velocity != ants[i].velocity) { // tcollided + ants[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions) @@ -3281,7 +3282,7 @@ static uint16_t mode_ants(void) { } } SEGMENT.setPixelColor(pos, color); // draw the pixel with the correct color - pos = pos + 1; + pos += 1; } ants[i].lastBounceUpdate = strip.now; @@ -3292,40 +3293,40 @@ static uint16_t mode_ants(void) { } 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"; + /* / Pac-Man (created by making modifications to the Ants effect which was a -* modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 2025 +* modification of the Rolling Balls effect) - Bob Loeffler - January - March 2025 * * 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 that PacMan eats. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. -* aux0 is for boolean flags: -* bit 1 is for the direction of character movement (0=ghosts chasing PacMan; 1=PacMan chasing ghosts) -* bit 2 is for whether the ghosts should be blue color (1=yes; 0=no) -* bit 3 is for whether the first power dot was eaten yet (1=yes; 0=no) -* bit 4 is for whether the last power dot was eaten yet (1=yes; 0=no) * aux1 is the main counter for timing */ typedef struct PacManChars { - unsigned pos; - unsigned topPos; - uint32_t color; + unsigned pos; // is for the LED position of the character (all characters) + unsigned 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 blinking; // is for whether the character should be blinking or not (ghosts and power dots) + bool eaten; // is for whether the power dot was eaten or not (power dots only) } pacmancharacters_t; #define ORANGEYELLOW (uint32_t)0xFF8800 #define WHITEISH (uint32_t)0x999999 - -#define DIRECTION_FLAG 0b0001 -#define GHOSTSBLUE_FLAG 0b0010 -#define FIRSTPWRDOTEATEN_FLAG 0b0100 -#define LASTPWRDOTEATEN_FLAG 0b1000 -#define PACMAN 0 // the PacMan character is character[0] +#define PACMAN 0 // PacMan is character[0] static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; - constexpr unsigned numPowerDots = 2; + static constexpr unsigned MAX_POWERDOTS = 5; // Maximum number of power dots (this may change to 10 or more in the future) + //unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, 5); // number of Power Dots (between 1 and 5) based on intensity slider setting + + unsigned numPowerDots = 5; // for troubleshooting only + //allocate segment data - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + numPowerDots); // 4 ghosts + 1 PacMan + 2 Power dots + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + MAX_POWERDOTS); // 4 ghosts + 1 PacMan + max number of Power dots if (SEGLEN <= 27 || !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); @@ -3337,35 +3338,62 @@ static uint16_t mode_pacman(void) { startBlinkingGhostsLED = SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left if (SEGENV.call == 0) { - character[PACMAN].color = YELLOW; // PacMan character[0] - character[PACMAN].pos = 10; + // 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 + character[PACMAN].direction = true; // initial direction of movement. true = PacMan chases ghosts + + // 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 + character[1].direction = true; // initial direction of movement. true = PacMan chases ghosts + character[1].blue = false; // the ghosts are not blue yet, so set this to false + character[1].blinking = false; // the ghosts are not blinking yet, so set this to false + + // Ghost character + character[2].color = 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 + character[2].direction = true; // initial direction of movement. true = PacMan chases ghosts + character[2].blue = false; // the ghosts are not blue yet, so set this to false + character[2].blinking = false; // the ghosts are not blinking yet, so set this to false + + // 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 + character[3].direction = true; // initial direction of movement. true = PacMan chases ghosts + character[3].blue = false; // the ghosts are not blue yet, so set this to false + character[3].blinking = false; // the ghosts are not blinking yet, so set this to false + + // Ghost character + character[4].color = 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 + character[4].direction = true; // initial direction of movement. true = PacMan chases ghosts + character[4].blue = false; // the ghosts are not blue yet, so set this to false + character[4].blinking = false; // the ghosts are not blinking yet, so set this to false + + // Power dot at the end of the segment (aka Last Power Dot) + character[5].color = ORANGEYELLOW; // always blinks until it is eaten + character[5].pos = SEGLEN-1; // last pixel of the segment. will not move. + character[5].blinking = true; // the last power dot blinks initially, so set this to true + character[5].eaten = false; // initially not eaten yet, so set this to false + SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led - character[1].color = RED; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) - character[1].pos = 6; - - character[2].color = PURPLE; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) - character[2].pos = 4; - - character[3].color = CYAN; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) - character[3].pos = 2; - - character[4].color = ORANGE; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color) - character[4].pos = 0; - - character[5].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) - character[5].pos = SEGLEN-1; // last pixel of the segment + // additional power dots + if (numPowerDots > 1) { + for (int i = 1; i < numPowerDots; i++) { + character[i+5].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) - character[6].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) - character[6].pos = SEGLEN/2; // pixel in the center of the segment + //float mycalc = SEGLEN / (numPowerDots / i); + //character[i+5].pos = static_cast(round(mycalc)); - SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led - SEGMENT.setPixelColor(character[6].pos, character[6].color); // draw the second power dot in the middle of the segment + character[i+5].pos = i*30; // a power dot every 30 pixels for troubleshooting // SEGLEN / (numPowerDots / i); // position of this power dot. will not move. - SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. bit=1 (true = PacMan chases ghosts) - SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // the ghosts are not blue yet, so set this to false (0) - SEGENV.aux0 = SEGENV.aux0 & ~FIRSTPWRDOTEATEN_FLAG; // the first powerdot has not been eaten yet, so set this to false (0) - SEGENV.aux0 = SEGENV.aux0 & ~LASTPWRDOTEATEN_FLAG; // the last powerdot has not been eaten yet, so set this to false (0) + character[i+5].blinking = true; // the first power dot blinks initially, so set this to true + character[i+5].eaten = false; // initially not eaten yet, so set this to false + SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); // draw the second power dot in the middle of the segment + } + } } if (strip.now > SEGENV.step) { @@ -3374,21 +3402,15 @@ static uint16_t mode_pacman(void) { } // draw white dots (or black LEDs) so PacMan can start eating them - if ((SEGENV.aux0 & DIRECTION_FLAG) && (character[PACMAN].pos > 0)) { - for (int i = character[4].pos; i > 1; i--) { // black out LEDs behind the last ghost (character[4]) in case they are on (transition from previous effect) + if (character[PACMAN].direction && (character[PACMAN].pos > 0)) { + for (int i = character[4].pos; i > 1; i--) // black out LEDs behind the last ghost (character[4]) in case they are on (transition from previous effect) SEGMENT.setPixelColor(i-2, BLACK); - } - if (SEGMENT.check1) { // If White Dots option is selected, draw white dots (and black LEDs between them) in front of PacMan - if (SEGMENT.check2) { // If Compact Dots option is selected, draw white dots without black LEDs between them (only works if White Dots is also selected) - for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { - SEGMENT.setPixelColor(i, WHITEISH); // white dots - } - } - else { // draw white dots with black LEDs between them - for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { - SEGMENT.setPixelColor(i, WHITEISH); // white dots - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + if (SEGMENT.check1) { // If White Dots option is selected, draw white dots (and maybe black LEDs between them) in front of PacMan + for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { + SEGMENT.setPixelColor(i, WHITEISH); // white dots + if (!SEGMENT.check2) { // If Compact Dots option is not selected, draw white dots with black LEDs between them (only works if White Dots is also selected) + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot i--; // skip a dot so the dots are not next to each other } } @@ -3400,113 +3422,121 @@ static uint16_t mode_pacman(void) { }; } - // blink the orange-ish power dot LEDs - if (SEGENV.aux1 % 10 == 0) { // blink power dots every 10 ticks of the ticker timer - // blink last power dot (at the end of the segment) only if it has not been eaten yet - if (character[5].color == ORANGEYELLOW) - character[5].color = BLACK; - else - character[5].color = ORANGEYELLOW; - - // blink the first power dot (in the middle of the segment) only if it has not been eaten yet - if (character[6].color == ORANGEYELLOW) - character[6].color = BLACK; - else - character[6].color = ORANGEYELLOW; + // blink power dots every 10 ticks of the ticker timer + if (SEGENV.aux1 % 10 == 0) { + for (int i = 0; i < numPowerDots; i++) { + if (character[i+5].color == ORANGEYELLOW) + character[i+5].color = BLACK; + else + character[i+5].color = ORANGEYELLOW; + } } // blink last power dot (at the end of the segment) only if it has not been eaten yet - if (((character[PACMAN].topPos < character[5].pos) && (SEGENV.aux0 & DIRECTION_FLAG)) || ((character[PACMAN].topPos < character[5].pos) && (SEGENV.aux0 | ~DIRECTION_FLAG))) - SEGMENT.setPixelColor(character[5].pos, character[5].color); + if (((character[PACMAN].topPos < character[5].pos) && character[PACMAN].direction) || ((character[PACMAN].topPos < character[5].pos) && !character[PACMAN].direction)) + SEGMENT.setPixelColor(character[5].pos, character[5].color); - // blink the first power dot (in the middle of the segment) only if it has not been eaten yet - if ((SEGENV.aux0 & DIRECTION_FLAG) && (character[PACMAN].topPos < character[6].pos)) - SEGMENT.setPixelColor(character[6].pos, character[6].color); + // blink the other power dots in the segment only if they have not been eaten yet + for (int i = 0; i < numPowerDots-1; i++) { + if ((character[PACMAN].topPos < character[i+6].pos) && !character[i+6].eaten) + SEGMENT.setPixelColor(character[i+6].pos, character[i+6].color); + } - // PacMan ate the first power dot! Chase the ghosts! - if ((character[PACMAN].pos >= character[6].pos) && (character[PACMAN].topPos <= character[6].pos)) { - if (SEGENV.aux0 & FIRSTPWRDOTEATEN_FLAG) { // TODO: not sure why I have to do it like this instead of: | ~FIRSTPWRDOTEATEN_FLAG - ; - } - else { - SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing PacMan - for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... - character[i].color = BLUE; // change their color to blue + + // PacMan ate one of the power dots (but not the last one)! Chase the ghosts! + for (int j = 0; j < numPowerDots-1; j++) { + if ((character[PACMAN].pos >= character[j+6].pos) && (character[PACMAN].topPos <= character[j+6].pos)) { + if (!character[j+6].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+6].eaten = true; // first powerdot was eaten, so set to true } - SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now, so set to true/yes - SEGENV.aux0 = SEGENV.aux0 | FIRSTPWRDOTEATEN_FLAG; // first powerdot was eaten, so set to true/yes } } - // PacMan ate the last power dot! Chase the ghosts! + // PacMan ate the last power dot! Chase the ghosts again! if (character[PACMAN].pos >= character[5].pos) { - SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing PacMan + 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].color = BLUE; // change their color to blue + character[i].blue = true; // ghosts are now blue, so set to true } - SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now, so set to true/yes - SEGENV.aux0 = SEGENV.aux0 | LASTPWRDOTEATEN_FLAG; // last powerdot was eaten, so set to true/yes + character[5].eaten = true; // last powerdot was eaten, so set to true } // when the ghosts are blue and PacMan gets to the beginning of the segment... - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && (character[PACMAN].pos <= 0)) { - SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=PacMan chasing ghosts) + 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 = PURPLE; // change ghost 2 color back to purple character[3].color = CYAN; // change ghost 3 color back to cyan character[4].color = ORANGE; // change ghost 4 color back to orange - SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // ghosts should not be blue anymore, so set to false - if (SEGENV.aux0 & LASTPWRDOTEATEN_FLAG) { // if the last power dot was eaten (and we are at the beginning of the segment) - SEGENV.aux0 = SEGENV.aux0 & ~FIRSTPWRDOTEATEN_FLAG; // set our power dot flags back to false (so neither has been eaten) - SEGENV.aux0 = SEGENV.aux0 & ~LASTPWRDOTEATEN_FLAG; // so we can redraw all of the white dots and power dots. - character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 + 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.aux0 & DIRECTION_FLAG) { // Going forward from the beginning... + if (character[PACMAN].direction) { // Going forward from the beginning of the segment... if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan - SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); - character[PACMAN].pos = character[PACMAN].pos+1; // update position for the next frame draw + SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); // and the black dot behind him + character[PACMAN].pos += 1; // update PacMan's position forwards for the next frame draw if (character[PACMAN].topPos < character[PACMAN].pos) // keep track of the top (farthest) position of the PacMan character character[PACMAN].topPos = character[PACMAN].pos; - for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) + for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and the black dot behind each ghost) SEGMENT.setPixelColor(character[i].pos, character[i].color); SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos = character[i].pos+1; // update their positions forwards for the next frame draw + character[i].pos += 1; // update their positions forwards for the next frame draw } } } else { // Going backward (after PacMan ate a power dot)... if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts - SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); // TODO: On odd-numbered segments, this will cause a white dot to be deleted next to the first powerdot when it's eaten; not an easy fix. + SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); // TODO: This causes a white dot to be deleted right after the powerdot when it's eaten; not sure how to fix it. SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan - SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); - character[PACMAN].pos = character[PACMAN].pos-1; // update position for the next frame draw + SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); // and the black dot behind him + character[PACMAN].pos -= 1; // update PacMan's position backwards for the next frame draw for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) - // if the ghost is blue and nearing the beginning of the strip, blink the ghosts. TODO: The ghosts blink too fast or too slow. Not sure how to fix it. - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && (character[PACMAN].pos <= startBlinkingGhostsLED)) { + + // if the ghost is blue and nearing the beginning of the strip, blink the ghosts. TODO: The ghosts blink too fast or too slow, depending on the Speed slider's value; not sure how to fix it. + if (character[1].blue && (character[PACMAN].pos <= startBlinkingGhostsLED)) { if (character[i].color == BLUE) character[i].color = BLACK; else character[i].color = BLUE; } + SEGMENT.setPixelColor(character[i].pos+1, BLACK); SEGMENT.setPixelColor(character[i].pos, character[i].color); SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos = character[i].pos-1; // update their positions backwards for the next frame draw + character[i].pos -= 1; // update their positions backwards for the next frame draw } } } return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White dots,Compact dots,;,,;!;1;m12=0,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,,,,White dots,Compact dots,;,,;!;1;m12=0,o1=1"; /* From f5e64778826e3094a2310f7655f98828560cac65 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 14 Apr 2025 00:06:58 -0700 Subject: [PATCH 14/19] PacMan effect: Code cleanup --- wled00/FX.cpp | 139 +++++++++++++++++++------------------------------- 1 file changed, 53 insertions(+), 86 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d6945076ac..0671911252 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3310,7 +3310,6 @@ typedef struct PacManChars { 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 blinking; // is for whether the character should be blinking or not (ghosts and power dots) bool eaten; // is for whether the power dot was eaten or not (power dots only) } pacmancharacters_t; @@ -3321,78 +3320,60 @@ typedef struct PacManChars { static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; static constexpr unsigned MAX_POWERDOTS = 5; // Maximum number of power dots (this may change to 10 or more in the future) + //unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, 5); // number of Power Dots (between 1 and 5) based on intensity slider setting - unsigned numPowerDots = 5; // for troubleshooting only + unsigned everyXLeds = round(SEGLEN / numPowerDots); + //allocate segment data unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + MAX_POWERDOTS); // 4 ghosts + 1 PacMan + max number of Power dots if (SEGLEN <= 27 || !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 - character[PACMAN].direction = true; // initial direction of movement. true = PacMan chases ghosts // 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 - character[1].direction = true; // initial direction of movement. true = PacMan chases ghosts - character[1].blue = false; // the ghosts are not blue yet, so set this to false - character[1].blinking = false; // the ghosts are not blinking yet, so set this to false // Ghost character character[2].color = 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 - character[2].direction = true; // initial direction of movement. true = PacMan chases ghosts - character[2].blue = false; // the ghosts are not blue yet, so set this to false - character[2].blinking = false; // the ghosts are not blinking yet, so set this to false // 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 - character[3].direction = true; // initial direction of movement. true = PacMan chases ghosts - character[3].blue = false; // the ghosts are not blue yet, so set this to false - character[3].blinking = false; // the ghosts are not blinking yet, so set this to false // Ghost character character[4].color = 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 - character[4].direction = true; // initial direction of movement. true = PacMan chases ghosts - character[4].blue = false; // the ghosts are not blue yet, so set this to false - character[4].blinking = false; // the ghosts are not blinking yet, so set this to false - - // Power dot at the end of the segment (aka Last Power Dot) - character[5].color = ORANGEYELLOW; // always blinks until it is eaten - character[5].pos = SEGLEN-1; // last pixel of the segment. will not move. - character[5].blinking = true; // the last power dot blinks initially, so set this to true - character[5].eaten = false; // initially not eaten yet, so set this to false - SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led - - // additional power dots - if (numPowerDots > 1) { - for (int i = 1; i < numPowerDots; i++) { - character[i+5].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) - - //float mycalc = SEGLEN / (numPowerDots / i); - //character[i+5].pos = static_cast(round(mycalc)); - - character[i+5].pos = i*30; // a power dot every 30 pixels for troubleshooting // SEGLEN / (numPowerDots / i); // position of this power dot. will not move. - - character[i+5].blinking = true; // the first power dot blinks initially, so set this to true - character[i+5].eaten = false; // initially not eaten yet, so set this to false - SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); // draw the second power dot in the middle of the segment - } + + // Power dots + for (int i = 0; i < numPowerDots; i++) { + character[i+5].color = ORANGEYELLOW; // orange-ish power dots + + if (i == 0) + character[i+5].pos = SEGLEN-1; // put the first power dot at the end of the segment + else + character[i+5].pos = i * everyXLeds; // and additional power dots every X LEDs/pixels + + character[i+5].eaten = false; // initially not eaten yet, so set this to false + SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); // draw the power dots } } @@ -3401,28 +3382,31 @@ static uint16_t mode_pacman(void) { SEGENV.aux1++; } + // black out LEDs behind the last ghost (character[4]) in case they are on from a previous effect (transition) + for (int i = character[4].pos; i > 1; i--) + SEGMENT.setPixelColor(i-2, BLACK); + // draw white dots (or black LEDs) so PacMan can start eating them - if (character[PACMAN].direction && (character[PACMAN].pos > 0)) { - for (int i = character[4].pos; i > 1; i--) // black out LEDs behind the last ghost (character[4]) in case they are on (transition from previous effect) - SEGMENT.setPixelColor(i-2, BLACK); - - if (SEGMENT.check1) { // If White Dots option is selected, draw white dots (and maybe black LEDs between them) in front of PacMan - for (int i = SEGLEN-1; i > character[PACMAN].topPos; i--) { - SEGMENT.setPixelColor(i, WHITEISH); // white dots - if (!SEGMENT.check2) { // If Compact Dots option is not selected, draw white dots with black LEDs between them (only works if White Dots is also selected) - SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot - i--; // skip a dot so the dots are not next to each other - } + 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 } } - else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan - for (int i = SEGLEN-1; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character - SEGMENT.setPixelColor(i, BLACK); // black LEDS only - } - }; } + else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan + if (character[PACMAN].direction) { + for (int i = SEGLEN-1; i > character[PACMAN].pos; i--) // start at the end of the segment and draw black LEDs to the PacMan character + SEGMENT.setPixelColor(i, BLACK); + } + else // this is needed so we don't draw a black LED on top of the PacMan character (it was causing a flicker of the PacMan LED when chasing the ghosts) + for (int i = SEGLEN-1; i > character[PACMAN].pos+1; i--) // start at the end of the segment and draw black LEDs to the LED just before the PacMan character + SEGMENT.setPixelColor(i, BLACK); + }; - // blink power dots every 10 ticks of the ticker timer + // blink power dots every 10 ticks of the ticker timer by changing their color between orangish color and black if (SEGENV.aux1 % 10 == 0) { for (int i = 0; i < numPowerDots; i++) { if (character[i+5].color == ORANGEYELLOW) @@ -3432,45 +3416,28 @@ static uint16_t mode_pacman(void) { } } - // blink last power dot (at the end of the segment) only if it has not been eaten yet - if (((character[PACMAN].topPos < character[5].pos) && character[PACMAN].direction) || ((character[PACMAN].topPos < character[5].pos) && !character[PACMAN].direction)) - SEGMENT.setPixelColor(character[5].pos, character[5].color); - - // blink the other power dots in the segment only if they have not been eaten yet - for (int i = 0; i < numPowerDots-1; i++) { - if ((character[PACMAN].topPos < character[i+6].pos) && !character[i+6].eaten) - SEGMENT.setPixelColor(character[i+6].pos, character[i+6].color); + // 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 - // PacMan ate one of the power dots (but not the last one)! Chase the ghosts! - for (int j = 0; j < numPowerDots-1; j++) { - if ((character[PACMAN].pos >= character[j+6].pos) && (character[PACMAN].topPos <= character[j+6].pos)) { - if (!character[j+6].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 + 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+6].eaten = true; // first powerdot was eaten, so set to true + character[j+5].eaten = true; // powerdot was eaten, so set to true } } } - // PacMan ate the last power dot! Chase the ghosts again! - if (character[PACMAN].pos >= character[5].pos) { - 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[5].eaten = true; // last 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) From 052c9f8bc7f88ff8de63b48eed34cb826a730094 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 14 Apr 2025 19:57:37 +0200 Subject: [PATCH 15/19] some bugfixes, added dynamic max power dots --- wled00/FX.cpp | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0671911252..087d5aab0f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3319,19 +3319,19 @@ typedef struct PacManChars { static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; - static constexpr unsigned MAX_POWERDOTS = 5; // Maximum number of power dots (this may change to 10 or more in the future) + unsigned maxPowerDots = SEGLEN / 5; // Maximum number of power dots depends on segment length, max is 1 every 5 Pixels - //unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, 5); // number of Power Dots (between 1 and 5) based on intensity slider setting - unsigned numPowerDots = 5; // for troubleshooting only + unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); // number of Power Dots (between 1 and 5) based on intensity slider setting - unsigned everyXLeds = round(SEGLEN / numPowerDots); + //update power dots + unsigned everyXLeds = (SEGLEN - 10) / numPowerDots; //allocate segment data - unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + MAX_POWERDOTS); // 4 ghosts + 1 PacMan + max number of Power dots + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + maxPowerDots); // 4 ghosts + 1 PacMan + max number of Power dots if (SEGLEN <= 27 || !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 + 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 @@ -3343,7 +3343,7 @@ static uint16_t mode_pacman(void) { character[i].blue = false; } // PacMan character[0] - character[PACMAN].color = YELLOW; + 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 @@ -3363,22 +3363,18 @@ static uint16_t mode_pacman(void) { character[4].color = 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 dots - for (int i = 0; i < numPowerDots; i++) { - character[i+5].color = ORANGEYELLOW; // orange-ish power dots - - if (i == 0) - character[i+5].pos = SEGLEN-1; // put the first power dot at the end of the segment - else - character[i+5].pos = i * everyXLeds; // and additional power dots every X LEDs/pixels + // 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; // orange-ish power dots character[i+5].eaten = false; // initially not eaten yet, so set this to false - SEGMENT.setPixelColor(character[i+5].pos, character[i+5].color); // draw the power dots } } 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; // "+ 100" creates a very jerky movement as the characters jump ahead several pixels each time they move SEGENV.aux1++; } @@ -3388,7 +3384,7 @@ 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 @@ -3406,13 +3402,19 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(i, BLACK); }; + // update power dot positions: can change if user selects a different number of power dots + for (int i = 1; i < maxPowerDots; i++) { + character[i+5].pos = 10 + i * everyXLeds; // 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) { - for (int i = 0; i < numPowerDots; i++) { - if (character[i+5].color == ORANGEYELLOW) - character[i+5].color = BLACK; + if (character[5].color == ORANGEYELLOW) + character[5].color = BLACK; else - character[i+5].color = ORANGEYELLOW; + 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 } } From e67e1e91f0875a417abbe23bb45614063f28e751 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Fri, 18 Apr 2025 13:33:59 -0700 Subject: [PATCH 16/19] Both effects: Bug fixes and cleanup --- wled00/FX.cpp | 154 ++++++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 66 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 087d5aab0f..c10a1785d9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3141,65 +3141,63 @@ typedef struct Ants { static uint16_t mode_ants(void) { //allocate segment data - uint32_t bgcolor = BLACK; - static constexpr unsigned MAX_ANTS = 32; // Maximum number of ants - static constexpr unsigned DEFAULT_ANT_SIZE = 1; + uint32_t bgcolor = SEGCOLOR(1); + constexpr unsigned MAX_ANTS = 32; // Maximum number of ants + constexpr unsigned DEFAULT_ANT_SIZE = 1; unsigned antSize = DEFAULT_ANT_SIZE; unsigned dataSize = sizeof(ant_t) * MAX_ANTS; - if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed + if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed - int confusedAnt; // the first random ant to go backwards + int confusedAnt; // the first random ant to go backwards ant_t *ants = reinterpret_cast(SEGENV.data); - unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); // number of ants based on intensity setting - if (numAnts > 32) numAnts = MAX_ANTS; // max of 32 ants + unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); // number of ants based on intensity setting + if (numAnts > 32) numAnts = MAX_ANTS; // max of 32 ants - bool passBy = SEGMENT.check3; // see if the user wants the ants to pass by each other without colliding with them + bool passBy = SEGMENT.check3; // see if the user wants the ants to pass by each other without colliding with them - antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider - if (SEGMENT.check1) // if checkbox 1 (Gather food) is enabled, add one pixel to the ant size to make it look like food is in it's mouth. + antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider + if (SEGMENT.check1) // if checkbox 1 (Gather food) is enabled, add one pixel to the ant size to make it look like food is in it's mouth. antSize += 1; if (SEGENV.call == 0) { confusedAnt = hw_random(0,numAnts-1); for (int i = 0; i < MAX_ANTS; i++) { ants[i].lastBounceUpdate = strip.now; - ants[i].velocity = 10.0f * float(hw_random16(1000, 5000))/5000.0f; // Random number from 1 to 5 - if (i == confusedAnt) // make ant[i] go in the opposite direction + ants[i].velocity = 10.0f * float(hw_random16(1000, 5000))/5000.0f; // Random number from 1 to 5 + if (i == confusedAnt) // make ant[i] go in the opposite direction ants[i].velocity = -ants[i].velocity; - ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // Random number from 0 to 1 (used for the position of the ant on the strip) + ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // Random number from 0 to 1 (used for the position of the ant on the strip) } } - float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider + float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider - if (!SEGMENT.check2) { - bgcolor = SEGCOLOR(1); - SEGMENT.fill(bgcolor); // fill all LEDs with background color (Bg) if the user didn't select Overlay checkbox + if (!SEGMENT.check2) { // if Overlay option is not selected... + SEGMENT.fill(bgcolor); // fill all LEDs with background color (Bg) } - for (int i = 0; i < numAnts; i++) { // for each Ant, do this... + for (int i = 0; i < numAnts; i++) { // for each Ant, do this... float timeSinceLastUpdate = float((strip.now - ants[i].lastBounceUpdate))/cfac; - float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution + float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution // test if intensity level was increased and some ants are way off the track then put them back if (thisHeight < -0.5f || thisHeight > 1.5f) { - thisHeight = ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 + thisHeight = ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 ants[i].lastBounceUpdate = strip.now; } // check if reached past the beginning of the strip. if (thisHeight <= 0.0f && ants[i].velocity < 0.0f) { - if (SEGMENT.check1) { // if looking for food, stop and go back the other way + if (SEGMENT.check1) { // if looking for food, stop and go back the other way thisHeight = 0.0f; - ants[i].velocity = -ants[i].velocity; // reverse direction + ants[i].velocity = -ants[i].velocity; // reverse direction ants[i].lastBounceUpdate = strip.now; ants[i].height = thisHeight; - ants[i].hasFood = true; // found food - passBy = true; // when looking for food, pass by other ants without bumping into them - SEGMENT.check3 = true; // when looking for food, pass by other ants without bumping into them + ants[i].hasFood = true; // found food + passBy = true; // when looking for food, pass by other ants without bumping into them } - else { // If not looking for food, wrap around + else { // If not looking for food, wrap around thisHeight = 1.0f; ants[i].lastBounceUpdate = strip.now; ants[i].height = thisHeight; @@ -3208,16 +3206,15 @@ static uint16_t mode_ants(void) { // check if reached past the end of the strip. if (thisHeight >= 1.0f && ants[i].velocity > 0.0f) { - if (SEGMENT.check1) { // if looking for food, stop and go back the other way + if (SEGMENT.check1) { // if looking for food, stop and go back the other way thisHeight = 1.0f; - ants[i].velocity = -ants[i].velocity; // reverse direction + ants[i].velocity = -ants[i].velocity; // reverse direction ants[i].lastBounceUpdate = strip.now; ants[i].height = thisHeight; - ants[i].hasFood = false; // dropped off the food, now going back for more - passBy = true; // when looking for food, pass by other ants without bumping into them - SEGMENT.check3 = true; // when looking for food, pass by other ants without bumping into them + ants[i].hasFood = false; // dropped off the food, now going back for more + passBy = true; // when looking for food, pass by other ants without bumping into them } - else { // If not looking for food, wrap around + else { // If not looking for food, wrap around thisHeight = 0.0f; ants[i].lastBounceUpdate = strip.now; ants[i].height = thisHeight; @@ -3225,7 +3222,7 @@ static uint16_t mode_ants(void) { } // check for "passing by" or "bumping into" other ants - if (!passBy) { // Ants bump into each other and some reverse direction if checkbox #3 (Pass by) is not "checked"; they pass each other if "checked" + if (!passBy) { // Ants bump into each other and some reverse direction if checkbox #3 (Pass by) is not "checked"; they pass each other if "checked" for (int j = i + 1; j < numAnts; j++) { if (ants[j].velocity != ants[i].velocity) { // tcollided + ants[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions) @@ -3249,6 +3246,7 @@ static uint16_t mode_ants(void) { } uint32_t color; + if (SEGMENT.palette != 0 ) { // if a Palette is selected (besides the Default palette), use the palette's colors color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP,255); } @@ -3264,25 +3262,40 @@ static uint16_t mode_ants(void) { if (thisHeight > 1.0f) thisHeight = 1.0f; unsigned pos = round(thisHeight * (SEGLEN - 1)); - for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 20 pixels) + // make each ant the selected size (between 1 and 20 pixels) + for (int z = 0; z < antSize; z++) { if (ants[i].velocity < 0) { - if (z == 0 && SEGMENT.check1 && ants[i].hasFood) { // make the food pixel white, but if the ant is white, make the food pixel yellow; - if (color == WHITE) // ...but if the bg is yellow, make the food pixel gray. If the bg is white, make it yellow. - color = bgcolor==YELLOW?GRAY:YELLOW; - else - color = bgcolor==WHITE?YELLOW:WHITE; + if (SEGMENT.check1 && ants[i].hasFood) { // if the ants are gathering food and this ant has food + if (z==0) { // is it the food pixel (first pixel of the ant) + if (color == WHITE) // if the ant color is white... + SEGMENT.setPixelColor(pos, bgcolor==YELLOW?GRAY:YELLOW); // draw food color (gray or yellow) depending on ant color and bg color + else + SEGMENT.setPixelColor(pos, bgcolor==WHITE?YELLOW:WHITE); // draw food color (white or yellow) depending on ant color and bg color + } + else { + SEGMENT.setPixelColor(pos, color); // not the food pixel, so draw regular ant color + } } + else + SEGMENT.setPixelColor(pos, color); // either not gathering food or the ant doesn't have food, so draw regular ant color } else { // velocity > 0 - if (z == antSize-1 && SEGMENT.check1 && ants[i].hasFood) { // make the food pixel white, but if the ant is white, make the food pixel yellow; - if (color == WHITE) // ...but if the bg is yellow, make the food pixel gray. If the bg is white, make it yellow. - color = bgcolor==YELLOW?GRAY:YELLOW; - else - color = bgcolor==WHITE?YELLOW:WHITE; + if (SEGMENT.check1 && ants[i].hasFood) { // if the ants are gathering food and this ant has food + if (z == antSize-1) { // the food pixel (first pixel of the ant when it's going in the other direction) + if (color == WHITE) // if the ant color is white... + SEGMENT.setPixelColor(pos, bgcolor==YELLOW?GRAY:YELLOW); // draw food color (gray or yellow) depending on ant color and bg color + else + SEGMENT.setPixelColor(pos, bgcolor==WHITE?YELLOW:WHITE); // draw food color (white or yellow) depending on ant color and bg color + } + else { + SEGMENT.setPixelColor(pos, color); // not the food pixel, so draw regular ant color + } } + else + SEGMENT.setPixelColor(pos, color); // either not gathering food or the ant doesn't have food, so draw regular ant color } - SEGMENT.setPixelColor(pos, color); // draw the pixel with the correct color - pos += 1; + + pos += 1; // get ready to draw the next pixel of the ant } ants[i].lastBounceUpdate = strip.now; @@ -3302,6 +3315,7 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s * 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 that PacMan eats. 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 { @@ -3319,16 +3333,19 @@ typedef struct PacManChars { static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; - unsigned maxPowerDots = SEGLEN / 5; // Maximum number of power dots depends on segment length, max is 1 every 5 Pixels - - unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots); // number of Power Dots (between 1 and 5) based on intensity slider setting + 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 - //update power dots + if (numPowerDots != SEGENV.aux0) // if the user selected a different number of power dots, reinitialize the animation. + SEGENV.call = 0; + SEGENV.aux0 = numPowerDots; + + //figure out how far apart the power dots will be unsigned everyXLeds = (SEGLEN - 10) / numPowerDots; //allocate segment data unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + maxPowerDots); // 4 ghosts + 1 PacMan + max number of Power dots - if (SEGLEN <= 27 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed or segment length is too short to have a nice display + 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 @@ -3374,7 +3391,7 @@ static uint16_t mode_pacman(void) { } 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; // "+ 100" creates a very jerky movement as the characters jump ahead several pixels each time they move SEGENV.aux1++; } @@ -3404,7 +3421,7 @@ static uint16_t mode_pacman(void) { // update power dot positions: can change if user selects a different number of power dots for (int i = 1; i < maxPowerDots; i++) { - character[i+5].pos = 10 + i * everyXLeds; // additional power dots every X LEDs/pixels, character[5] is power dot at end of strip + character[i+5].pos = 10 + i * everyXLeds; // 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 @@ -3418,6 +3435,20 @@ static uint16_t mode_pacman(void) { } } + // 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) @@ -3463,7 +3494,7 @@ static uint16_t mode_pacman(void) { // display the characters if (character[PACMAN].direction) { // Going forward from the beginning of the segment... - if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); // and the black dot behind him character[PACMAN].pos += 1; // update PacMan's position forwards for the next frame draw @@ -3479,22 +3510,13 @@ static uint16_t mode_pacman(void) { } } else { // Going backward (after PacMan ate a power dot)... - if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts - SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); // TODO: This causes a white dot to be deleted right after the powerdot when it's eaten; not sure how to fix it. + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts + SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); // and the black dot behind him character[PACMAN].pos -= 1; // update PacMan's position backwards for the next frame draw for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) - - // if the ghost is blue and nearing the beginning of the strip, blink the ghosts. TODO: The ghosts blink too fast or too slow, depending on the Speed slider's value; not sure how to fix it. - if (character[1].blue && (character[PACMAN].pos <= startBlinkingGhostsLED)) { - if (character[i].color == BLUE) - character[i].color = BLACK; - else - character[i].color = BLUE; - } - SEGMENT.setPixelColor(character[i].pos+1, BLACK); SEGMENT.setPixelColor(character[i].pos, character[i].color); SEGMENT.setPixelColor(character[i].pos-1, BLACK); @@ -3505,7 +3527,7 @@ static uint16_t mode_pacman(void) { 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,,,,White dots,Compact dots,;;!;1;m12=0,o1=1"; /* From 7f622bdf976a29edc1a5412535629e792c6762dc Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 4 May 2025 14:58:08 -0700 Subject: [PATCH 17/19] PacMan: Bug fix in 2D matrix --- wled00/FX.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c10a1785d9..da65788b5c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3309,18 +3309,18 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s /* / Pac-Man (created by making modifications to the Ants effect which was a -* modification of the Rolling Balls effect) - Bob Loeffler - January - March 2025 +* modification of the Rolling Balls effect) - Bob Loeffler - January - May 2025 * * 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 that PacMan eats. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots. +* 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 { - unsigned pos; // is for the LED position of the character (all characters) - unsigned topPos; // is for the LED position of the farthest that the character has moved (PacMan only) + 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) @@ -3333,7 +3333,7 @@ typedef struct PacManChars { static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; - unsigned maxPowerDots = SEGLEN / 10; // Maximum number of power dots depends on segment length, max is 1 every 10 Pixels + unsigned maxPowerDots = SEGLEN / 15; // Maximum number of power dots depends on segment length, max is 1 every 15 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. @@ -3419,7 +3419,7 @@ static uint16_t mode_pacman(void) { SEGMENT.setPixelColor(i, BLACK); }; - // update power dot positions: can change if user selects a different number of power dots + // update power dot positions: can change if user selects a different number of power dots (but not the last one - character[5] - as it will never change) for (int i = 1; i < maxPowerDots; i++) { character[i+5].pos = 10 + i * everyXLeds; // additional power dots every X LEDs/pixels, character[5] is power dot at end of strip } @@ -3527,6 +3527,9 @@ static uint16_t mode_pacman(void) { return FRAMETIME; } +#undef ORANGEYELLOW +#undef WHITEISH +#undef PACMAN static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of Power Dots,,,,White dots,Compact dots,;;!;1;m12=0,o1=1"; From a67ac369f69245667c7c57271f86261804ad56de Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 5 May 2025 20:56:29 +0200 Subject: [PATCH 18/19] minor fixes --- wled00/FX.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c10a1785d9..b3b9d1fce8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3327,12 +3327,11 @@ typedef struct PacManChars { bool eaten; // is for whether the power dot was eaten or not (power dots only) } pacmancharacters_t; -#define ORANGEYELLOW (uint32_t)0xFF8800 -#define WHITEISH (uint32_t)0x999999 -#define PACMAN 0 // PacMan is character[0] - static uint16_t mode_pacman(void) { constexpr unsigned numGhosts = 4; + constexpr unsigned ORANGEYELLOW = 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 @@ -3340,9 +3339,6 @@ static uint16_t mode_pacman(void) { SEGENV.call = 0; SEGENV.aux0 = numPowerDots; - //figure out how far apart the power dots will be - unsigned everyXLeds = (SEGLEN - 10) / 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 @@ -3410,18 +3406,14 @@ static uint16_t mode_pacman(void) { } } else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan - if (character[PACMAN].direction) { - for (int i = SEGLEN-1; i > character[PACMAN].pos; i--) // start at the end of the segment and draw black LEDs to the PacMan character - SEGMENT.setPixelColor(i, BLACK); - } - else // this is needed so we don't draw a black LED on top of the PacMan character (it was causing a flicker of the PacMan LED when chasing the ghosts) - for (int i = SEGLEN-1; i > character[PACMAN].pos+1; i--) // start at the end of the segment and draw black LEDs to the LED just before the PacMan character - SEGMENT.setPixelColor(i, BLACK); + for (int i = SEGLEN-1; i > character[PACMAN].pos + !character[PACMAN].direction; i--) // start at the end of the segment and draw black LEDs to the PacMan character + SEGMENT.setPixelColor(i, BLACK); }; // 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; // 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 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 @@ -3436,7 +3428,7 @@ static uint16_t mode_pacman(void) { } // 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 (SEGENV.aux1 % 15 == 0) { if (character[1].blue && (character[PACMAN].pos <= startBlinkingGhostsLED)) { if (character[1].color == BLUE) character[1].color = BLACK; From b19ec2c37b38cac268bfc58b3f1025fb175ee11a Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Wed, 7 May 2025 08:13:17 -0700 Subject: [PATCH 19/19] PacMan: SEGMENT.fill(BLACK) and many associated optimizations --- wled00/FX.cpp | 53 +++++++++++++++++---------------------------------- wled00/FX.h | 1 - 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5262459c75..a677f81501 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3304,7 +3304,7 @@ static uint16_t mode_ants(void) { 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"; +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"; /* @@ -3391,9 +3391,8 @@ static uint16_t mode_pacman(void) { SEGENV.aux1++; } - // black out LEDs behind the last ghost (character[4]) in case they are on from a previous effect (transition) - for (int i = character[4].pos; i > 1; i--) - SEGMENT.setPixelColor(i-2, BLACK); + // 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 @@ -3405,15 +3404,11 @@ static uint16_t mode_pacman(void) { } } } - else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan - for (int i = SEGLEN-1; i > character[PACMAN].pos + !character[PACMAN].direction; i--) // start at the end of the segment and draw black LEDs to the PacMan character - SEGMENT.setPixelColor(i, BLACK); - }; // 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 + 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 @@ -3485,38 +3480,26 @@ static uint16_t mode_pacman(void) { } // display the characters - if (character[PACMAN].direction) { // Going forward from the beginning of the segment... - if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan - SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); // and the black dot behind him - character[PACMAN].pos += 1; // update PacMan's position forwards for the next frame draw - - if (character[PACMAN].topPos < character[PACMAN].pos) // keep track of the top (farthest) position of the PacMan character - character[PACMAN].topPos = character[PACMAN].pos; - - for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and the black dot behind each ghost) - SEGMENT.setPixelColor(character[i].pos, character[i].color); - SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos += 1; // update their positions forwards for the next frame draw - } + 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 { // Going backward (after PacMan ate a power dot)... - if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0) { // User-selectable speed of PacMan and the Ghosts - SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); - SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // draw PacMan - SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); // and the black dot behind him - character[PACMAN].pos -= 1; // update PacMan's position backwards for the next frame draw + 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 (and black dots surrounding each ghost) - SEGMENT.setPixelColor(character[i].pos+1, BLACK); - SEGMENT.setPixelColor(character[i].pos, character[i].color); - SEGMENT.setPixelColor(character[i].pos-1, BLACK); - character[i].pos -= 1; // update their positions backwards for the next frame draw - } + 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"; diff --git a/wled00/FX.h b/wled00/FX.h index e2caf1a89a..31138aeef6 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -325,7 +325,6 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 - #define FX_MODE_PARTICLEVOLCANO 187 #define FX_MODE_PARTICLEFIRE 188 #define FX_MODE_PARTICLEFIREWORKS 189