diff --git a/src/main.js b/src/main.js index 051d94e..dc55494 100644 --- a/src/main.js +++ b/src/main.js @@ -435,93 +435,93 @@ updateHud(); }; - const finishChain = (releasePoint) => { - if (!chain.active || gameOver || isPaused) return; - if (chain.bodies.length >= config.minChain) { - const chainLength = chain.bodies.length; - if (chainLength > longestChainRecord) { - longestChainRecord = chainLength; - saveLongestChain(currentScene.id, longestChainRecord); - console.log( - "New longest chain record", - chainLength, - "scene", - currentScene?.id, - ); - ui.showFloatingMessage(`New chain record: ${chainLength}`, { - durationMs: 3600, - position: config.messages.position, - }); + const updateLongestChain = (chainLength) => { + if (chainLength <= longestChainRecord) return; + longestChainRecord = chainLength; + saveLongestChain(currentScene.id, longestChainRecord); + console.log( + "New longest chain record", + chainLength, + "scene", + currentScene?.id, + ); + ui.showFloatingMessage(`New chain record: ${chainLength}`, { + durationMs: 3600, + position: config.messages.position, + }); + }; + + const getChainScoreState = () => { + const baseGain = 10 * Math.pow(chain.bodies.length, 2); + const negativeColors = ( + currentScene?.config?.negativeScoreColors || [] + ).map(normalizeColor); + const negativeProgressColors = ( + currentScene?.config?.negativeProgressColors || [] + ).map(normalizeColor); + const normalizedColor = chain.color + ? normalizeColor(chain.color || "") + : null; + const isNegative = + normalizedColor && negativeColors.includes(normalizedColor); + const isNegativeProgress = + normalizedColor && negativeProgressColors.includes(normalizedColor); + const gain = isNegative ? -baseGain : baseGain; + return { gain, isNegativeProgress }; + }; + + const removeChainConstraints = () => { + chain.constraints.forEach((c) => World.remove(world, c)); + }; + + const removeChainBodies = () => { + const blobIds = new Set(); + 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; + } + spawnSystem.cleanupBall(body); + World.remove(world, body); } - const baseGain = 10 * Math.pow(chain.bodies.length, 2); - const negativeColors = ( - currentScene?.config?.negativeScoreColors || [] - ).map(normalizeColor); - const negativeProgressColors = ( - currentScene?.config?.negativeProgressColors || [] - ).map(normalizeColor); - const isNegative = - chain.color && - negativeColors.includes(normalizeColor(chain.color || "")); - const isNegativeProgress = - chain.color && - negativeProgressColors.includes(normalizeColor(chain.color || "")); - const gain = isNegative ? -baseGain : baseGain; - score += gain; - clearedCount += chain.bodies.length; - if (score > highScore) { - highScore = score; - saveHighScore(currentScene.id, highScore); - } - ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color); - chain.constraints.forEach((c) => World.remove(world, c)); - const blobIds = new Set(); - chain.bodies.forEach((body) => { - if (body.plugin?.blobId) { - blobIds.add(body.plugin.blobId); - } else { - if (body.plugin?.color) { - const key = normalizeColor(body.plugin.color); + }); + 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; } - spawnSystem.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; - } - }); - spawnSystem.removeBlob(id); - }); - // Remove cleared balls from tracking list. - for (let i = balls.length - 1; i >= 0; i -= 1) { - if ( - chain.bodies.includes(balls[i]) || - (balls[i].plugin?.blobId && blobIds.has(balls[i].plugin?.blobId)) - ) { - balls.splice(i, 1); - } + }); + spawnSystem.removeBlob(id); + }); + for (let i = balls.length - 1; i >= 0; i -= 1) { + if ( + chain.bodies.includes(balls[i]) || + (balls[i].plugin?.blobId && blobIds.has(balls[i].plugin?.blobId)) + ) { + balls.splice(i, 1); } - if (isNegativeProgress) { - const winCond = currentScene?.config?.winCondition; - if (winCond?.type === "colorClear" && Array.isArray(winCond.targets)) { - winCond.targets.forEach((target) => { - const key = normalizeColor(target.color); - const current = clearedByColor[key] || 0; - clearedByColor[key] = Math.max(0, current - chain.bodies.length); - }); - } - } - } else { - chain.constraints.forEach((c) => World.remove(world, c)); - chain.bodies.forEach((b) => setHighlight(b, false)); } + }; + + const applyNegativeProgressPenalty = (chainLength) => { + const winCond = currentScene?.config?.winCondition; + if (winCond?.type !== "colorClear" || !Array.isArray(winCond.targets)) { + return; + } + winCond.targets.forEach((target) => { + const key = normalizeColor(target.color); + const current = clearedByColor[key] || 0; + clearedByColor[key] = Math.max(0, current - chainLength); + }); + }; + + const resetChainState = () => { chain.active = false; chain.color = null; chain.bodies = []; @@ -531,6 +531,31 @@ checkWinCondition(); }; + const finishChain = (releasePoint) => { + if (!chain.active || gameOver || isPaused) return; + const chainLength = chain.bodies.length; + if (chainLength >= config.minChain) { + updateLongestChain(chainLength); + const { gain, isNegativeProgress } = getChainScoreState(); + score += gain; + clearedCount += chainLength; + if (score > highScore) { + highScore = score; + saveHighScore(currentScene.id, highScore); + } + ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color); + removeChainConstraints(); + removeChainBodies(); + if (isNegativeProgress) { + applyNegativeProgressPenalty(chainLength); + } + } else { + removeChainConstraints(); + chain.bodies.forEach((b) => setHighlight(b, false)); + } + resetChainState(); + }; + const updateHud = () => { ui.updateHud({ spawnIntervalMs: config.spawnIntervalMs,