From e197a02fd0c1d4afad829ec640b92af5dc3256e9 Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Sun, 14 Dec 2025 22:13:25 +0100 Subject: [PATCH] Add Storm Grid Shift scene --- index.html | 1 + src/scenes/index.js | 1 + src/scenes/scene-storm-grid.js | 119 +++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/scenes/scene-storm-grid.js diff --git a/index.html b/index.html index 3255d03..350d6b2 100644 --- a/index.html +++ b/index.html @@ -102,6 +102,7 @@ + diff --git a/src/scenes/index.js b/src/scenes/index.js index f9c004a..9ef3988 100644 --- a/src/scenes/index.js +++ b/src/scenes/index.js @@ -9,6 +9,7 @@ "swirl-arena", "relax", "stack-blocks", + "storm-grid", ]; const orderedScenes = desiredOrder .map((id) => scenes.find((s) => s.id === id)) diff --git a/src/scenes/scene-storm-grid.js b/src/scenes/scene-storm-grid.js new file mode 100644 index 0000000..74cd12a --- /dev/null +++ b/src/scenes/scene-storm-grid.js @@ -0,0 +1,119 @@ +(() => { + const { Bodies } = Matter; + const scenes = (window.PhysilinksSceneDefs = + window.PhysilinksSceneDefs || []); + + const makeSquareBodies = (w, h, offset = 0, wallThickness = 24) => { + const squareSize = Math.min(w, h) * 0.82; + const left = (w - squareSize) / 2 + offset; + const top = (h - squareSize) / 2 + offset; + const render = { + fillStyle: "#0b1222", + strokeStyle: "#334155", + lineWidth: 2, + }; + const floorHeight = Math.max(30, squareSize * 0.06); + return [ + Bodies.rectangle( + left - wallThickness / 2, + h / 2, + wallThickness, + h + wallThickness * 2, + { isStatic: true, render }, + ), + Bodies.rectangle( + left + squareSize + wallThickness / 2, + h / 2, + wallThickness, + h + wallThickness * 2, + { isStatic: true, render }, + ), + Bodies.rectangle( + w / 2, + h + floorHeight / 2, + w + wallThickness * 2, + floorHeight, + { isStatic: true, restitution: 0.1, render }, + ), + ]; + }; + + scenes.push({ + id: "storm-grid", + name: "Storm Grid Shift", + config: { + gravity: 0.9, + spawnIntervalMs: 520, + autoSpawn: true, + minChain: 3, + palette: ["#38bdf8", "#f97316", "#facc15", "#22c55e"], + ballRadius: 18, + ballShape: "rect", + spawnColumns: 10, + sizeFromColumns: true, + initialRows: 3, + rowGapMultiplier: 1, + squarePlayArea: true, + requireClearSpawn: true, + spawnInset: 0, + spawnIntervals: [ + { seconds: 0, gravityX: 0, gravityY: 0.9, label: "Calm start" }, + { seconds: 20, gravityX: 0.2, gravityY: 0.88, label: "Gust: East pull" }, + { seconds: 40, gravityX: -0.25, gravityY: 0.85, label: "Gust: West pull" }, + { seconds: 60, gravityX: 0, gravityY: 0.7, label: "Updraft" }, + ], + winCondition: { type: "timer", durationSec: 90, onWin: { shoveBalls: true } }, + link: { + stiffness: 0.82, + lengthScale: 1.05, + damping: 0.08, + lineWidth: 3, + rope: true, + renderType: "line", + maxLengthMultiplier: 3.3, + }, + onBeforeUpdate: ({ engine, width, height }) => { + const state = engine.plugin.stormState || {}; + const now = (engine.timing?.timestamp || 0) / 1000; + if (!state.startTime) { + state.startTime = now; + state.nextIdx = 0; + engine.plugin.stormState = state; + } + const elapsed = now - state.startTime; + const steps = engine.world.plugin?.stormSteps || []; + const upcoming = steps[state.nextIdx]; + if (upcoming && elapsed >= upcoming.seconds) { + engine.gravity.x = upcoming.gravityX; + engine.gravity.y = upcoming.gravityY; + // Nudge play area offset to keep things lively. + const offset = ((state.nextIdx % 2 === 0 ? 1 : -1) * Math.min(width, height)) / 50; + engine.world.plugin.squareOffset = offset; + state.nextIdx += 1; + if (typeof window?.PhysilinksUI?.create === "function" && window.PhysilinksUI?.instance) { + window.PhysilinksUI.instance.showFloatingMessage( + { text: upcoming.label || "Gust incoming" }, + { durationMs: 2200 }, + ); + } + } + }, + spawnInsets: ({ width, height, world }) => { + const wallThickness = Math.max(20, Math.min(width, height) * 0.02); + const offset = world?.plugin?.squareOffset || 0; + const squareSize = Math.min(width, height) * 0.82; + const left = (width - squareSize) / 2 + offset; + return { left, right: width - (left + squareSize) }; + }, + }, + createBodies: (w, h) => { + const offset = 0; + const walls = makeSquareBodies(w, h, offset, Math.max(20, Math.min(w, h) * 0.02)); + walls.forEach((b) => { + b.plugin = b.plugin || {}; + b.plugin.stormWall = true; + }); + return walls; + }, + }); +})();