diff --git a/src/scripts/symbol-rain.config.js b/src/scripts/symbol-rain.config.js index d703f166..11f0c6a6 100644 --- a/src/scripts/symbol-rain.config.js +++ b/src/scripts/symbol-rain.config.js @@ -27,7 +27,7 @@ // Spawn rates spawnRate: 0.5, burstSpawnRate: 0.15, - symbolsPerWave: 7, + symbolsPerWave: 14, waveInterval: 80, guaranteedSpawnInterval: 5000, // Face reveal @@ -37,7 +37,7 @@ // Layout columnWidth: 50, gridCellSize: 100, - poolSize: 30, + poolSize: 60, // Desktop collision (pixels) desktopSymbolHeight: 30, desktopSymbolWidth: 30, diff --git a/src/scripts/worm-movement-navigation.js b/src/scripts/worm-movement-navigation.js index 5026546f..947db437 100644 --- a/src/scripts/worm-movement-navigation.js +++ b/src/scripts/worm-movement-navigation.js @@ -49,7 +49,9 @@ console.log("🐛 Worm movement navigation loading..."); return false; } - const symbolsToSearch = this.getCachedAllSymbols(); + const symbolsToSearch = worm.isPurple + ? this.getCachedAllSymbols() + : this.getCachedRevealedSymbols(); const targetElement = this._resolveTargetElement(worm, symbolsToSearch); diff --git a/src/scripts/worm-system.behavior.js b/src/scripts/worm-system.behavior.js index 311a02b0..ebe074eb 100644 --- a/src/scripts/worm-system.behavior.js +++ b/src/scripts/worm-system.behavior.js @@ -31,8 +31,7 @@ return; } - // FIX: Purple worms need access to ALL symbols (including hidden), not just revealed - const symbolsSource = this.getCachedAllSymbols(); + const symbolsSource = this.getCachedRevealedSymbols(); // Get all available symbols (not stolen, not spaces, not completed) const allAvailableSymbols = Array.from(symbolsSource).filter( @@ -42,37 +41,22 @@ !el.classList.contains("completed-row-symbol"), ); - // PURPLE WORM LOGIC: Only steal blue symbols when NO red symbols available + // PURPLE WORM LOGIC: can only steal symbols currently visible to the user let availableSymbols; if (worm.canStealBlue && worm.isPurple) { - // First, try to get red (hidden) symbols only - const redSymbols = allAvailableSymbols.filter((el) => - el.classList.contains("hidden-symbol"), + availableSymbols = allAvailableSymbols.filter((el) => + el.classList.contains("revealed-symbol"), + ); + console.log( + `🟣 PURPLE WORM - ${availableSymbols.length} revealed symbols available`, ); - - if (redSymbols.length > 0) { - // Red symbols available - purple worm steals red symbols like normal - availableSymbols = redSymbols; - console.log( - `🟣 PURPLE WORM - ${redSymbols.length} red symbols available (preferring red)`, - ); - } else { - // NO red symbols - now purple worm can steal blue symbols! - const blueSymbols = allAvailableSymbols.filter((el) => - el.classList.contains("revealed-symbol"), - ); - availableSymbols = blueSymbols; - console.log( - `🟣 PURPLE WORM - NO red symbols! Stealing blue symbols (${blueSymbols.length} available)`, - ); - } } else { - // Normal worm - only steal red (hidden) symbols + // All non-purple steal attempts are restricted to currently revealed symbols availableSymbols = allAvailableSymbols.filter((el) => - el.classList.contains("hidden-symbol"), + el.classList.contains("revealed-symbol"), ); console.log( - `🐛 Normal worm - ${availableSymbols.length} red symbols available`, + `🐛 Normal worm - ${availableSymbols.length} revealed symbols available`, ); } @@ -178,18 +162,13 @@ )); if (worm.isPurple && worm.canStealBlue) { - const redSymbols = allAvailableSymbols.filter((el) => - el.classList.contains("hidden-symbol"), - ); - if (redSymbols.length > 0) return redSymbols; - return allAvailableSymbols.filter((el) => el.classList.contains("revealed-symbol"), ); } return allAvailableSymbols.filter((el) => - el.classList.contains("hidden-symbol"), + el.classList.contains("revealed-symbol"), ); }; diff --git a/src/scripts/worm-system.effects.js b/src/scripts/worm-system.effects.js index 943b18db..c5f085f0 100644 --- a/src/scripts/worm-system.effects.js +++ b/src/scripts/worm-system.effects.js @@ -208,9 +208,11 @@ proto.createSlimeSplat = function(x, y) { const splat = document.createElement("div"); splat.className = "slime-splat"; + splat.textContent = "🫟"; splat.style.left = `${x}px`; splat.style.top = `${y}px`; splat.style.position = "fixed"; // Use fixed positioning to place at exact coordinates + splat.style.zIndex = "10002"; // Random rotation for variation splat.style.transform = `translate(-50%, -50%) rotate(${Math.random() * diff --git a/src/scripts/worm-system.events.js b/src/scripts/worm-system.events.js index 688893b5..c647648f 100644 --- a/src/scripts/worm-system.events.js +++ b/src/scripts/worm-system.events.js @@ -108,41 +108,13 @@ const normalizedWormSymbol = normalizeSymbol(worm.stolenSymbol); - if (normalizedWormSymbol === normalizedClicked) { - // PURPLE WORM: Turn green when matching symbol clicked (must click worm to destroy) + if (normalizedWormSymbol === normalizedClicked) { + // PURPLE WORM: matching rain symbol is the only valid kill path if (worm.isPurple) { console.log( - `🟣→🟢 User clicked rain symbol "${clickedSymbol}" - Purple worm ${worm.id} turns GREEN!`, + `💥 User clicked matching rain symbol "${clickedSymbol}" - EXPLODING purple worm ${worm.id}!`, ); - - // Turn worm green (damaged state) - worm.element.style.filter = "hue-rotate(120deg) brightness(1.2)"; // Purple → Green - worm.element.classList.remove("purple-worm"); - worm.element.classList.add("worm-damaged", "purple-turned-green"); - worm.isPurple = false; // No longer purple - worm.canBeClicked = true; // Now clickable for destruction - - // Flash effect - worm.element.style.animation = "worm-flash-green 0.5s ease-out"; - setTimeout(() => { - worm.element.style.animation = ""; - }, 500); - - // Update click handler to explode instead of clone - worm.element.removeEventListener("click", worm.clickHandler); - worm.clickHandler = (e) => { - e.stopPropagation(); - console.log( - `💥 Green (was purple) worm ${worm.id} clicked - EXPLODING!`, - ); - - // Drop power-up when purple worm (now green) is destroyed - this.dropPowerUp(worm.x, worm.y); - - this.explodeWorm(worm, false); - }; - worm.element.addEventListener("click", worm.clickHandler); - + this.explodeWorm(worm, true); return; } diff --git a/src/scripts/worm-system.interactions.js b/src/scripts/worm-system.interactions.js index 35450d83..c4d116cf 100644 --- a/src/scripts/worm-system.interactions.js +++ b/src/scripts/worm-system.interactions.js @@ -68,8 +68,7 @@ worm.element.style.animation = ""; }, 500); - // FIX: Explode original purple worm AND clone it - this.explodeWorm(worm, false); + // Purple worms should not die on direct click; click is a clone penalty. this.clonePurpleWorm(worm); }; diff --git a/src/scripts/worm-system.spawn.js b/src/scripts/worm-system.spawn.js index 666dd1bd..7f22d7d1 100644 --- a/src/scripts/worm-system.spawn.js +++ b/src/scripts/worm-system.spawn.js @@ -293,7 +293,7 @@ )}, ${startY.toFixed(0)}). Total worms: ${this.worms.length}`, ); console.log( - `🟣 Purple worm moves slower, prioritizes RED symbols, and CLONES on click!`, + `🟣 Purple worm moves slower, steals visible symbols, and CLONES on click!`, ); // Start animation loop if not already running diff --git a/src/styles/css/score-timer.css b/src/styles/css/score-timer.css index 5532544e..3d49eb9f 100644 --- a/src/styles/css/score-timer.css +++ b/src/styles/css/score-timer.css @@ -57,13 +57,13 @@ } .hud-label { - font-size: 12px; + font-size: 8px; letter-spacing: 2px; opacity: 0.9; } .hud-value { - font-size: 28px; + font-size: 20px; font-weight: 900; line-height: 1.05; } @@ -158,7 +158,7 @@ } .hud-value { - font-size: 22px; + font-size: 15px; } } diff --git a/tests/worm-behavior.spec.js b/tests/worm-behavior.spec.js index fe9a1b11..8cf700fe 100644 --- a/tests/worm-behavior.spec.js +++ b/tests/worm-behavior.spec.js @@ -81,4 +81,32 @@ test.describe("Worm behavior: aggression, targeting, and click rules", () => { expect(afterSecondClick).toBeFalsy(); }); + + test("purple worm click clones instead of dying", async ({ page }) => { + await page.evaluate(() => { + document.dispatchEvent(new CustomEvent("purpleWormTriggered")); + }); + + await page.waitForFunction( + () => window.wormSystem?.worms.some((w) => w.active && w.isPurple), + ); + + const beforeClickCount = await page.evaluate( + () => window.wormSystem.worms.filter((w) => w.active && w.isPurple).length, + ); + + const purpleWorm = page.locator(".worm-container.purple-worm").first(); + await purpleWorm.click({ force: true }); + + await page.waitForTimeout(400); + + const afterClickState = await page.evaluate(() => ({ + purpleCount: window.wormSystem.worms.filter((w) => w.active && w.isPurple) + .length, + totalActive: window.wormSystem.worms.filter((w) => w.active).length, + })); + + expect(afterClickState.purpleCount).toBeGreaterThan(beforeClickCount); + expect(afterClickState.totalActive).toBeGreaterThanOrEqual(2); + }); });