refactor; level reorder

This commit is contained in:
Daddy32
2025-12-15 16:43:13 +01:00
parent e197a02fd0
commit 9a68214c8d
7 changed files with 123 additions and 70 deletions

View File

@@ -106,6 +106,7 @@
<script src="./src/scenes/scene-stack-blocks.js"></script> <script src="./src/scenes/scene-stack-blocks.js"></script>
<script src="./src/scenes/index.js"></script> <script src="./src/scenes/index.js"></script>
<script src="./src/ui.js"></script> <script src="./src/ui.js"></script>
<script src="./src/storage.js"></script>
<script src="./src/main.js"></script> <script src="./src/main.js"></script>
</body> </body>
</html> </html>

View File

@@ -151,55 +151,12 @@
let lastTimerDisplay = null; let lastTimerDisplay = null;
let dragConstraint = null; let dragConstraint = null;
const makeStorageKey = (sceneId) => `physilinks-highscore-${sceneId}`; const {
const makeChainKey = (sceneId) => `physilinks-longestchain-${sceneId}`; loadHighScore = () => 0,
loadLongestChain = () => 0,
const loadHighScore = (sceneId) => { saveHighScore = () => {},
try { saveLongestChain = () => {},
const raw = localStorage.getItem(makeStorageKey(sceneId)); } = window.PhysilinksStorage || {};
const parsed = parseInt(raw, 10);
return Number.isFinite(parsed) ? parsed : 0;
} catch (err) {
console.error("Failed to load high score", { sceneId, err });
return 0;
}
};
const loadLongestChain = (sceneId) => {
try {
const raw = localStorage.getItem(makeChainKey(sceneId));
const parsed = parseInt(raw, 10);
return Number.isFinite(parsed) ? parsed : 0;
} catch (err) {
console.error("Failed to load longest chain", { sceneId, err });
return 0;
}
};
const saveHighScore = () => {
try {
localStorage.setItem(makeStorageKey(currentScene.id), String(highScore));
} catch (err) {
console.error("Failed to save high score", {
sceneId: currentScene.id,
err,
});
}
};
const saveLongestChain = () => {
try {
localStorage.setItem(
makeChainKey(currentScene.id),
String(longestChainRecord),
);
} catch (err) {
console.error("Failed to save longest chain", {
sceneId: currentScene.id,
err,
});
}
};
const applyScene = (sceneId) => { const applyScene = (sceneId) => {
const next = getSceneById(sceneId) || scenes[0]; const next = getSceneById(sceneId) || scenes[0];
@@ -234,6 +191,13 @@
: defaultMessageConfig.colors, : defaultMessageConfig.colors,
}; };
ui.setMessageDefaults(config.messages); ui.setMessageDefaults(config.messages);
world.plugin = world.plugin || {};
world.plugin.stormSteps = Array.isArray(next.config?.spawnIntervals)
? next.config.spawnIntervals
: null;
world.plugin.squareOffset = 0;
engine.plugin = engine.plugin || {};
engine.plugin.stormState = null;
engine.gravity.scale = engine.gravity.scale =
typeof next.config.gravityScale === "number" typeof next.config.gravityScale === "number"
? next.config.gravityScale ? next.config.gravityScale
@@ -432,8 +396,9 @@
const getSquarePlayArea = () => { const getSquarePlayArea = () => {
if (!currentScene?.config?.squarePlayArea) return null; if (!currentScene?.config?.squarePlayArea) return null;
const size = Math.min(width, height); const size = Math.min(width, height);
const left = (width - size) / 2; const offset = world?.plugin?.squareOffset || 0;
const top = (height - size) / 2; const left = (width - size) / 2 + offset;
const top = (height - size) / 2 + offset;
return { size, left, top }; return { size, left, top };
}; };
@@ -447,7 +412,7 @@
} }
if (typeof currentScene?.config?.spawnInsets === "function") { if (typeof currentScene?.config?.spawnInsets === "function") {
try { try {
const res = currentScene.config.spawnInsets({ width, height }); const res = currentScene.config.spawnInsets({ width, height, world });
if (res && Number.isFinite(res.left)) left = res.left; if (res && Number.isFinite(res.left)) left = res.left;
if (res && Number.isFinite(res.right)) right = res.right; if (res && Number.isFinite(res.right)) right = res.right;
} catch (err) { } catch (err) {
@@ -764,7 +729,7 @@
const chainLength = chain.bodies.length; const chainLength = chain.bodies.length;
if (chainLength > longestChainRecord) { if (chainLength > longestChainRecord) {
longestChainRecord = chainLength; longestChainRecord = chainLength;
saveLongestChain(); saveLongestChain(currentScene.id, longestChainRecord);
console.log( console.log(
"New longest chain record", "New longest chain record",
chainLength, chainLength,
@@ -794,7 +759,7 @@
clearedCount += chain.bodies.length; clearedCount += chain.bodies.length;
if (score > highScore) { if (score > highScore) {
highScore = score; highScore = score;
saveHighScore(); saveHighScore(currentScene.id, highScore);
} }
ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color); ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color);
chain.constraints.forEach((c) => World.remove(world, c)); chain.constraints.forEach((c) => World.remove(world, c));
@@ -1135,7 +1100,7 @@
const side = config.ballRadius * 2; const side = config.ballRadius * 2;
const body = Bodies.rectangle(x, y, side, side, { const body = Bodies.rectangle(x, y, side, side, {
...commonOpts, ...commonOpts,
chamfer: 4, chamfer: 0,
}); });
body.plugin = { body.plugin = {
color, color,

View File

@@ -2,13 +2,13 @@
const scenes = window.PhysilinksSceneDefs || []; const scenes = window.PhysilinksSceneDefs || [];
const desiredOrder = [ const desiredOrder = [
"scene-grid", "scene-grid",
"low-g-terraces",
"fast-drop-maze",
"balanced", "balanced",
"stack-blocks",
"scene-lava", "scene-lava",
"swirl-arena", "swirl-arena",
"relax", "relax",
"stack-blocks", "low-g-terraces",
"fast-drop-maze",
"storm-grid", "storm-grid",
]; ];
const orderedScenes = desiredOrder const orderedScenes = desiredOrder

View File

@@ -58,11 +58,25 @@
spawnInset: 0, spawnInset: 0,
spawnIntervals: [ spawnIntervals: [
{ seconds: 0, gravityX: 0, gravityY: 0.9, label: "Calm start" }, { 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: 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" }, { seconds: 60, gravityX: 0, gravityY: 0.7, label: "Updraft" },
], ],
winCondition: { type: "timer", durationSec: 90, onWin: { shoveBalls: true } }, winCondition: {
type: "timer",
durationSec: 90,
onWin: { shoveBalls: true },
},
link: { link: {
stiffness: 0.82, stiffness: 0.82,
lengthScale: 1.05, lengthScale: 1.05,
@@ -87,10 +101,11 @@
engine.gravity.x = upcoming.gravityX; engine.gravity.x = upcoming.gravityX;
engine.gravity.y = upcoming.gravityY; engine.gravity.y = upcoming.gravityY;
// Nudge play area offset to keep things lively. // Nudge play area offset to keep things lively.
const offset = ((state.nextIdx % 2 === 0 ? 1 : -1) * Math.min(width, height)) / 50; const offset =
((state.nextIdx % 2 === 0 ? 1 : -1) * Math.min(width, height)) / 50;
engine.world.plugin.squareOffset = offset; engine.world.plugin.squareOffset = offset;
state.nextIdx += 1; state.nextIdx += 1;
if (typeof window?.PhysilinksUI?.create === "function" && window.PhysilinksUI?.instance) { if (window?.PhysilinksUI?.instance?.showFloatingMessage) {
window.PhysilinksUI.instance.showFloatingMessage( window.PhysilinksUI.instance.showFloatingMessage(
{ text: upcoming.label || "Gust incoming" }, { text: upcoming.label || "Gust incoming" },
{ durationMs: 2200 }, { durationMs: 2200 },
@@ -108,7 +123,12 @@
}, },
createBodies: (w, h) => { createBodies: (w, h) => {
const offset = 0; const offset = 0;
const walls = makeSquareBodies(w, h, offset, Math.max(20, Math.min(w, h) * 0.02)); const walls = makeSquareBodies(
w,
h,
offset,
Math.max(20, Math.min(w, h) * 0.02),
);
walls.forEach((b) => { walls.forEach((b) => {
b.plugin = b.plugin || {}; b.plugin = b.plugin || {};
b.plugin.stormWall = true; b.plugin.stormWall = true;

51
src/storage.js Normal file
View File

@@ -0,0 +1,51 @@
(() => {
const makeStorageKey = (sceneId) => `physilinks-highscore-${sceneId}`;
const makeChainKey = (sceneId) => `physilinks-longestchain-${sceneId}`;
const loadHighScore = (sceneId) => {
try {
const raw = localStorage.getItem(makeStorageKey(sceneId));
const parsed = parseInt(raw, 10);
return Number.isFinite(parsed) ? parsed : 0;
} catch (err) {
console.error("Failed to load high score", { sceneId, err });
return 0;
}
};
const loadLongestChain = (sceneId) => {
try {
const raw = localStorage.getItem(makeChainKey(sceneId));
const parsed = parseInt(raw, 10);
return Number.isFinite(parsed) ? parsed : 0;
} catch (err) {
console.error("Failed to load longest chain", { sceneId, err });
return 0;
}
};
const saveHighScore = (sceneId, highScore) => {
try {
localStorage.setItem(makeStorageKey(sceneId), String(highScore));
} catch (err) {
console.error("Failed to save high score", { sceneId, err });
}
};
const saveLongestChain = (sceneId, longestChain) => {
try {
localStorage.setItem(makeChainKey(sceneId), String(longestChain));
} catch (err) {
console.error("Failed to save longest chain", { sceneId, err });
}
};
window.PhysilinksStorage = {
makeStorageKey,
makeChainKey,
loadHighScore,
loadLongestChain,
saveHighScore,
saveLongestChain,
};
})();

View File

@@ -232,6 +232,8 @@
el.appendChild(textSpan); el.appendChild(textSpan);
}; };
const activeMessages = [];
const showFloatingMessage = (message, options = {}) => { const showFloatingMessage = (message, options = {}) => {
if (!floatingMessagesEl) return; if (!floatingMessagesEl) return;
const msgObj = const msgObj =
@@ -250,18 +252,24 @@
el.className = "floating-message"; el.className = "floating-message";
renderFloatingMessage(el, text, colors); renderFloatingMessage(el, text, colors);
el.style.left = `${position.xPercent ?? 50}%`; el.style.left = `${position.xPercent ?? 50}%`;
el.style.top = `${position.yPercent ?? 10}%`; const verticalOffset = activeMessages.length * 34;
el.style.top = `calc(${position.yPercent ?? 10}% + ${verticalOffset}px)`;
floatingMessagesEl.appendChild(el); floatingMessagesEl.appendChild(el);
requestAnimationFrame(() => { requestAnimationFrame(() => {
el.classList.add("visible"); el.classList.add("visible");
}); });
setTimeout(() => { setTimeout(() => {
el.classList.remove("visible"); el.classList.remove("visible");
setTimeout(() => el.remove(), 260); setTimeout(() => {
el.remove();
const idx = activeMessages.indexOf(el);
if (idx >= 0) activeMessages.splice(idx, 1);
}, 260);
}, durationMs); }, durationMs);
activeMessages.push(el);
}; };
return { const api = {
sceneEl, sceneEl,
updateHud, updateHud,
buildLegend, buildLegend,
@@ -278,7 +286,15 @@
showFloatingMessage, showFloatingMessage,
setMessageDefaults, setMessageDefaults,
}; };
return api;
}; };
window.PhysilinksUI = { create }; window.PhysilinksUI = {
create: (...args) => {
const instance = create(...args);
window.PhysilinksUI.instance = instance;
return instance;
},
instance: null,
};
})(); })();

View File

@@ -208,11 +208,11 @@ canvas {
transform: translate(-50%, 0) scale(0.98); transform: translate(-50%, 0) scale(0.98);
background: rgba(15, 23, 42, 0.76); background: rgba(15, 23, 42, 0.76);
color: #f8fafc; color: #f8fafc;
padding: 15px 20px; padding: 16px 22px;
border-radius: 14px; border-radius: 14px;
border: 1px solid rgba(226, 232, 240, 0.16); border: 1px solid rgba(226, 232, 240, 0.16);
font-weight: 800; font-weight: 800;
font-size: 19px; font-size: 21px;
letter-spacing: 0.4px; letter-spacing: 0.4px;
text-shadow: text-shadow:
0 10px 30px rgba(0, 0, 0, 0.45), 0 10px 30px rgba(0, 0, 0, 0.45),