Clamp square spawns for stack blocks
This commit is contained in:
117
src/main.js
117
src/main.js
@@ -322,23 +322,32 @@
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
const columnCount = currentScene?.config?.spawnColumns;
|
const columnCount = currentScene?.config?.spawnColumns;
|
||||||
|
const insets = getSpawnInsets();
|
||||||
|
const squareArea = getSquarePlayArea();
|
||||||
|
const playWidth = squareArea ? squareArea.size : width;
|
||||||
|
const playLeft = squareArea ? squareArea.left : 0;
|
||||||
|
const usableWidth = Math.max(0, playWidth - insets.left - insets.right);
|
||||||
|
const minX = playLeft + insets.left + config.ballRadius + 2;
|
||||||
|
const maxX = playLeft + playWidth - insets.right - config.ballRadius - 2;
|
||||||
let x =
|
let x =
|
||||||
centerSpawn?.x ??
|
centerSpawn?.x ??
|
||||||
Math.max(
|
Math.max(
|
||||||
config.ballRadius + 10,
|
minX,
|
||||||
Math.min(width - config.ballRadius - 10, Math.random() * width),
|
Math.min(maxX, playLeft + insets.left + Math.random() * usableWidth),
|
||||||
);
|
);
|
||||||
if (Number.isFinite(columnCount) && columnCount > 0) {
|
if (Number.isFinite(columnCount) && columnCount > 0) {
|
||||||
const columnWidth = width / columnCount;
|
const columnWidth = usableWidth / columnCount;
|
||||||
const colIndex = Math.floor(Math.random() * columnCount);
|
const colIndex = Math.floor(Math.random() * columnCount);
|
||||||
x = columnWidth * (colIndex + 0.5);
|
x = playLeft + insets.left + columnWidth * (colIndex + 0.5);
|
||||||
|
x = Math.max(minX, Math.min(maxX, x));
|
||||||
}
|
}
|
||||||
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
|
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
|
||||||
|
const defaultTop = squareArea
|
||||||
|
? squareArea.top - config.ballRadius * 2
|
||||||
|
: -config.ballRadius * 2;
|
||||||
const y =
|
const y =
|
||||||
centerSpawn?.y ??
|
centerSpawn?.y ??
|
||||||
(spawnFromBottom
|
(spawnFromBottom ? height + config.ballRadius * 2 : defaultTop);
|
||||||
? height + config.ballRadius * 2
|
|
||||||
: -config.ballRadius * 2);
|
|
||||||
const batchMin = currentScene?.config?.spawnBatchMin ?? 1;
|
const batchMin = currentScene?.config?.spawnBatchMin ?? 1;
|
||||||
const batchMax = currentScene?.config?.spawnBatchMax ?? 1;
|
const batchMax = currentScene?.config?.spawnBatchMax ?? 1;
|
||||||
const batchCount =
|
const batchCount =
|
||||||
@@ -362,6 +371,7 @@
|
|||||||
(spawnFromBottom
|
(spawnFromBottom
|
||||||
? -config.ballRadius * 0.5
|
? -config.ballRadius * 0.5
|
||||||
: config.ballRadius * 0.5);
|
: config.ballRadius * 0.5);
|
||||||
|
if (currentScene?.config?.requireClearSpawn) {
|
||||||
const halfSize = config.ballRadius * 1.05;
|
const halfSize = config.ballRadius * 1.05;
|
||||||
const region = {
|
const region = {
|
||||||
min: { x: targetX - halfSize, y: targetY - halfSize },
|
min: { x: targetX - halfSize, y: targetY - halfSize },
|
||||||
@@ -372,6 +382,7 @@
|
|||||||
triggerGameOver();
|
triggerGameOver();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const blob = createBallBodies(targetX, targetY, color);
|
const blob = createBallBodies(targetX, targetY, color);
|
||||||
if (blob.constraints.length > 0 && blob.blobId) {
|
if (blob.constraints.length > 0 && blob.blobId) {
|
||||||
blobConstraints.set(blob.blobId, blob.constraints);
|
blobConstraints.set(blob.blobId, blob.constraints);
|
||||||
@@ -418,18 +429,58 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSquarePlayArea = () => {
|
||||||
|
if (!currentScene?.config?.squarePlayArea) return null;
|
||||||
|
const size = Math.min(width, height);
|
||||||
|
const left = (width - size) / 2;
|
||||||
|
const top = (height - size) / 2;
|
||||||
|
return { size, left, top };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSpawnInsets = () => {
|
||||||
|
let left = 0;
|
||||||
|
let right = 0;
|
||||||
|
const insetVal = currentScene?.config?.spawnInset;
|
||||||
|
if (Number.isFinite(insetVal)) {
|
||||||
|
left = insetVal;
|
||||||
|
right = insetVal;
|
||||||
|
}
|
||||||
|
if (typeof currentScene?.config?.spawnInsets === "function") {
|
||||||
|
try {
|
||||||
|
const res = currentScene.config.spawnInsets({ width, height });
|
||||||
|
if (res && Number.isFinite(res.left)) left = res.left;
|
||||||
|
if (res && Number.isFinite(res.right)) right = res.right;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("spawnInsets function failed", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { left, right };
|
||||||
|
};
|
||||||
|
|
||||||
const spawnInitialColumns = () => {
|
const spawnInitialColumns = () => {
|
||||||
const rows = currentScene?.config?.initialRows;
|
const rows = currentScene?.config?.initialRows;
|
||||||
const columns = currentScene?.config?.spawnColumns;
|
const columns = currentScene?.config?.spawnColumns;
|
||||||
if (!Number.isFinite(rows) || rows <= 0) return false;
|
if (!Number.isFinite(rows) || rows <= 0) return false;
|
||||||
if (!Number.isFinite(columns) || columns <= 0) return false;
|
if (!Number.isFinite(columns) || columns <= 0) return false;
|
||||||
const columnWidth = width / columns;
|
const insets = getSpawnInsets();
|
||||||
|
const squareArea = getSquarePlayArea();
|
||||||
|
const usableWidth = Math.max(
|
||||||
|
0,
|
||||||
|
(squareArea ? squareArea.size : width) - insets.left - insets.right,
|
||||||
|
);
|
||||||
|
const columnWidth = usableWidth / columns;
|
||||||
const side = config.ballRadius * 2;
|
const side = config.ballRadius * 2;
|
||||||
const rowGap = side * 1.05;
|
const rowGap = side * (currentScene?.config?.rowGapMultiplier ?? 1.05);
|
||||||
|
const startY = squareArea
|
||||||
|
? squareArea.top + config.ballRadius
|
||||||
|
: config.ballRadius * 1.5;
|
||||||
for (let r = 0; r < rows; r += 1) {
|
for (let r = 0; r < rows; r += 1) {
|
||||||
const y = config.ballRadius * 1.5 + r * rowGap;
|
const y = startY + r * rowGap;
|
||||||
for (let c = 0; c < columns; c += 1) {
|
for (let c = 0; c < columns; c += 1) {
|
||||||
const x = columnWidth * (c + 0.5);
|
const x =
|
||||||
|
(squareArea ? squareArea.left : 0) +
|
||||||
|
insets.left +
|
||||||
|
columnWidth * (c + 0.5);
|
||||||
const color =
|
const color =
|
||||||
config.palette[Math.floor(Math.random() * config.palette.length)];
|
config.palette[Math.floor(Math.random() * config.palette.length)];
|
||||||
const blob = createBallBodies(x, y, color);
|
const blob = createBallBodies(x, y, color);
|
||||||
@@ -969,6 +1020,21 @@
|
|||||||
const scaled = cellSize * scale;
|
const scaled = cellSize * scale;
|
||||||
return Math.round(Math.max(10, Math.min(60, scaled)));
|
return Math.round(Math.max(10, Math.min(60, scaled)));
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
currentScene?.config?.sizeFromColumns &&
|
||||||
|
Number.isFinite(currentScene?.config?.spawnColumns) &&
|
||||||
|
currentScene.config.spawnColumns > 0
|
||||||
|
) {
|
||||||
|
const insets = getSpawnInsets();
|
||||||
|
const squareArea = getSquarePlayArea();
|
||||||
|
const usableWidth = Math.max(
|
||||||
|
0,
|
||||||
|
(squareArea ? squareArea.size : width) - insets.left - insets.right,
|
||||||
|
);
|
||||||
|
const colWidth = usableWidth / currentScene.config.spawnColumns;
|
||||||
|
// Fit 10 per side; halve to get radius. Cap to a reasonable range.
|
||||||
|
return Math.round(Math.max(8, Math.min(60, colWidth / 2)));
|
||||||
|
}
|
||||||
const baseRadius =
|
const baseRadius =
|
||||||
(currentScene && currentScene.config && currentScene.config.ballRadius) ||
|
(currentScene && currentScene.config && currentScene.config.ballRadius) ||
|
||||||
config.ballRadius ||
|
config.ballRadius ||
|
||||||
@@ -988,7 +1054,15 @@
|
|||||||
const scale = nextRadius / prevRadius;
|
const scale = nextRadius / prevRadius;
|
||||||
balls.forEach((ball) => {
|
balls.forEach((ball) => {
|
||||||
Body.scale(ball, scale, scale);
|
Body.scale(ball, scale, scale);
|
||||||
|
if (ball.plugin?.shape === "rect") return;
|
||||||
|
if (ball.plugin?.shape === "jagged") {
|
||||||
|
// Recompute center to keep shape consistent; vertices are scaled by Body.scale
|
||||||
|
Body.setPosition(ball, ball.position);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof ball.circleRadius === "number") {
|
||||||
ball.circleRadius = nextRadius;
|
ball.circleRadius = nextRadius;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
config.ballRadius = nextRadius;
|
config.ballRadius = nextRadius;
|
||||||
@@ -1049,7 +1123,12 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const body = Bodies.fromVertices(x, y, [points], commonOpts, true);
|
const body = Bodies.fromVertices(x, y, [points], commonOpts, true);
|
||||||
body.plugin = { color, hasEntered: false, entryCheckId: null };
|
body.plugin = {
|
||||||
|
color,
|
||||||
|
hasEntered: false,
|
||||||
|
entryCheckId: null,
|
||||||
|
shape: "jagged",
|
||||||
|
};
|
||||||
return { bodies: [body], constraints: [], blobId: null };
|
return { bodies: [body], constraints: [], blobId: null };
|
||||||
}
|
}
|
||||||
if (currentScene?.config?.ballShape === "rect") {
|
if (currentScene?.config?.ballShape === "rect") {
|
||||||
@@ -1058,11 +1137,21 @@
|
|||||||
...commonOpts,
|
...commonOpts,
|
||||||
chamfer: 4,
|
chamfer: 4,
|
||||||
});
|
});
|
||||||
body.plugin = { color, hasEntered: false, entryCheckId: null };
|
body.plugin = {
|
||||||
|
color,
|
||||||
|
hasEntered: false,
|
||||||
|
entryCheckId: null,
|
||||||
|
shape: "rect",
|
||||||
|
};
|
||||||
return { bodies: [body], constraints: [], blobId: null };
|
return { bodies: [body], constraints: [], blobId: null };
|
||||||
}
|
}
|
||||||
const body = Bodies.circle(x, y, config.ballRadius, commonOpts);
|
const body = Bodies.circle(x, y, config.ballRadius, commonOpts);
|
||||||
body.plugin = { color, hasEntered: false, entryCheckId: null };
|
body.plugin = {
|
||||||
|
color,
|
||||||
|
hasEntered: false,
|
||||||
|
entryCheckId: null,
|
||||||
|
shape: "circle",
|
||||||
|
};
|
||||||
return { bodies: [body], constraints: [], blobId: null };
|
return { bodies: [body], constraints: [], blobId: null };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
id: "stack-blocks",
|
id: "stack-blocks",
|
||||||
name: "Stack Blocks",
|
name: "Stack Blocks",
|
||||||
config: {
|
config: {
|
||||||
gravity: 1.05,
|
gravity: 1.85,
|
||||||
spawnIntervalMs: 900,
|
spawnIntervalMs: 900,
|
||||||
autoSpawn: true,
|
autoSpawn: true,
|
||||||
minChain: 3,
|
minChain: 3,
|
||||||
@@ -15,7 +15,15 @@
|
|||||||
ballRadius: 18,
|
ballRadius: 18,
|
||||||
ballShape: "rect",
|
ballShape: "rect",
|
||||||
spawnColumns: 10,
|
spawnColumns: 10,
|
||||||
|
sizeFromColumns: true,
|
||||||
initialRows: 3,
|
initialRows: 3,
|
||||||
|
requireClearSpawn: true,
|
||||||
|
squarePlayArea: true,
|
||||||
|
rowGapMultiplier: 1,
|
||||||
|
spawnInsets: ({ width }) => {
|
||||||
|
const wallThickness = Math.max(20, width * 0.02);
|
||||||
|
return { left: 0, right: 0 };
|
||||||
|
},
|
||||||
winCondition: {
|
winCondition: {
|
||||||
type: "clearCount",
|
type: "clearCount",
|
||||||
target: 100,
|
target: 100,
|
||||||
@@ -27,31 +35,39 @@
|
|||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
rope: true,
|
rope: true,
|
||||||
renderType: "line",
|
renderType: "line",
|
||||||
maxLengthMultiplier: 3.1,
|
maxLengthMultiplier: 2.5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createBodies: (w, h) => {
|
createBodies: (w, h) => {
|
||||||
|
const squareSize = Math.min(w, h);
|
||||||
|
const left = (w - squareSize) / 2;
|
||||||
|
const top = (h - squareSize) / 2;
|
||||||
const wallThickness = Math.max(20, w * 0.02);
|
const wallThickness = Math.max(20, w * 0.02);
|
||||||
const floorHeight = Math.max(30, h * 0.06);
|
const floorHeight = Math.max(30, squareSize * 0.06);
|
||||||
|
const wallRender = {
|
||||||
|
fillStyle: "#1e293b",
|
||||||
|
strokeStyle: "#94a3b8",
|
||||||
|
lineWidth: 2,
|
||||||
|
};
|
||||||
return [
|
return [
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(
|
||||||
-wallThickness / 2,
|
left - wallThickness / 2,
|
||||||
h / 2,
|
h / 2,
|
||||||
wallThickness,
|
wallThickness,
|
||||||
h * 2,
|
h + wallThickness * 2,
|
||||||
{
|
{
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
render: { fillStyle: "#0b1222", strokeStyle: "#0b1222" },
|
render: wallRender,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(
|
||||||
w + wallThickness / 2,
|
left + squareSize + wallThickness / 2,
|
||||||
h / 2,
|
h / 2,
|
||||||
wallThickness,
|
wallThickness,
|
||||||
h * 2,
|
h + wallThickness * 2,
|
||||||
{
|
{
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
render: { fillStyle: "#0b1222", strokeStyle: "#0b1222" },
|
render: wallRender,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Bodies.rectangle(
|
Bodies.rectangle(
|
||||||
@@ -62,7 +78,7 @@
|
|||||||
{
|
{
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
restitution: 0.2,
|
restitution: 0.2,
|
||||||
render: { fillStyle: "#0b1222", strokeStyle: "#0b1222" },
|
render: wallRender,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user