From e500380a7ef7309dbc44d506d3181c166c6be7b6 Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Sun, 14 Dec 2025 21:05:25 +0100 Subject: [PATCH] Add stack blocks scene with column spawning --- index.html | 1 + src/main.js | 91 +++++++++++++++++++++++++++++++++++++-------- src/scenes/index.js | 1 + 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index 69bdee6..3255d03 100644 --- a/index.html +++ b/index.html @@ -102,6 +102,7 @@ + diff --git a/src/main.js b/src/main.js index a6fdd0f..d697930 100644 --- a/src/main.js +++ b/src/main.js @@ -52,6 +52,7 @@ minChain: 3, palette: ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"], ballRadius: 18, + ballShape: "circle", link: { stiffness: 0.85, lengthScale: 1.05, // max stretch factor; slack below this @@ -320,12 +321,18 @@ (Math.random() - 0.5) * Math.max(spawnJitter, config.ballRadius), } : null; - const x = + const columnCount = currentScene?.config?.spawnColumns; + let x = centerSpawn?.x ?? Math.max( config.ballRadius + 10, Math.min(width - config.ballRadius - 10, Math.random() * width), ); + if (Number.isFinite(columnCount) && columnCount > 0) { + const columnWidth = width / columnCount; + const colIndex = Math.floor(Math.random() * columnCount); + x = columnWidth * (colIndex + 0.5); + } const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom"; const y = centerSpawn?.y ?? @@ -342,21 +349,30 @@ Math.floor(Math.random() * (batchMax - batchMin + 1)) + batchMin, ); for (let i = 0; i < batchCount; i += 1) { - const blob = createBallBodies( - Math.min( - Math.max( - config.ballRadius + 10, - x + (i - batchCount / 2) * config.ballRadius * 1.5, - ), - width - config.ballRadius - 10, + const targetX = Math.min( + Math.max( + config.ballRadius + 10, + x + (i - batchCount / 2) * config.ballRadius * 1.5, ), - y + - i * - (spawnFromBottom - ? -config.ballRadius * 0.5 - : config.ballRadius * 0.5), - color, + width - config.ballRadius - 10, ); + const targetY = + y + + i * + (spawnFromBottom + ? -config.ballRadius * 0.5 + : config.ballRadius * 0.5); + const halfSize = config.ballRadius * 1.05; + const region = { + min: { x: targetX - halfSize, y: targetY - halfSize }, + max: { x: targetX + halfSize, y: targetY + halfSize }, + }; + const hits = Query.region(balls, region); + if (hits && hits.length > 0) { + triggerGameOver(); + return; + } + const blob = createBallBodies(targetX, targetY, color); if (blob.constraints.length > 0 && blob.blobId) { blobConstraints.set(blob.blobId, blob.constraints); } @@ -402,6 +418,39 @@ } }; + const spawnInitialColumns = () => { + const rows = currentScene?.config?.initialRows; + const columns = currentScene?.config?.spawnColumns; + if (!Number.isFinite(rows) || rows <= 0) return false; + if (!Number.isFinite(columns) || columns <= 0) return false; + const columnWidth = width / columns; + const side = config.ballRadius * 2; + const rowGap = side * 1.05; + for (let r = 0; r < rows; r += 1) { + const y = config.ballRadius * 1.5 + r * rowGap; + for (let c = 0; c < columns; c += 1) { + const x = columnWidth * (c + 0.5); + const color = + config.palette[Math.floor(Math.random() * config.palette.length)]; + const blob = createBallBodies(x, y, color); + blob.bodies.forEach((body) => { + body.plugin = body.plugin || {}; + body.plugin.hasEntered = true; + balls.push(body); + World.add(world, body); + }); + if (blob.constraints.length > 0) { + World.add(world, blob.constraints); + } + if (blob.constraints.length > 0 && blob.blobId) { + blobConstraints.set(blob.blobId, blob.constraints); + } + } + } + spawnCount += rows * columns; + return true; + }; + const cleanupBall = (ball) => { if (ball.plugin && ball.plugin.entryCheckId) { clearTimeout(ball.plugin.entryCheckId); @@ -517,7 +566,10 @@ if (isGridScene()) { spawnGridBalls(); } else { - spawnInitialBurst(); + const spawnedGrid = spawnInitialColumns(); + if (!spawnedGrid) { + spawnInitialBurst(); + } startSpawner(); } showGoalIntro(); @@ -1000,6 +1052,15 @@ body.plugin = { color, hasEntered: false, entryCheckId: null }; return { bodies: [body], constraints: [], blobId: null }; } + if (currentScene?.config?.ballShape === "rect") { + const side = config.ballRadius * 2; + const body = Bodies.rectangle(x, y, side, side, { + ...commonOpts, + chamfer: 4, + }); + 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 }; diff --git a/src/scenes/index.js b/src/scenes/index.js index aba2356..f9c004a 100644 --- a/src/scenes/index.js +++ b/src/scenes/index.js @@ -8,6 +8,7 @@ "scene-lava", "swirl-arena", "relax", + "stack-blocks", ]; const orderedScenes = desiredOrder .map((id) => scenes.find((s) => s.id === id))