refactor; level reorder
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
75
src/main.js
75
src/main.js
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
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);
|
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,
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user