Extract input module

This commit is contained in:
Daddy32
2025-12-15 18:30:42 +01:00
parent 823746a588
commit fb7b6b2e6d
4 changed files with 194 additions and 152 deletions

167
src/input.js Normal file
View File

@@ -0,0 +1,167 @@
(() => {
const { Query, Constraint, World, Vector } = Matter;
const create = ({
render,
world,
balls,
boundaries,
chain,
config,
getCurrentScene,
isPaused,
isLevelWon,
isGameOver,
getMaxLinkDistance,
setHighlight,
removeLastFromChain,
addToChain,
finishChain,
updateHud,
}) => {
let dragConstraint = null;
const getPointerPosition = (evt) => {
const rect = render.canvas.getBoundingClientRect();
const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX;
const clientY = evt.touches ? evt.touches[0].clientY : evt.clientY;
return {
x: clientX - rect.left,
y: clientY - rect.top,
};
};
const getDraggableBody = (point) => {
const draggables = [
...boundaries.filter((b) => b.plugin?.draggable),
...balls.filter((b) => b.plugin?.draggable),
];
const hits = Query.point(draggables, point);
return hits[0];
};
const pickBody = (point) => {
const hits = Query.point(balls, point);
return hits[0];
};
const startDrag = (body, point) => {
endDrag();
dragConstraint = Constraint.create({
pointA: point,
bodyB: body,
stiffness: 0.2,
damping: 0.3,
render: { visible: false },
});
World.add(world, dragConstraint);
};
const updateDrag = (point) => {
if (!dragConstraint) return;
dragConstraint.pointA = point;
};
const endDrag = () => {
if (dragConstraint) {
World.remove(world, dragConstraint);
dragConstraint = null;
}
};
const handlePointerDown = (evt) => {
if (isGameOver() || isPaused() || isLevelWon()) return;
const point = getPointerPosition(evt);
const dragTarget = getDraggableBody(point);
if (dragTarget) {
startDrag(dragTarget, point);
return;
}
const body = pickBody(point);
if (!body) return;
const scene = getCurrentScene();
if (!scene?.config?.relaxMode) {
chain.color = body.plugin.color;
} else {
chain.color = body.plugin.color;
}
chain.active = true;
chain.bodies = [body];
chain.constraints = [];
chain.pointer = point;
setHighlight(body, true);
updateHud();
};
const handlePointerMove = (evt) => {
if (dragConstraint) {
updateDrag(getPointerPosition(evt));
return;
}
if (!chain.active) return;
if (isGameOver() || isPaused() || isLevelWon()) return;
const point = getPointerPosition(evt);
chain.pointer = point;
const body = pickBody(point);
if (!body) return;
const alreadyInChain = chain.bodies.includes(body);
if (alreadyInChain) {
const targetIndex = chain.bodies.indexOf(body);
if (chain.bodies.length > 1 && targetIndex === chain.bodies.length - 2) {
removeLastFromChain();
}
return;
}
const scene = getCurrentScene();
if (!scene?.config?.relaxMode && body.plugin.color !== chain.color) return;
const maxLinkDist = getMaxLinkDistance();
const dist = Vector.magnitude(
Vector.sub(chain.bodies[chain.bodies.length - 1].position, body.position),
);
if (dist > maxLinkDist) return;
addToChain(body);
};
const handlePointerUp = () => {
if (dragConstraint) {
endDrag();
return;
}
finishChain(chain.pointer);
};
render.canvas.addEventListener("mousedown", handlePointerDown);
render.canvas.addEventListener("mousemove", handlePointerMove);
window.addEventListener("mouseup", handlePointerUp);
render.canvas.addEventListener(
"touchstart",
(e) => {
e.preventDefault();
handlePointerDown(e);
},
{ passive: false },
);
render.canvas.addEventListener(
"touchmove",
(e) => {
e.preventDefault();
handlePointerMove(e);
},
{ passive: false },
);
render.canvas.addEventListener(
"touchend",
(e) => {
e.preventDefault();
handlePointerUp(e);
},
{ passive: false },
);
return {
endDrag,
};
};
window.PhysilinksInput = { create };
})();

View File

