From de28fa0cdd6a8be67265c9ba4477af10504208ad Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Fri, 12 Dec 2025 13:17:52 +0100 Subject: [PATCH] Store high scores per scene and add README --- README.md | 26 ++++++++++++++++++++++++++ main.js | 12 ++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e9e416c --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Physilinks + +Physilinks is a browser-based physics linking game built with Matter.js. Match and chain same-colored falling balls; link enough to clear them and rack up points. + +## Play instructions +- Open `index.html` in a modern browser (desktop or mobile touch). +- Choose a scene from the selector (changes gravity, obstacles, spawn rate, palette, ball size). Switching scenes restarts the run. +- Click/touch a ball to start a chain; drag through balls of the same color to add them. Drag back to the previous ball to undo the last link. +- Release: if the chain length is below the minimum, links vanish; if it meets/exceeds the minimum, linked balls are cleared and you score `10 × length²` (popup shows the gain). +- The top is open; if a new ball cannot enter because the entry is blocked, the run ends. The overlay shows final score with a restart button. +- Pause/resume with the button or `Esc`. HUD shows spawn rate, min link, chain length, score, per-scene high score, and palette legend. + +## Tech notes +- **Engine**: Matter.js (via CDN). Canvas rendering with custom overlays for HUD, pause, game over, and score popups. +- **Scenes**: Defined in `main.js` as an array of presets (config + `createBodies` factory). Applying a scene updates gravity, spawn rate, ball radius, palette, and static bodies, then restarts the game. +- **Physics entities**: Falling balls (`Bodies.circle`) with gentle restitution/friction; static boundaries/obstacles per scene. The top is open; sides and floor are static bodies. +- **Input**: Pointer/touch events mapped to scene coords; chain state tracks bodies and a dashed preview line to the pointer. Undo by dragging back to the previous node. +- **Scoring**: `10 × length²` per cleared chain. Score popup rendered as DOM element near release point. +- **Persistence**: Per-scene high score stored in `localStorage` under `physilinks-highscore-`; loaded on scene change; HUD shows current scene's best. +- **Game loop**: Single Matter runner started once. Pause sets `engine.timing.timeScale = 0` and stops spawning; resume resets time scale and spawner. Game over stops spawner, freezes physics, and shows overlay. +- **Lose detection**: Spawned balls monitor entry; if they remain near the spawn zone with negligible velocity after a short delay, the run is over. + +## Development quick start +- No build step. Open `index.html` directly in the browser. +- Key files: `index.html` (layout/styles), `main.js` (game logic). +- Adjust or add scenes in `main.js` by extending the `scenes` array with config and a `createBodies(width, height)` function. diff --git a/main.js b/main.js index 5710146..807aab9 100644 --- a/main.js +++ b/main.js @@ -24,7 +24,7 @@ id: "scene1", name: "Balanced (default)", config: { - gravity: 0.78, + gravity: 0.88, spawnIntervalMs: 720, minChain: 3, palette: ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"], @@ -175,11 +175,11 @@ let gameOver = false; let isPaused = false; - const STORAGE_KEY = "physilinks-highscore"; + const makeStorageKey = (sceneId) => `physilinks-highscore-${sceneId}`; - const loadHighScore = () => { + const loadHighScore = (sceneId) => { try { - const raw = localStorage.getItem(STORAGE_KEY); + const raw = localStorage.getItem(makeStorageKey(sceneId)); const parsed = parseInt(raw, 10); return Number.isFinite(parsed) ? parsed : 0; } catch (err) { @@ -189,7 +189,7 @@ const saveHighScore = () => { try { - localStorage.setItem(STORAGE_KEY, String(highScore)); + localStorage.setItem(makeStorageKey(currentScene.id), String(highScore)); } catch (err) { // ignore write failures (private mode or blocked storage) } @@ -212,6 +212,7 @@ if (sceneSelectEl) sceneSelectEl.value = next.id; Object.assign(config, next.config); engine.gravity.y = config.gravity; + highScore = loadHighScore(next.id); rebuildSceneBodies(); buildLegend(); restartGame(); @@ -583,7 +584,6 @@ if (sceneSelectEl) { sceneSelectEl.addEventListener("change", (e) => applyScene(e.target.value)); } - highScore = loadHighScore(); populateSceneSelect(); applyScene(currentScene.id); })();