Shared drag placement; drags snap one block to join a diagonal (t67 phases 3-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,24 +7,20 @@ function slopeBeltConnections(bx, by, code, planned = null) {
|
||||
const join = endJoin(bx, by, end, planned);
|
||||
if (join < 0) continue;
|
||||
const nb = join >> 1, next = planned && planned.get(nb);
|
||||
const pcode = next ? next[0] : blockP[nb];
|
||||
const slope = isSlopeBelt(pcode) && ((nb / BW) | 0) - by === beltGrade(code) * (nb % BW - bx);
|
||||
const slope = isSlopeBelt(next ? next[0] : blockP[nb]) &&
|
||||
((nb / BW) | 0) - by === beltGrade(code) * (nb % BW - bx);
|
||||
// A same-row feed into a low end drops onto its face: drawn as two capped ends, still joined.
|
||||
if (!slope && beltDrop(nb, by, beltGrade(code) * (end ? 1 : -1) > 0, pcode, join & 1)) continue;
|
||||
if (!slope && joinIsDrop(bx, by, end, join, planned)) continue;
|
||||
joins |= (slope ? 4 : 1) << end;
|
||||
}
|
||||
return joins;
|
||||
}
|
||||
// Does a same-row join between a piece in row `by` and block nb meet a slope's low end?
|
||||
function beltDrop(nb, by, ownLow, pcode, pend) {
|
||||
return ((nb / BW) | 0) === by && (ownLow || beltGrade(pcode) * (pend ? 1 : -1) > 0);
|
||||
}
|
||||
function flatBeltCaps(b, planned = null) {
|
||||
const bx = b % BW, by = (b / BW) | 0;
|
||||
let caps = 0;
|
||||
for (let end = 0; end < 2; end++) {
|
||||
const join = endJoin(bx, by, end, planned), nb = join >> 1, next = planned && planned.get(nb);
|
||||
if (join < 0 || beltDrop(nb, by, false, next ? next[0] : blockP[nb], join & 1)) caps |= 1 << end;
|
||||
const join = endJoin(bx, by, end, planned);
|
||||
if (join < 0 || joinIsDrop(bx, by, end, join, planned)) caps |= 1 << end;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
@@ -71,3 +71,35 @@ function endJoin(bx, by, endIndex, planned = null) {
|
||||
function pieceJoins(bx, by, planned = null) {
|
||||
return (endJoin(bx, by, 0, planned) >= 0 ? 1 : 0) | (endJoin(bx, by, 1, planned) >= 0 ? 2 : 0);
|
||||
}
|
||||
function endAt(b, end, planned = null) {
|
||||
const p = planned && planned.get(b);
|
||||
return pieceEnds(p ? p[0] : blockP[b], p ? p[1] : blockV[b])[end];
|
||||
}
|
||||
// A same-row join into a sloped belt's low end: the flat surface is level with the top of the
|
||||
// slope's first block, so grains drop about 3 grains onto its face. Joined, but not seamless.
|
||||
function joinIsDrop(bx, by, end, join, planned = null) {
|
||||
if (join < 0 || (((join >> 1) / BW) | 0) !== by) return false;
|
||||
const low = e => !!e && e.kind === 'belt-sloped' && e.dy > 0;
|
||||
return low(endAt(by * BW + bx, end, planned)) || low(endAt(join >> 1, join & 1, planned));
|
||||
}
|
||||
// Rows to move a line so its first block joins: 0, -1 or +1. A line that already joins stays
|
||||
// put; it moves only when one block up or down gives a seamless join involving a diagonal piece
|
||||
// and every block there is free, so straight lines beside straight lines never jump.
|
||||
function joinSnap(code, v, cells) {
|
||||
if (!cells.length) return 0;
|
||||
const joins = dy => {
|
||||
const plan = new Map(cells.map(([x, y]) => [(y + dy) * BW + x, [code, v]]));
|
||||
const x = cells[0][0], y = cells[0][1] + dy, own = pieceEnds(code, v);
|
||||
for (let end = 0; end < own.length; end++) {
|
||||
const j = endJoin(x, y, end, plan);
|
||||
if (j < 0 || plan.has(j >> 1) || joinIsDrop(x, y, end, j, plan)) continue;
|
||||
return own[end].diagonal || endAt(j >> 1, j & 1, plan).diagonal ? 2 : 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
if (!pieceEnds(code, v).length || joins(0)) return 0;
|
||||
for (const dy of [-1, 1]) {
|
||||
if (cells.every(([x, y]) => canPlaceBlock(x, y + dy, code) === '') && joins(dy) === 2) return dy;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+3
-5
@@ -166,11 +166,9 @@ function primaryRelease(shift) {
|
||||
return;
|
||||
}
|
||||
if (t === 'build' && GAME.drag) {
|
||||
const P = piece(), b = aimBlock(), code = pieceCode(P, b);
|
||||
const list = shift && !lineOnly(code) ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW);
|
||||
const dd = dragDirFor(P, GAME.drag, b);
|
||||
if (dd >= 0) GAME.pieceDir[P.id] = dd;
|
||||
const v = pieceV(P), ex = pieceExtra(P);
|
||||
const P = piece(), plan = dragPlan(P, GAME.drag, aimBlock(), shift), { code, v, cells: list } = plan;
|
||||
if (plan.dd >= 0) GAME.pieceDir[P.id] = plan.dd;
|
||||
const ex = pieceExtra(P);
|
||||
// blocks past the bot's reach become ghosts it builds when it gets close, so a line can be any length
|
||||
const near = list.filter(([bx, by]) => blockInReach(bx, by)), far = list.filter(([bx, by]) => !blockInReach(bx, by));
|
||||
let ghosts = 0;
|
||||
|
||||
+4
-6
@@ -98,19 +98,17 @@ function drawPreviews() {
|
||||
if (reachOk && (blowing || GAME.lmb)) drawAirStreaks(ax, ay, p, r, blowing);
|
||||
}
|
||||
} else if (t === 'build') {
|
||||
const P = piece(), b = aimBlock(), code = pieceCode(P, b);
|
||||
const P = piece(), b = aimBlock(), plan = P.kind === 'block' ? dragPlan(P, GAME.drag, b, modShift()) : null;
|
||||
const code = plan ? plan.code : pieceCode(P, b);
|
||||
if (GAME.previewCode !== code) { GAME.previewCode = code; hudDirty = true; }
|
||||
drawGrid(b.bx, b.by, 9);
|
||||
let cells = [], col = '120,200,190';
|
||||
if (P.kind === 'block') {
|
||||
const v = pieceV(P);
|
||||
const { v, dd, dir: dIdx, planned } = plan;
|
||||
col = previewColor(code, v);
|
||||
cells = GAME.drag ? (modShift() && !lineOnly(code) ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW)) : [[b.bx, b.by]];
|
||||
const dd = GAME.drag ? dragDirFor(P, GAME.drag, b) : -1;
|
||||
cells = plan.cells;
|
||||
if (dd !== GAME.dragDir) { GAME.dragDir = dd; hudDirty = true; }
|
||||
const dIdx = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
||||
const afford = canAfford(pieceCostNow(P), cells.length);
|
||||
const planned = code === BP_SCREW || isSlopeBelt(code) ? new Map(cells.map(([x, y]) => [y * BW + x, [code, isFilter(code) ? v : dIdx]])) : null;
|
||||
for (const [bx, by] of cells) {
|
||||
const err = canPlaceBlock(bx, by, code), rb = by * BW + bx;
|
||||
const swap = GAME.replace && !!blockP[rb] && /already built/.test(err) && !blockIs(rb, code, v, pieceExtra(P));
|
||||
|
||||
@@ -87,6 +87,17 @@ function pieceV(P) {
|
||||
if (P.filter) return filterVariant(pieceCode(P), (GAME.pieceDir[P.id] || 0) << 7);
|
||||
return GAME.pieceDir[P.id] || 0;
|
||||
}
|
||||
// One drag for the build tool and its preview: code, direction, variant, blocks, and the planned
|
||||
// blocks the join rule sees. A line that joins one block up or down snaps there (joinSnap).
|
||||
function dragPlan(P, a, b, shift) {
|
||||
const code = pieceCode(P, b), dd = a ? dragDirFor(P, a, b) : -1, dir = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
||||
const v = isWall(P.code) ? GAME.wallMat : P.filter ? filterVariant(code, dir << 7) : dir;
|
||||
let cells = !a ? [[b.bx, b.by]] : shift && !lineOnly(code) ? blockRect(a.bx, a.by, b.bx, b.by)
|
||||
: blockLine(a.bx, a.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW);
|
||||
const snap = lineOnly(code) ? joinSnap(code, v, cells) : 0;
|
||||
if (snap) cells = cells.map(([x, y]) => [x, y + snap]);
|
||||
return { code, dd, dir, v, cells, snap, planned: new Map(cells.map(([x, y]) => [y * BW + x, [code, v]])) };
|
||||
}
|
||||
function pieceExtra(P) { return P.filter ? { m: GAME.filterCfg.m.slice(), inv: GAME.filterCfg.inv } : undefined; }
|
||||
function filterBlockAt(x, y) { if (x < 0 || y < 0 || x >= W || y >= H) return -1; const b = ((y / BS) | 0) * BW + ((x / BS) | 0); return isFilter(blockP[b]) ? b : -1; }
|
||||
function chipCost(cost) { const ok = canAfford(cost, 1); return `<span class="chip ${ok ? 'ok' : 'bad'}">${esc(costText(cost, 1))}</span>`; }
|
||||
|
||||
Reference in New Issue
Block a user