From 357922510aced3177939fda20b4b0857b753c3b7 Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Sat, 13 Dec 2025 14:03:52 +0100 Subject: [PATCH] Refactor scenes into separate files --- README.md | 2 +- index.html | 6 +- scenes/index.js | 10 +++ scenes/scene-balanced.js | 91 +++++++++++++++++++++++++++ scenes/scene-fastdrop.js | 98 +++++++++++++++++++++++++++++ scenes/scene-grid.js | 129 +++++++++++++++++++++++++++++++++++++++ scenes/scene-lowg.js | 83 +++++++++++++++++++++++++ 7 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 scenes/index.js create mode 100644 scenes/scene-balanced.js create mode 100644 scenes/scene-fastdrop.js create mode 100644 scenes/scene-grid.js create mode 100644 scenes/scene-lowg.js diff --git a/README.md b/README.md index df319ce..a99ce23 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Physilinks is a browser-based physics linking game built with Matter.js. Match a ## File structure - `index.html`: Shell layout and HUD overlays; loads Matter.js plus game scripts. - `styles.css`: Styling for canvas, HUD, overlays, and score popups. -- `scenes.js`: Scene presets and per-scene static body factories. +- `scenes/`: Scene presets split per file (`scene-*.js`) plus `index.js` that registers them to `window.PhysilinksScenes`. - `ui.js`: DOM access, HUD updates, overlays, popups, and control/selector wiring. - `main.js`: Physics setup, state machine, chain interaction, spawning, scene application, and pause/restart logic. diff --git a/index.html b/index.html index 1089d60..5cf76dc 100644 --- a/index.html +++ b/index.html @@ -92,7 +92,11 @@ - + + + + + diff --git a/scenes/index.js b/scenes/index.js new file mode 100644 index 0000000..a302828 --- /dev/null +++ b/scenes/index.js @@ -0,0 +1,10 @@ +(() => { + const scenes = window.PhysilinksSceneDefs || []; + const gridDefault = scenes.find((s) => s.id === "scene-grid"); + const defaultSceneId = gridDefault?.id || scenes[0]?.id || "scene-grid"; + + window.PhysilinksScenes = { + scenes, + defaultSceneId, + }; +})(); diff --git a/scenes/scene-balanced.js b/scenes/scene-balanced.js new file mode 100644 index 0000000..d479d8d --- /dev/null +++ b/scenes/scene-balanced.js @@ -0,0 +1,91 @@ +(() => { + const { Bodies } = Matter; + const scenes = + (window.PhysilinksSceneDefs = window.PhysilinksSceneDefs || []); + + scenes.push({ + id: "scene1", + name: "Balanced", + config: { + gravity: 0.88, + spawnIntervalMs: 520, + minChain: 3, + palette: ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"], + ballRadius: 38, + link: { + stiffness: 0.85, + lengthScale: 1.05, + damping: 0.08, + lineWidth: 3, + rope: true, + renderType: "line", + maxLengthMultiplier: 3.2, + }, + }, + createBodies: (w, h) => { + const floorHeight = Math.max(60, h * 0.12); + const wallThickness = Math.max(32, w * 0.05); + const wallHeight = h * 1.6; + const platformHeight = Math.max(14, h * 0.025); + const smallPlatformWidth = Math.max(140, w * 0.18); + const largePlatformWidth = Math.max(180, w * 0.22); + return [ + Bodies.rectangle( + w / 2, + h + floorHeight / 2, + w + wallThickness * 2, + floorHeight, + { + isStatic: true, + restitution: 0.8, + render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, + }, + ), + Bodies.rectangle( + -wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, + }, + ), + Bodies.rectangle( + w + wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, + }, + ), + Bodies.rectangle( + w * 0.25, + h * 0.55, + smallPlatformWidth, + platformHeight, + { + isStatic: true, + angle: -0.3, + render: { fillStyle: "#22c55e", strokeStyle: "#22c55e" }, + plugin: { rotSpeed: 0.1 }, + }, + ), + Bodies.rectangle( + w * 0.7, + h * 0.4, + largePlatformWidth, + platformHeight * 1.2, + { + isStatic: true, + angle: 0.26, + render: { fillStyle: "#a855f7", strokeStyle: "#a855f7" }, + plugin: { rotSpeed: -0.08 }, + }, + ), + ]; + }, + }); +})(); diff --git a/scenes/scene-fastdrop.js b/scenes/scene-fastdrop.js new file mode 100644 index 0000000..2524bad --- /dev/null +++ b/scenes/scene-fastdrop.js @@ -0,0 +1,98 @@ +(() => { + const { Bodies } = Matter; + const scenes = + (window.PhysilinksSceneDefs = window.PhysilinksSceneDefs || []); + + scenes.push({ + id: "scene3", + name: "Fast drop maze", + config: { + gravity: 1.25, + spawnIntervalMs: 220, + minChain: 3, + palette: ["#e879f9", "#38bdf8", "#f97316", "#22c55e"], + ballRadius: 16, + link: { + stiffness: 1, + lengthScale: 0.85, + damping: 0.15, + lineWidth: 3, + rope: false, + renderType: "line", + maxLengthMultiplier: 23.8, + }, + }, + createBodies: (w, h) => { + const floorHeight = Math.max(60, h * 0.1); + const wallThickness = Math.max(28, w * 0.045); + const wallHeight = h * 1.5; + const pegRadius = Math.max(10, Math.min(22, Math.min(w, h) * 0.03)); + const platformHeight = Math.max(12, h * 0.02); + const leftPlatformWidth = Math.max(120, w * 0.16); + const rightPlatformWidth = Math.max(130, w * 0.18); + const bodies = [ + Bodies.rectangle( + w / 2, + h + floorHeight / 2, + w + wallThickness * 2, + floorHeight, + { + isStatic: true, + restitution: 0.75, + render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" }, + }, + ), + Bodies.rectangle( + -wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" }, + }, + ), + Bodies.rectangle( + w + wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" }, + }, + ), + ]; + for (let i = 0; i < 5; i += 1) { + const x = (w * (i + 1)) / 6; + const y = h * 0.35 + (i % 2 === 0 ? h * 0.06 : -h * 0.05); + bodies.push( + Bodies.circle(x, y, pegRadius, { + isStatic: true, + restitution: 0.9, + render: { fillStyle: "#facc15", strokeStyle: "#facc15" }, + }), + ); + } + bodies.push( + Bodies.rectangle(w * 0.3, h * 0.55, leftPlatformWidth, platformHeight, { + isStatic: true, + angle: -0.3, + render: { fillStyle: "#8b5cf6", strokeStyle: "#8b5cf6" }, + }), + Bodies.rectangle( + w * 0.7, + h * 0.58, + rightPlatformWidth, + platformHeight * 1.05, + { + isStatic: true, + angle: 0.28, + render: { fillStyle: "#10b981", strokeStyle: "#10b981" }, + }, + ), + ); + return bodies; + }, + }); +})(); diff --git a/scenes/scene-grid.js b/scenes/scene-grid.js new file mode 100644 index 0000000..760827b --- /dev/null +++ b/scenes/scene-grid.js @@ -0,0 +1,129 @@ +(() => { + const { Bodies } = Matter; + const scenes = (window.PhysilinksSceneDefs = + window.PhysilinksSceneDefs || []); + + scenes.push({ + id: "scene-grid", + name: "Zero-G Grid (default)", + config: { + gravity: 0, + spawnIntervalMs: 0, + autoSpawn: false, + minChain: 3, + palette: ["#38bdf8", "#f472b6", "#facc15", "#34d399", "#a78bfa"], + ballRadius: 24, + gridPadding: 0.08, // percent of viewport padding applied to both axes + gridBallScale: 0.38, // percent of cell size used as radius + gridLegend: { + A: "#38bdf8", + B: "#f472b6", + C: "#facc15", + D: "#34d399", + E: "#a78bfa", + }, + winCondition: { + type: "clearCount", + target: 25, + nextSceneId: "scene1", + onWin: { setGravity: 0.88 }, + }, + gridLayouts: [ + [ + "AABBBBAA", + "ACCCBCCA", + "ACDDDDCA", + "ACEEEECA", + "ACEEEECA", + "ACDDDDCA", + "ACCCBCCA", + "AABBBBAA", + ], + [ + "AAAABBBA", + "ABBBCCCA", + "ABCDDDCA", + "ABCEEECA", + "ABCEEECA", + "ABCDDDCA", + "ABBBCCCA", + "AAAABBBA", + ], + [ + "AABBCCDD", + "ABBCCDEE", + "ABCCDEEA", + "ACCDDEEA", + "ACCDDEEA", + "ABCCDEEA", + "ABBCCDEE", + "AABBCCDD", + ], + ], + link: { + stiffness: 0.82, + lengthScale: 1.05, + damping: 0.06, + lineWidth: 3, + rope: true, + renderType: "line", + maxLengthMultiplier: 3.8, + }, + }, + createBodies: (w, h) => { + const pad = 0.08; + const usableW = w * (1 - pad * 2); + const usableH = h * (1 - pad * 2); + const gridSize = Math.min(usableW, usableH); + const gridX = (w - gridSize) / 2; + const gridY = (h - gridSize) / 2; + const wallThickness = Math.max(18, gridSize * 0.045); + const innerW = gridSize; + const innerH = gridSize; + const cx = gridX + innerW / 2; + const cy = gridY + innerH / 2; + return [ + Bodies.rectangle( + cx, + gridY - wallThickness / 2, + innerW + wallThickness * 2, + wallThickness, + { + isStatic: true, + render: { fillStyle: "#0b1222", strokeStyle: "#0b1222" }, + }, + ), + Bodies.rectangle( + cx, + gridY + innerH + wallThickness / 2, + innerW + wallThickness * 2, + wallThickness, + { + isStatic: true, + render: { fillStyle: "#0b1222", strokeStyle: "#0b1222" }, + }, + ), + Bodies.rectangle( + gridX - wallThickness / 2, + cy, + wallThickness, + innerH + wallThickness * 2, + { + isStatic: true, + render: { fillStyle: "#0b1222", strokeStyle: "#0b1222" }, + }, + ), + Bodies.rectangle( + gridX + innerW + wallThickness / 2, + cy, + wallThickness, + innerH + wallThickness * 2, + { + isStatic: true, + render: { fillStyle: "#0b1222", strokeStyle: "#0b1222" }, + }, + ), + ]; + }, + }); +})(); diff --git a/scenes/scene-lowg.js b/scenes/scene-lowg.js new file mode 100644 index 0000000..f3a1f82 --- /dev/null +++ b/scenes/scene-lowg.js @@ -0,0 +1,83 @@ +(() => { + const { Bodies } = Matter; + const scenes = + (window.PhysilinksSceneDefs = window.PhysilinksSceneDefs || []); + + scenes.push({ + id: "scene2", + name: "Low-G terraces", + config: { + gravity: 0.65, + spawnIntervalMs: 600, + minChain: 3, + palette: ["#fb7185", "#fbbf24", "#34d399", "#38bdf8"], + ballRadius: 22, + link: { + stiffness: 0.6, + lengthScale: 1, + damping: 0.01, + lineWidth: 4, + rope: false, + renderType: "spring", + maxLengthMultiplier: 4.7, + }, + }, + createBodies: (w, h) => { + const floorHeight = Math.max(70, h * 0.12); + const wallThickness = Math.max(32, w * 0.05); + const wallHeight = h * 1.8; + const ledgeHeight = Math.max(14, h * 0.022); + const leftWidth = Math.max(160, w * 0.18); + const midWidth = Math.max(190, w * 0.26); + const rightWidth = Math.max(150, w * 0.18); + return [ + Bodies.rectangle( + w / 2, + h + floorHeight / 2, + w + wallThickness * 2, + floorHeight, + { + isStatic: true, + restitution: 0.9, + render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, + }, + ), + Bodies.rectangle( + -wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, + }, + ), + Bodies.rectangle( + w + wallThickness / 2, + h / 2, + wallThickness, + wallHeight, + { + isStatic: true, + render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, + }, + ), + Bodies.rectangle(w * 0.2, h * 0.45, leftWidth, ledgeHeight, { + isStatic: true, + angle: 0.08, + render: { fillStyle: "#f97316", strokeStyle: "#f97316" }, + }), + Bodies.rectangle(w * 0.5, h * 0.6, midWidth, ledgeHeight, { + isStatic: true, + angle: -0.04, + render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" }, + }), + Bodies.rectangle(w * 0.8, h * 0.42, rightWidth, ledgeHeight, { + isStatic: true, + angle: 0.14, + render: { fillStyle: "#c084fc", strokeStyle: "#c084fc" }, + }), + ]; + }, + }); +})();