refactor; level reorder
This commit is contained in:
75
src/main.js
75
src/main.js
@@ -151,55 +151,12 @@
|
||||
let lastTimerDisplay = null;
|
||||
let dragConstraint = null;
|
||||
|
||||
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 = () => {
|
||||
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 {
|
||||
loadHighScore = () => 0,
|
||||
loadLongestChain = () => 0,
|
||||
saveHighScore = () => {},
|
||||
saveLongestChain = () => {},
|
||||
} = window.PhysilinksStorage || {};
|
||||
|
||||
const applyScene = (sceneId) => {
|
||||
const next = getSceneById(sceneId) || scenes[0];
|
||||
@@ -234,6 +191,13 @@
|
||||
: defaultMessageConfig.colors,
|
||||
};
|
||||
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 =
|
||||
typeof next.config.gravityScale === "number"
|
||||
? next.config.gravityScale
|
||||
@@ -432,8 +396,9 @@
|
||||
const getSquarePlayArea = () => {
|
||||
if (!currentScene?.config?.squarePlayArea) return null;
|
||||
const size = Math.min(width, height);
|
||||
const left = (width - size) / 2;
|
||||
const top = (height - size) / 2;
|
||||
const offset = world?.plugin?.squareOffset || 0;
|
||||
const left = (width - size) / 2 + offset;
|
||||
const top = (height - size) / 2 + offset;
|
||||
return { size, left, top };
|
||||
};
|
||||
|
||||
@@ -447,7 +412,7 @@
|
||||
}
|
||||
if (typeof currentScene?.config?.spawnInsets === "function") {
|
||||
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.right)) right = res.right;
|
||||
} catch (err) {
|
||||
@@ -764,7 +729,7 @@
|
||||
const chainLength = chain.bodies.length;
|
||||
if (chainLength > longestChainRecord) {
|
||||
longestChainRecord = chainLength;
|
||||
saveLongestChain();
|
||||
saveLongestChain(currentScene.id, longestChainRecord);
|
||||
console.log(
|
||||
"New longest chain record",
|
||||
chainLength,
|
||||
@@ -794,7 +759,7 @@
|
||||
clearedCount += chain.bodies.length;
|
||||
if (score > highScore) {
|
||||
highScore = score;
|
||||
saveHighScore();
|
||||
saveHighScore(currentScene.id, highScore);
|
||||
}
|
||||
ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color);
|
||||
chain.constraints.forEach((c) => World.remove(world, c));
|
||||
@@ -1135,7 +1100,7 @@
|
||||
const side = config.ballRadius * 2;
|
||||
const body = Bodies.rectangle(x, y, side, side, {
|
||||
...commonOpts,
|
||||
chamfer: 4,
|
||||
chamfer: 0,
|
||||
});
|
||||
body.plugin = {
|
||||
color,
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
const scenes = window.PhysilinksSceneDefs || [];
|
||||
const desiredOrder = [
|
||||
"scene-grid",
|
||||
"low-g-terraces",
|
||||
"fast-drop-maze",
|
||||
"balanced",
|
||||
"stack-blocks",
|
||||
"scene-lava",
|
||||
"swirl-arena",
|
||||
"relax",
|
||||
"stack-blocks",
|
||||
"low-g-terraces",
|
||||
"fast-drop-maze",
|
||||
"storm-grid",
|
||||
];
|
||||
const orderedScenes = desiredOrder
|
||||
|
||||
@@ -58,11 +58,25 @@
|
||||
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: 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 } },
|
||||
winCondition: {
|
||||
type: "timer",
|
||||
durationSec: 90,
|
||||
onWin: { shoveBalls: true },
|
||||
},
|
||||
link: {
|
||||
stiffness: 0.82,
|
||||
lengthScale: 1.05,
|
||||
@@ -87,10 +101,11 @@
|
||||
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;
|
||||
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) {
|
||||
if (window?.PhysilinksUI?.instance?.showFloatingMessage) {
|
||||
window.PhysilinksUI.instance.showFloatingMessage(
|
||||
{ text: upcoming.label || "Gust incoming" },
|
||||
{ durationMs: 2200 },
|
||||
@@ -108,7 +123,12 @@
|
||||
},
|
||||
createBodies: (w, h) => {
|
||||
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) => {
|
||||
b.plugin = b.plugin || {};
|
||||
b.plugin.stormWall = true;
|
||||
|
||||
51
src/storage.js
Normal file
51
src/storage.js
Normal 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,
|
||||
};
|
||||
})();
|
||||
24
src/ui.js
24
src/ui.js
@@ -232,6 +232,8 @@
|
||||
el.appendChild(textSpan);
|
||||
};
|
||||
|
||||
const activeMessages = [];
|
||||
|
||||
const showFloatingMessage = (message, options = {}) => {
|
||||
if (!floatingMessagesEl) return;
|
||||
const msgObj =
|
||||
@@ -250,18 +252,24 @@
|
||||
el.className = "floating-message";
|
||||
renderFloatingMessage(el, text, colors);
|
||||
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);
|
||||
requestAnimationFrame(() => {
|
||||
el.classList.add("visible");
|
||||
});
|
||||
setTimeout(() => {
|
||||
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);
|
||||
activeMessages.push(el);
|
||||
};
|
||||
|
||||
return {
|
||||
const api = {
|
||||
sceneEl,
|
||||
updateHud,
|
||||
buildLegend,
|
||||
@@ -278,7 +286,15 @@
|
||||
showFloatingMessage,
|
||||
setMessageDefaults,
|
||||
};
|
||||
return api;
|
||||
};
|
||||
|
||||
window.PhysilinksUI = { create };
|
||||
window.PhysilinksUI = {
|
||||
create: (...args) => {
|
||||
const instance = create(...args);
|
||||
window.PhysilinksUI.instance = instance;
|
||||
return instance;
|
||||
},
|
||||
instance: null,
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user