From 37607ef148993976ba7db05a850bcbb7b55807fd Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Sat, 13 Dec 2025 20:20:08 +0100 Subject: [PATCH] Improve color goal display with swatches --- main.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++-------- ui.js | 24 ++++++++++++++++-- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/main.js b/main.js index abcd7a2..ff8f7fd 100644 --- a/main.js +++ b/main.js @@ -96,10 +96,12 @@ }; const balls = []; + const blobConstraints = new Map(); let spawnTimer = null; let score = 0; let highScore = 0; let clearedCount = 0; + let clearedByColor = {}; let gameOver = false; let isPaused = false; let levelWon = false; @@ -135,6 +137,7 @@ engine.gravity.y = config.gravity; clearedCount = 0; levelWon = false; + clearedByColor = {}; highScore = loadHighScore(next.id); rebuildSceneBodies(); buildLegend(); @@ -309,6 +312,7 @@ levelWon = false; score = 0; clearedCount = 0; + clearedByColor = {}; resetChainVisuals(); balls.forEach((ball) => { cleanupBall(ball); @@ -379,6 +383,17 @@ Body.applyForce(ball, ball.position, force); }); } + if (winCond.onWin.swirlBalls) { + balls.forEach((ball) => { + const angle = Math.random() * Math.PI * 2; + const mag = 0.06; + Body.applyForce(ball, ball.position, { + x: Math.cos(angle) * mag, + y: -Math.abs(Math.sin(angle)) * mag * 1.5, + }); + Body.setAngularVelocity(ball, (Math.random() - 0.5) * 0.3); + }); + } if (winCond.onWin.removeCurves) { const remaining = []; boundaries.forEach((b) => { @@ -457,13 +472,36 @@ } ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color); chain.constraints.forEach((c) => World.remove(world, c)); + const blobIds = new Set(); chain.bodies.forEach((body) => { - cleanupBall(body); - World.remove(world, body); + if (body.plugin?.blobId) { + blobIds.add(body.plugin.blobId); + } else { + if (body.plugin?.color) { + const key = normalizeColor(body.plugin.color); + clearedByColor[key] = (clearedByColor[key] || 0) + 1; + } + cleanupBall(body); + World.remove(world, body); + } + }); + blobIds.forEach((id) => { + balls + .filter((b) => b.plugin?.blobId === id) + .forEach((b) => { + if (b.plugin?.color) { + const key = normalizeColor(b.plugin.color); + clearedByColor[key] = (clearedByColor[key] || 0) + 1; + } + }); + removeBlob(id); }); // Remove cleared balls from tracking list. for (let i = balls.length - 1; i >= 0; i -= 1) { - if (chain.bodies.includes(balls[i])) { + if ( + chain.bodies.includes(balls[i]) || + (balls[i].plugin?.blobId && blobIds.has(balls[i].plugin?.blobId)) + ) { balls.splice(i, 1); } } @@ -644,6 +682,8 @@ return Bodies.circle(x, y, config.ballRadius, commonOpts); }; + const normalizeColor = (c) => (c || "").trim().toLowerCase(); + const getGoalState = () => { const winCond = currentScene?.config?.winCondition; if (!winCond) return null; @@ -666,14 +706,29 @@ }; } if (winCond.type === "colorClear" && Array.isArray(winCond.targets)) { - const target = winCond.targets.reduce( - (sum, t) => sum + (t.count || 0), - 0, - ); + const targets = winCond.targets.map((t) => ({ + color: normalizeColor(t.color), + count: t.count || 0, + })); + const totalTarget = targets.reduce((sum, t) => sum + t.count, 0); + let totalAchieved = 0; + const parts = targets.map((t) => { + const got = Math.min( + t.count, + clearedByColor[normalizeColor(t.color)] || 0, + ); + totalAchieved += got; + const remaining = Math.max(0, t.count - got); + return `${got}/${t.count} (${remaining} left)`; + }); return { - label: "Clear target colors", - progress: target > 0 ? (100 * clearedCount) / target : 0, - met: false, + label: parts.join(" • "), + progress: totalTarget > 0 ? (100 * totalAchieved) / totalTarget : 0, + met: targets.every( + (t) => + (clearedByColor[normalizeColor(t.color)] || 0) >= (t.count || 0), + ), + colors: targets.map((t) => t.color), }; } return null; diff --git a/ui.js b/ui.js index c8dd594..e4cb4f4 100644 --- a/ui.js +++ b/ui.js @@ -97,8 +97,28 @@ } }; - const setGoal = ({ label, progress }) => { - if (goalLabelEl) goalLabelEl.textContent = label || "—"; + const setGoal = ({ label, progress, colors }) => { + if (goalLabelEl) { + goalLabelEl.innerHTML = ""; + if (Array.isArray(colors) && colors.length > 0) { + colors.forEach((color) => { + const swatch = document.createElement("span"); + swatch.style.background = color; + swatch.style.display = "inline-block"; + swatch.style.width = "14px"; + swatch.style.height = "14px"; + swatch.style.borderRadius = "50%"; + swatch.style.border = "1px solid rgba(255,255,255,0.2)"; + swatch.style.marginRight = "6px"; + goalLabelEl.appendChild(swatch); + }); + const text = document.createElement("span"); + text.textContent = label || ""; + goalLabelEl.appendChild(text); + } else { + goalLabelEl.textContent = label || "—"; + } + } if (goalProgressEl) goalProgressEl.style.width = `${Math.max( 0,