|
|
@@ -70,7 +70,7 @@ const computePosition = (
|
|
|
container: { width: number; height: number },
|
|
|
stoneSize: StoneSize
|
|
|
): StonePosition => {
|
|
|
- const maxAttempts = 12;
|
|
|
+ const maxAttempts = 24;
|
|
|
const width = container.width || FALLBACK_SIZE.width;
|
|
|
const height = container.height || FALLBACK_SIZE.height;
|
|
|
const halfWidth = stoneSize.width / 2;
|
|
|
@@ -81,6 +81,10 @@ const computePosition = (
|
|
|
const verticalMax = Math.max(verticalMin, Math.min(height * 0.5, height - halfHeight));
|
|
|
let candidate: StonePosition = { xRatio: 0.5, yRatio: 0.2 };
|
|
|
|
|
|
+ const hasOverlap = (rect: StoneRect) =>
|
|
|
+ existing.some((stone) => rectanglesOverlap(rect, createRectFromStone(stone, { width, height })));
|
|
|
+
|
|
|
+ // 1) 随机采样若干次,先尝试随机避让
|
|
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
|
const usableWidth = Math.max(0, horizontalMax - horizontalMin);
|
|
|
const usableHeight = Math.max(0, verticalMax - verticalMin);
|
|
|
@@ -95,20 +99,45 @@ const computePosition = (
|
|
|
bottom: centerY + halfHeight,
|
|
|
};
|
|
|
|
|
|
- const overlap = existing.some((stone) =>
|
|
|
- rectanglesOverlap(candidateRect, createRectFromStone(stone, { width, height }))
|
|
|
- );
|
|
|
-
|
|
|
candidate = {
|
|
|
xRatio: width ? centerX / width : 0.5,
|
|
|
yRatio: height ? centerY / height : 0.2,
|
|
|
};
|
|
|
|
|
|
- if (!overlap) {
|
|
|
+ if (!hasOverlap(candidateRect)) {
|
|
|
return candidate;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 2) 如果随机失败,使用网格扫描尝试找到第一个不重叠的点
|
|
|
+ const usableWidth = Math.max(0, horizontalMax - horizontalMin);
|
|
|
+ const usableHeight = Math.max(0, verticalMax - verticalMin);
|
|
|
+ const cols = Math.max(2, Math.floor(usableWidth / Math.max(halfWidth * 2, 1)));
|
|
|
+ const rows = Math.max(2, Math.floor(usableHeight / Math.max(halfHeight * 2, 1)));
|
|
|
+ const stepX = cols > 1 ? usableWidth / (cols - 1) : usableWidth;
|
|
|
+ const stepY = rows > 1 ? usableHeight / (rows - 1) : usableHeight;
|
|
|
+
|
|
|
+ for (let row = 0; row < rows; row++) {
|
|
|
+ for (let col = 0; col < cols; col++) {
|
|
|
+ const centerX = horizontalMin + col * stepX;
|
|
|
+ const centerY = verticalMin + row * stepY;
|
|
|
+ const candidateRect: StoneRect = {
|
|
|
+ left: centerX - halfWidth,
|
|
|
+ right: centerX + halfWidth,
|
|
|
+ top: centerY - halfHeight,
|
|
|
+ bottom: centerY + halfHeight,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (!hasOverlap(candidateRect)) {
|
|
|
+ return {
|
|
|
+ xRatio: width ? centerX / width : 0.5,
|
|
|
+ yRatio: height ? centerY / height : 0.2,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3) 实在找不到,就使用最后一次随机的候选(可能存在轻微重叠,但概率更低)
|
|
|
return candidate;
|
|
|
};
|
|
|
|
|
|
@@ -257,7 +286,9 @@ export const useEnergyStones = (
|
|
|
size: stoneSize,
|
|
|
};
|
|
|
|
|
|
- setStones((prev) => [...prev, stone]);
|
|
|
+ const nextStones = [...stonesRef.current, stone];
|
|
|
+ stonesRef.current = nextStones;
|
|
|
+ setStones(nextStones);
|
|
|
scheduleLifecycle(stone);
|
|
|
},
|
|
|
[
|