From ed7e12b9c10cc85f4464b55929d4024b90745cfa Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Sat, 13 Dec 2025 21:18:33 +0100 Subject: [PATCH] Fix color matching and blob types --- main.js | 82 ++++++++++++++++++++++++++++++++++++++-- scenes/scene-lavalamp.js | 5 +-- scenes/scene-relax.js | 3 +- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/main.js b/main.js index de3e0ea..d110016 100644 --- a/main.js +++ b/main.js @@ -108,6 +108,7 @@ let levelWon = false; let timerEndMs = null; let lastTimerDisplay = null; + let dragConstraint = null; const makeStorageKey = (sceneId) => `physilinks-highscore-${sceneId}`; @@ -345,6 +346,7 @@ score = 0; clearedCount = 0; clearedByColor = {}; + endDrag(); const winCond = currentScene?.config?.winCondition; if (winCond?.type === "timer") { const duration = winCond.durationSec ?? 120; @@ -564,6 +566,39 @@ return hits[0]; }; + const getDraggableBody = (point) => { + const draggables = [ + ...boundaries.filter((b) => b.plugin?.draggable), + ...balls.filter((b) => b.plugin?.draggable), + ]; + const hits = Query.point(draggables, point); + return hits[0]; + }; + + const startDrag = (body, point) => { + endDrag(); + dragConstraint = Constraint.create({ + pointA: point, + bodyB: body, + stiffness: 0.2, + damping: 0.3, + render: { visible: false }, + }); + World.add(world, dragConstraint); + }; + + const updateDrag = (point) => { + if (!dragConstraint) return; + dragConstraint.pointA = point; + }; + + const endDrag = () => { + if (dragConstraint) { + World.remove(world, dragConstraint); + dragConstraint = null; + } + }; + const getPointerPosition = (evt) => { const rect = render.canvas.getBoundingClientRect(); const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX; @@ -577,10 +612,20 @@ const handlePointerDown = (evt) => { if (gameOver || isPaused || levelWon) return; const point = getPointerPosition(evt); + const dragTarget = getDraggableBody(point); + if (dragTarget) { + startDrag(dragTarget, point); + return; + } const body = pickBody(point); if (!body) return; + if (!currentScene?.config?.relaxMode) { + // Only allow linking same colors unless relax mode explicitly opts out. + chain.color = body.plugin.color; + } else { + chain.color = body.plugin.color; + } chain.active = true; - chain.color = body.plugin.color; chain.bodies = [body]; chain.constraints = []; chain.pointer = point; @@ -589,6 +634,10 @@ }; const handlePointerMove = (evt) => { + if (dragConstraint) { + updateDrag(getPointerPosition(evt)); + return; + } if (!chain.active) return; if (gameOver || isPaused || levelWon) return; const point = getPointerPosition(evt); @@ -603,7 +652,8 @@ } return; } - if (body.plugin.color !== chain.color) return; + if (!currentScene?.config?.relaxMode && body.plugin.color !== chain.color) + return; const maxLinkDist = getMaxLinkDistance(); const dist = Vector.magnitude( Vector.sub(chain.bodies[chain.bodies.length - 1].position, body.position), @@ -612,7 +662,13 @@ addToChain(body); }; - const handlePointerUp = () => finishChain(chain.pointer); + const handlePointerUp = () => { + if (dragConstraint) { + endDrag(); + return; + } + finishChain(chain.pointer); + }; render.canvas.addEventListener("mousedown", handlePointerDown); render.canvas.addEventListener("mousemove", handlePointerMove); @@ -706,7 +762,7 @@ lineWidth: 2, }, }; - if (currentScene?.config?.blobBalls) { + if (currentScene?.config?.blobBalls === "soft") { const cols = 3; const rows = 2; const radius = Math.max(10, config.ballRadius * 0.55); @@ -737,7 +793,24 @@ }); return { bodies: soft.bodies, constraints: soft.constraints, blobId }; } + if (currentScene?.config?.blobBalls === "jagged") { + const points = []; + const segments = 6; + for (let i = 0; i < segments; i += 1) { + const angle = Math.min((i / segments) * Math.PI * 2, Math.PI * 2); + const variance = 0.6 + Math.random() * 0.5; + const r = config.ballRadius * variance; + points.push({ + x: x + Math.cos(angle) * r, + y: y + Math.sin(angle) * r, + }); + } + const body = Bodies.fromVertices(x, y, [points], commonOpts, true); + body.plugin = { color, hasEntered: false, entryCheckId: null }; + return { bodies: [body], constraints: [], blobId: null }; + } const body = Bodies.circle(x, y, config.ballRadius, commonOpts); + body.plugin = { color, hasEntered: false, entryCheckId: null }; return { bodies: [body], constraints: [], blobId: null }; }; @@ -823,6 +896,7 @@ Body.setVelocity(ball, { x: 0, y: 0 }); }); resetChainVisuals(); + endDrag(); }; const handleResize = () => { diff --git a/scenes/scene-lavalamp.js b/scenes/scene-lavalamp.js index 1db176a..dc2f0f1 100644 --- a/scenes/scene-lavalamp.js +++ b/scenes/scene-lavalamp.js @@ -41,12 +41,11 @@ minChain: 3, palette: ["#f472b6", "#38bdf8", "#fbbf24", "#a855f7", "#22c55e"], ballRadius: 26, - blobBalls: true, + blobBalls: "jagged", winCondition: { type: "score", target: 25000, onWin: { setGravity: -0.55, removeCurves: true }, - nextSceneId: "scene-grid", }, link: { stiffness: 0.7, @@ -55,7 +54,7 @@ lineWidth: 3, rope: true, renderType: "line", - maxLengthMultiplier: 4.8, + maxLengthMultiplier: 5.8, }, }, createBodies: (w, h) => { diff --git a/scenes/scene-relax.js b/scenes/scene-relax.js index 3d9e104..fff986f 100644 --- a/scenes/scene-relax.js +++ b/scenes/scene-relax.js @@ -16,8 +16,9 @@ minChain: 2, palette: ["#38bdf8", "#f472b6", "#fbbf24", "#22c55e", "#a855f7"], ballRadius: 20, - blobBalls: false, + blobBalls: "soft", noGameOver: true, + relaxMode: true, winCondition: { type: "timer", durationSec: 120,