Clamp square spawns for stack blocks

This commit is contained in:
Daddy32
2025-12-14 22:06:52 +01:00
parent 9bed2f6125
commit 95420f1acf
2 changed files with 139 additions and 34 deletions

View File

@@ -322,23 +322,32 @@
}
: null;
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 =
centerSpawn?.x ??
Math.max(
config.ballRadius + 10,
Math.min(width - config.ballRadius - 10, Math.random() * width),
minX,
Math.min(maxX, playLeft + insets.left + Math.random() * usableWidth),
);
if (Number.isFinite(columnCount) && columnCount > 0) {
const columnWidth = width / columnCount;
const columnWidth = usableWidth / 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 defaultTop = squareArea
? squareArea.top - config.ballRadius * 2
: -config.ballRadius * 2;
const y =
centerSpawn?.y ??
(spawnFromBottom
? height + config.ballRadius * 2
: -config.ballRadius * 2);
(spawnFromBottom ? height + config.ballRadius * 2 : defaultTop);
const batchMin = currentScene?.config?.spawnBatchMin ?? 1;
const batchMax = currentScene?.config?.spawnBatchMax ?? 1;
const batchCount =
@@ -362,15 +371,17 @@
(spawnFromBottom
? -config.ballRadius * 0.5
: config.ballRadius * 0.5);
const halfSize = config.ballRadius * 1.05;
const region = {
min: { x: targetX - halfSize, y: targetY - halfSize },
max: { x: targetX + halfSize, y: targetY + halfSize },
};
const hits = Query.region(balls, region);
if (hits && hits.length > 0) {
triggerGameOver();
return;
if (currentScene?.config?.requireClearSpawn) {
const halfSize = config.ballRadius * 1.05;
const region = {
min: { x: targetX - halfSize, y: targetY - halfSize },
max: { x: targetX + halfSize, y: targetY + halfSize },
};
const hits = Query.region(balls, region);
if (hits && hits.length > 0) {
triggerGameOver();
return;
}
}
const blob = createBallBodies(targetX, targetY, color);
if (blob.constraints.length > 0 && blob.blobId) {
@@ -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 rows = currentScene?.config?.initialRows;
const columns = currentScene?.config?.spawnColumns;
if (!Number.isFinite(rows) || rows <= 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 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) {
const y = config.ballRadius * 1.5 + r * rowGap;
const y = startY + r * rowGap;
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 =
config.palette[Math.floor(Math.random() * config.palette.length)];
const blob = createBallBodies(x, y, color);
@@ -969,6 +1020,21 @@
const scaled = cellSize * scale;
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 =
(currentScene && currentScene.config && currentScene.config.ballRadius) ||
config.ballRadius ||
@@ -988,7 +1054,15 @@
const scale = nextRadius / prevRadius;
balls.forEach((ball) => {
Body.scale(ball, scale, scale);
ball.circleRadius = nextRadius;
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;
}
});
}
config.ballRadius = nextRadius;
@@ -1049,7 +1123,12 @@
});
}
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 };
}
if (currentScene?.config?.ballShape === "rect") {
@@ -1058,11 +1137,21 @@
...commonOpts,
chamfer: 4,
});
body.plugin = { color, hasEntered: false, entryCheckId: null };
body.plugin = {
color,
hasEntered: false,
entryCheckId: null,
shape: "rect",
};
return { bodies: [body], constraints: [], blobId: null };
}
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 };
};