Skip to content

Commit bb6114e

Browse files
PacMan effect (#4891)
* PacMan effect added
1 parent c097cb1 commit bb6114e

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

‎wled00/FX.cpp‎

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3178,6 +3178,202 @@ static uint16_t rolling_balls(void) {
31783178
static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collide,Overlay,Trails;!,!,!;!;1;m12=1"; //bar
31793179
#endif // WLED_PS_DONT_REPLACE_1D_FX
31803180

3181+
3182+
/*
3183+
/ Pac-Man by Bob Loeffler with help from @dedehai and @blazoncek
3184+
* speed slider is for speed.
3185+
* intensity slider is for selecting the number of power dots.
3186+
* custom1 slider is for selecting the LED where the ghosts will start blinking blue.
3187+
* custom2 slider is for blurring the LEDs in the segment.
3188+
* custom3 slider is for selecting the # of ghosts (between 2 and 8).
3189+
* check1 is for displaying White Dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black).
3190+
* check2 is for Smear mode (enabled will smear/persist the LED colors, disabled will not).
3191+
* check3 is for the Compact Dots mode of displaying white dots. Enabled will show white dots in every LED. Disabled will show black LEDs between the white dots.
3192+
* aux0 is used to keep track of the previous number of power dots in case the user selects a different number with the intensity slider.
3193+
* aux1 is the main counter for timing.
3194+
*/
3195+
typedef struct PacManChars {
3196+
signed pos;
3197+
signed topPos; // LED position of farthest PacMan has moved
3198+
uint32_t color;
3199+
bool direction; // true = moving away from first LED
3200+
bool blue; // used for ghosts only
3201+
bool eaten; // used for power dots only
3202+
} pacmancharacters_t;
3203+
3204+
static uint16_t mode_pacman(void) {
3205+
constexpr unsigned ORANGEYELLOW = 0xFFCC00;
3206+
constexpr unsigned PURPLEISH = 0xB000B0;
3207+
constexpr unsigned ORANGEISH = 0xFF8800;
3208+
constexpr unsigned WHITEISH = 0x999999;
3209+
constexpr unsigned PACMAN = 0; // PacMan is character[0]
3210+
constexpr uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH};
3211+
3212+
unsigned maxPowerDots = min(SEGLEN / 10U, 255U); // cap the max so packed state fits in 8 bits
3213+
unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots);
3214+
unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8);
3215+
bool smearMode = SEGMENT.check2;
3216+
3217+
// Pack two 8-bit values into one 16-bit field (stored in SEGENV.aux0)
3218+
uint16_t combined_value = uint16_t(((numPowerDots & 0xFF) << 8) | (numGhosts & 0xFF));
3219+
if (combined_value != SEGENV.aux0) SEGENV.call = 0; // Reinitialize on setting change
3220+
SEGENV.aux0 = combined_value;
3221+
3222+
// Allocate segment data
3223+
unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character
3224+
if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) return mode_static();
3225+
pacmancharacters_t *character = reinterpret_cast<pacmancharacters_t *>(SEGENV.data);
3226+
3227+
// Calculate when blue ghosts start blinking.
3228+
// On first call (or after settings change), `topPos` is not known yet, so fall back to the full segment length in that case.
3229+
int maxBlinkPos = (SEGENV.call == 0) ? (int)SEGLEN - 1 : character[PACMAN].topPos;
3230+
if (maxBlinkPos < 20) maxBlinkPos = 20;
3231+
int startBlinkingGhostsLED = (SEGLEN < 64)
3232+
? (int)SEGLEN / 3
3233+
: map(SEGMENT.custom1, 0, 255, 20, maxBlinkPos);
3234+
3235+
// Initialize characters on first call
3236+
if (SEGENV.call == 0) {
3237+
// Initialize PacMan
3238+
character[PACMAN].color = YELLOW;
3239+
character[PACMAN].pos = 0;
3240+
character[PACMAN].topPos = 0;
3241+
character[PACMAN].direction = true;
3242+
character[PACMAN].blue = false;
3243+
3244+
// Initialize ghosts with alternating colors
3245+
for (int i = 1; i <= numGhosts; i++) {
3246+
character[i].color = ghostColors[(i-1) % 4];
3247+
character[i].pos = -2 * (i + 1);
3248+
character[i].direction = true;
3249+
character[i].blue = false;
3250+
}
3251+
3252+
// Initialize power dots
3253+
for (int i = 0; i < numPowerDots; i++) {
3254+
character[i + numGhosts + 1].color = ORANGEYELLOW;
3255+
character[i + numGhosts + 1].eaten = false;
3256+
}
3257+
character[numGhosts + 1].pos = SEGLEN - 1; // Last power dot at end
3258+
}
3259+
3260+
if (strip.now > SEGENV.step) {
3261+
SEGENV.step = strip.now;
3262+
SEGENV.aux1++;
3263+
}
3264+
3265+
// Clear background if not in smear mode
3266+
if (!smearMode) SEGMENT.fill(BLACK);
3267+
3268+
// Draw white dots in front of PacMan if option selected
3269+
if (SEGMENT.check1) {
3270+
int step = SEGMENT.check3 ? 1 : 2; // Compact or spaced dots
3271+
for (int i = SEGLEN - 1; i > character[PACMAN].topPos; i -= step) {
3272+
SEGMENT.setPixelColor(i, WHITEISH);
3273+
}
3274+
}
3275+
3276+
// Update power dot positions dynamically
3277+
uint32_t everyXLeds = (((uint32_t)SEGLEN - 10U) << 8) / numPowerDots; // Fixed-point spacing for power dots: use 32-bit math to avoid overflow on long segments.
3278+
for (int i = 1; i < numPowerDots; i++) {
3279+
character[i + numGhosts + 1].pos = 10 + ((i * everyXLeds) >> 8);
3280+
}
3281+
3282+
// Blink power dots every 10 ticks
3283+
if (SEGENV.aux1 % 10 == 0) {
3284+
uint32_t dotColor = (character[numGhosts + 1].color == ORANGEYELLOW) ? BLACK : ORANGEYELLOW;
3285+
for (int i = 0; i < numPowerDots; i++) {
3286+
character[i + numGhosts + 1].color = dotColor;
3287+
}
3288+
}
3289+
3290+
// Blink blue ghosts when nearing start
3291+
if (SEGENV.aux1 % 15 == 0 && character[1].blue && character[PACMAN].pos <= startBlinkingGhostsLED) {
3292+
uint32_t ghostColor = (character[1].color == BLUE) ? WHITEISH : BLUE;
3293+
for (int i = 1; i <= numGhosts; i++) {
3294+
character[i].color = ghostColor;
3295+
}
3296+
}
3297+
3298+
// Draw uneaten power dots
3299+
for (int i = 0; i < numPowerDots; i++) {
3300+
if (!character[i + numGhosts + 1].eaten && (unsigned)character[i + numGhosts + 1].pos < SEGLEN) {
3301+
SEGMENT.setPixelColor(character[i + numGhosts + 1].pos, character[i + numGhosts + 1].color);
3302+
}
3303+
}
3304+
3305+
// Check if PacMan ate a power dot
3306+
for (int j = 0; j < numPowerDots; j++) {
3307+
auto &dot = character[j + numGhosts + 1];
3308+
if (character[PACMAN].pos == dot.pos && !dot.eaten) {
3309+
// Reverse all characters - PacMan now chases ghosts
3310+
for (int i = 0; i <= numGhosts; i++) {
3311+
character[i].direction = false;
3312+
}
3313+
// Turn ghosts blue
3314+
for (int i = 1; i <= numGhosts; i++) {
3315+
character[i].color = BLUE;
3316+
character[i].blue = true;
3317+
}
3318+
dot.eaten = true;
3319+
break; // only one power dot per frame
3320+
}
3321+
}
3322+
3323+
// Reset when PacMan reaches start with blue ghosts
3324+
if (character[1].blue && character[PACMAN].pos <= 0) {
3325+
// Reverse direction back
3326+
for (int i = 0; i <= numGhosts; i++) {
3327+
character[i].direction = true;
3328+
}
3329+
// Reset ghost colors
3330+
for (int i = 1; i <= numGhosts; i++) {
3331+
character[i].color = ghostColors[(i-1) % 4];
3332+
character[i].blue = false;
3333+
}
3334+
// Reset power dots if last one was eaten
3335+
if (character[numGhosts + 1].eaten) {
3336+
for (int i = 0; i < numPowerDots; i++) {
3337+
character[i + numGhosts + 1].eaten = false;
3338+
}
3339+
character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment)
3340+
}
3341+
}
3342+
3343+
// Update and draw characters based on speed setting
3344+
bool updatePositions = (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0);
3345+
3346+
// update positions of characters if it's time to do so
3347+
if (updatePositions) {
3348+
character[PACMAN].pos += character[PACMAN].direction ? 1 : -1;
3349+
for (int i = 1; i <= numGhosts; i++) {
3350+
character[i].pos += character[i].direction ? 1 : -1;
3351+
}
3352+
}
3353+
3354+
// Draw PacMan
3355+
if ((unsigned)character[PACMAN].pos < SEGLEN) {
3356+
SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color);
3357+
}
3358+
3359+
// Draw ghosts
3360+
for (int i = 1; i <= numGhosts; i++) {
3361+
if ((unsigned)character[i].pos < SEGLEN) {
3362+
SEGMENT.setPixelColor(character[i].pos, character[i].color);
3363+
}
3364+
}
3365+
3366+
// Track farthest position of PacMan
3367+
if (character[PACMAN].topPos < character[PACMAN].pos) {
3368+
character[PACMAN].topPos = character[PACMAN].pos;
3369+
}
3370+
3371+
SEGMENT.blur(SEGMENT.custom2>>1);
3372+
return FRAMETIME;
3373+
}
3374+
static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,# of PowerDots,Blink distance,Blur,# of Ghosts,Dots,Smear,Compact;;!;1;m12=0,sx=192,ix=64,c1=64,c2=0,c3=12,o1=1,o2=0";
3375+
3376+
31813377
/*
31823378
* Sinelon stolen from FASTLED examples
31833379
*/
@@ -10929,6 +11125,7 @@ void WS2812FX::setupEffectData() {
1092911125
addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS);
1093011126
addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR);
1093111127
addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH);
11128+
addEffect(FX_MODE_PACMAN, &mode_pacman, _data_FX_MODE_PACMAN);
1093211129

1093311130
// --- 1D audio effects ---
1093411131
addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS);

‎wled00/FX.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
310310
#define FX_MODE_2DFIRENOISE 149
311311
#define FX_MODE_2DSQUAREDSWIRL 150
312312
// #define FX_MODE_2DFIRE2012 151
313+
#define FX_MODE_PACMAN 151 // gap fill (non-SR). Do NOT renumber; SR-ID range must remain stable.
313314
#define FX_MODE_2DDNA 152
314315
#define FX_MODE_2DMATRIX 153
315316
#define FX_MODE_2DMETABALLS 154

0 commit comments

Comments
 (0)