Improve color goal display with swatches
This commit is contained in:
69
main.js
69
main.js
@@ -96,10 +96,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const balls = [];
|
const balls = [];
|
||||||
|
const blobConstraints = new Map();
|
||||||
let spawnTimer = null;
|
let spawnTimer = null;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let highScore = 0;
|
let highScore = 0;
|
||||||
let clearedCount = 0;
|
let clearedCount = 0;
|
||||||
|
let clearedByColor = {};
|
||||||
let gameOver = false;
|
let gameOver = false;
|
||||||
let isPaused = false;
|
let isPaused = false;
|
||||||
let levelWon = false;
|
let levelWon = false;
|
||||||
@@ -135,6 +137,7 @@
|
|||||||
engine.gravity.y = config.gravity;
|
engine.gravity.y = config.gravity;
|
||||||
clearedCount = 0;
|
clearedCount = 0;
|
||||||
levelWon = false;
|
levelWon = false;
|
||||||
|
clearedByColor = {};
|
||||||
highScore = loadHighScore(next.id);
|
highScore = loadHighScore(next.id);
|
||||||
rebuildSceneBodies();
|
rebuildSceneBodies();
|
||||||
buildLegend();
|
buildLegend();
|
||||||
@@ -309,6 +312,7 @@
|
|||||||
levelWon = false;
|
levelWon = false;
|
||||||
score = 0;
|
score = 0;
|
||||||
clearedCount = 0;
|
clearedCount = 0;
|
||||||
|
clearedByColor = {};
|
||||||
resetChainVisuals();
|
resetChainVisuals();
|
||||||
balls.forEach((ball) => {
|
balls.forEach((ball) => {
|
||||||
cleanupBall(ball);
|
cleanupBall(ball);
|
||||||
@@ -379,6 +383,17 @@
|
|||||||
Body.applyForce(ball, ball.position, force);
|
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) {
|
if (winCond.onWin.removeCurves) {
|
||||||
const remaining = [];
|
const remaining = [];
|
||||||
boundaries.forEach((b) => {
|
boundaries.forEach((b) => {
|
||||||
@@ -457,13 +472,36 @@
|
|||||||
}
|
}
|
||||||
ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color);
|
ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color);
|
||||||
chain.constraints.forEach((c) => World.remove(world, c));
|
chain.constraints.forEach((c) => World.remove(world, c));
|
||||||
|
const blobIds = new Set();
|
||||||
chain.bodies.forEach((body) => {
|
chain.bodies.forEach((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);
|
cleanupBall(body);
|
||||||
World.remove(world, 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.
|
// Remove cleared balls from tracking list.
|
||||||
for (let i = balls.length - 1; i >= 0; i -= 1) {
|
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);
|
balls.splice(i, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -644,6 +682,8 @@
|
|||||||
return Bodies.circle(x, y, config.ballRadius, commonOpts);
|
return Bodies.circle(x, y, config.ballRadius, commonOpts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeColor = (c) => (c || "").trim().toLowerCase();
|
||||||
|
|
||||||
const getGoalState = () => {
|
const getGoalState = () => {
|
||||||
const winCond = currentScene?.config?.winCondition;
|
const winCond = currentScene?.config?.winCondition;
|
||||||
if (!winCond) return null;
|
if (!winCond) return null;
|
||||||
@@ -666,14 +706,29 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (winCond.type === "colorClear" && Array.isArray(winCond.targets)) {
|
if (winCond.type === "colorClear" && Array.isArray(winCond.targets)) {
|
||||||
const target = winCond.targets.reduce(
|
const targets = winCond.targets.map((t) => ({
|
||||||
(sum, t) => sum + (t.count || 0),
|
color: normalizeColor(t.color),
|
||||||
0,
|
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 {
|
return {
|
||||||
label: "Clear target colors",
|
label: parts.join(" • "),
|
||||||
progress: target > 0 ? (100 * clearedCount) / target : 0,
|
progress: totalTarget > 0 ? (100 * totalAchieved) / totalTarget : 0,
|
||||||
met: false,
|
met: targets.every(
|
||||||
|
(t) =>
|
||||||
|
(clearedByColor[normalizeColor(t.color)] || 0) >= (t.count || 0),
|
||||||
|
),
|
||||||
|
colors: targets.map((t) => t.color),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
24
ui.js
24
ui.js
@@ -97,8 +97,28 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setGoal = ({ label, progress }) => {
|
const setGoal = ({ label, progress, colors }) => {
|
||||||
if (goalLabelEl) goalLabelEl.textContent = label || "—";
|
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)
|
if (goalProgressEl)
|
||||||
goalProgressEl.style.width = `${Math.max(
|
goalProgressEl.style.width = `${Math.max(
|
||||||
0,
|
0,
|
||||||
|
|||||||
Reference in New Issue
Block a user