@@ -1,15 +1,6 @@
(() => {
const {
Engine,
Render,
Runner,
World,
Body,
Constraint,
Events,
Query,
Vector,
} = Matter;
const { Engine, Render, Runner, World, Body, Constraint, Events, Vector } =
Matter;
const { scenes = [], defaultSceneId } = window.PhysilinksScenes || {};
const getSceneById = (sceneId) =>
@@ -143,8 +134,8 @@
let levelWon = false;
let timerEndMs = null;
let lastTimerDisplay = null;
let dragConstraint = null;
let spawnSystem = null;
let input = null;
const {
loadHighScore = () => 0,
@@ -303,7 +294,7 @@
clearedCount = 0;
clearedByColor = {};
goals.resetMilestones();
endDrag();
input?.endDrag();
const winCond = currentScene?.config?.winCondition;
if (winCond?.type === "timer") {
const duration = winCond.durationSec ?? 120;
@@ -571,143 +562,6 @@
checkWinCondition();
};
const pickBody = (point) => {
const hits = Query.point(balls, point);
return hits[0];
};
const getDraggableBody = (point) => {
const draggables = [
...boundaries.filter((b) => b.plugin?.draggable),
...balls.filter((b) => b.plugin?.draggable),
];
const hits = Query.point(draggables, point);
return hits[0];
};
const startDrag = (body, point) => {
endDrag();
dragConstraint = Constraint.create({
pointA: point,
bodyB: body,
stiffness: 0.2,
damping: 0.3,
render: { visible: false },
});
World.add(world, dragConstraint);
};
const updateDrag = (point) => {
if (!dragConstraint) return;
dragConstraint.pointA = point;
};
const endDrag = () => {
if (dragConstraint) {
World.remove(world, dragConstraint);
dragConstraint = null;
}
};
const getPointerPosition = (evt) => {
const rect = render.canvas.getBoundingClientRect();
const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX;
const clientY = evt.touches ? evt.touches[0].clientY : evt.clientY;
return {
x: clientX - rect.left,
y: clientY - rect.top,
};
};
const handlePointerDown = (evt) => {
if (gameOver || isPaused || levelWon) return;
const point = getPointerPosition(evt);
const dragTarget = getDraggableBody(point);
if (dragTarget) {
startDrag(dragTarget, point);
return;
}
const body = pickBody(point);
if (!body) return;
if (!currentScene?.config?.relaxMode) {
// Only allow linking same colors unless relax mode explicitly opts out.
chain.color = body.plugin.color;
} else {
chain.color = body.plugin.color;
}
chain.active = true;
chain.bodies = [body];
chain.constraints = [];
chain.pointer = point;
setHighlight(body, true);
updateHud();
};
const handlePointerMove = (evt) => {
if (dragConstraint) {
updateDrag(getPointerPosition(evt));
return;
}
if (!chain.active) return;
if (gameOver || isPaused || levelWon) return;
const point = getPointerPosition(evt);
chain.pointer = point;
const body = pickBody(point);
if (!body) return;
const alreadyInChain = chain.bodies.includes(body);
if (alreadyInChain) {
const targetIndex = chain.bodies.indexOf(body);
if (chain.bodies.length > 1 && targetIndex === chain.bodies.length - 2) {
removeLastFromChain();
}
return;
}
if (!currentScene?.config?.relaxMode && body.plugin.color !== chain.color)
return;
const maxLinkDist = getMaxLinkDistance();
const dist = Vector.magnitude(
Vector.sub(chain.bodies[chain.bodies.length - 1].position, body.position),
);
if (dist > maxLinkDist) return;
addToChain(body);
};
const handlePointerUp = () => {
if (dragConstraint) {
endDrag();
return;
}
finishChain(chain.pointer);
};
render.canvas.addEventListener("mousedown", handlePointerDown);
render.canvas.addEventListener("mousemove", handlePointerMove);
window.addEventListener("mouseup", handlePointerUp);
render.canvas.addEventListener(
"touchstart",
(e) => {
e.preventDefault();
handlePointerDown(e);
},
{ passive: false },
);
render.canvas.addEventListener(
"touchmove",
(e) => {
e.preventDefault();
handlePointerMove(e);
},
{ passive: false },
);
render.canvas.addEventListener(
"touchend",
(e) => {
e.preventDefault();
handlePointerUp(e);
},
{ passive: false },
);
const updateHud = () => {
ui.updateHud({
spawnIntervalMs: config.spawnIntervalMs,
@@ -722,6 +576,25 @@
goals.maybeAnnounceGoalProgress(goal);
};
input = window.PhysilinksInput.create({
render,
world,
balls,
boundaries,
chain,
config,
getCurrentScene: () => currentScene,
isPaused: () => isPaused,
isLevelWon: () => levelWon,
isGameOver: () => gameOver,
getMaxLinkDistance,
setHighlight,
removeLastFromChain,
addToChain,
finishChain,
updateHud,
});
const buildLegend = () => {
ui.buildLegend(config.palette);
};
@@ -743,7 +616,7 @@
Body.setVelocity(ball, { x: 0, y: 0 });
});
resetChainVisuals();
endDrag();
input?.endDrag();
};
const handleResize = () => {