111 lines
5.2 KiB
JavaScript
111 lines
5.2 KiB
JavaScript
// 诊断:打印可达站立点的最大 x(按行),找出断连位置
|
|
const handlers = {};
|
|
const addL = (t, fn) => { (handlers[t] = handlers[t] || []).push(fn); };
|
|
globalThis.addEventListener = addL;
|
|
globalThis.window = { addEventListener: addL };
|
|
|
|
const { LEVELS } = await import('../js/levels/index.js');
|
|
const { Level } = await import('../js/level.js');
|
|
|
|
const TILE = 32, PW = 14, PH = 18;
|
|
const GRAV = 0.4, MAXFALL = 9, JUMP = 8.5, DJUMP = 7.6, HSPD = 3;
|
|
|
|
function solidAt(L, br, x, y, w, h, down, prevBottom) {
|
|
const x0 = Math.floor(x / TILE), x1 = Math.floor((x + w - 0.001) / TILE);
|
|
const y0 = Math.floor(y / TILE), y1 = Math.floor((y + h - 0.001) / TILE);
|
|
for (let ty = y0; ty <= y1; ty++) for (let tx = x0; tx <= x1; tx++) {
|
|
const t = L.tileAt(tx, ty);
|
|
if (t === 7) { if (down && prevBottom !== undefined && prevBottom <= ty * TILE + 6) return true; continue; }
|
|
if (L.isSolidTile(t, tx, ty)) return true;
|
|
}
|
|
for (const b of br) if (x < b.x + b.w && x + w > b.x && y < b.y + b.h && y + h > b.y) return true;
|
|
return false;
|
|
}
|
|
function canStand(L, br, fx, fy) {
|
|
if (fx < 0 || fx + PW > L.pxW) return false;
|
|
if (solidAt(L, br, fx, fy - PH, PW, PH, false)) return false;
|
|
if (!solidAt(L, br, fx, fy - PH + 1, PW, PH, true, fy - 1)) return false;
|
|
if (L.deadlyRect(fx, fy - PH, PW, PH)) return false;
|
|
return true;
|
|
}
|
|
function jump(L, br, fx, fy, vx0, djump, flip) {
|
|
let x = fx, y = fy - PH, vy = -JUMP, vx = vx0, t = 0, used = false;
|
|
while (t++ < 240) {
|
|
if (djump && !used && t === djump) { vy = -DJUMP; used = true; }
|
|
if (flip && t === flip) vx = -vx;
|
|
vy = Math.min(vy + GRAV, MAXFALL);
|
|
if (vx !== 0) { const nx = x + vx; if (!solidAt(L, br, nx, y, PW, PH, false)) x = nx; else vx = 0; }
|
|
const prevBottom = y + PH;
|
|
let rem = Math.abs(vy), dy = Math.sign(vy), landed = false;
|
|
while (rem > 0.0001) {
|
|
const m = Math.min(1, rem); const ny = y + dy * m;
|
|
if (!solidAt(L, br, x, ny, PW, PH, dy > 0, prevBottom)) { y = ny; rem -= m; }
|
|
else { if (dy > 0) landed = true; vy = 0; break; }
|
|
}
|
|
if (L.deadlyRect(x, y, PW, PH)) return null;
|
|
if (y > L.pxH + 120 || y < -240) return null;
|
|
if (landed) return { fx: x, fy: y + PH };
|
|
}
|
|
return null;
|
|
}
|
|
function fall(L, br, fx, fy, dir) {
|
|
let x = fx + dir * 3, y = fy - PH, vy = 0, t = 0;
|
|
if (solidAt(L, br, x, y, PW, PH, false)) return null;
|
|
while (t++ < 400) {
|
|
vy = Math.min(vy + GRAV, MAXFALL);
|
|
let rem = Math.abs(vy), landed = false;
|
|
while (rem > 0.0001) {
|
|
const m = Math.min(1, rem); const ny = y + m;
|
|
if (!solidAt(L, br, x, ny, PW, PH, true, y + PH)) { y = ny; rem -= m; }
|
|
else { landed = true; break; }
|
|
}
|
|
if (L.deadlyRect(x, y, PW, PH)) return null;
|
|
if (y > L.pxH + 120) return null;
|
|
if (landed) return { fx: x, fy: y + PH };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const id = process.argv[2] || 'j6';
|
|
const d = LEVELS.find(l => l.id === id);
|
|
const L = new Level(d);
|
|
const br = [];
|
|
for (let y = 0; y < L.h; y++) for (let x = 0; x < L.w; x++) if (L.tiles[y][x] === 6) L.tiles[y][x] = 1;
|
|
for (const m of L.movers) {
|
|
const xs = m.path.map(p => p.x).concat([m.x]), ys = m.path.map(p => p.y).concat([m.y]);
|
|
const x0 = Math.min(...xs), x1 = Math.max(...xs) + m.w, y0 = Math.min(...ys), y1 = Math.max(...ys) + m.h;
|
|
br.push({ x: x0, y: y0, w: x1 - x0, h: y1 - y0 });
|
|
}
|
|
let sx = L.spawn.x, sy = L.spawn.y + PH, start = null;
|
|
for (let k = 0; k < 400; k++) { if (canStand(L, br, sx, sy + k)) { start = { fx: sx, fy: sy + k }; break; } if (sy + k > L.pxH) break; }
|
|
const key = s => `${Math.round(s.fx / 6)},${Math.round(s.fy / 6)}`;
|
|
const seen = new Set([key(start)]); const q = [start]; let n = 0;
|
|
const byRow = new Map();
|
|
const note = s => {
|
|
const r = Math.round(s.fy / TILE);
|
|
const cur = byRow.get(r) || { max: -1, min: 1e9 };
|
|
byRow.set(r, { max: Math.max(cur.max, s.fx), min: Math.min(cur.min, s.fx) });
|
|
};
|
|
note(start);
|
|
while (q.length && n < 40000) {
|
|
const s = q.shift(); n++;
|
|
for (const d2 of [-HSPD, HSPD]) {
|
|
const nx = s.fx + d2;
|
|
if (canStand(L, br, nx, s.fy)) { const ns = { fx: nx, fy: s.fy }; const k = key(ns); if (!seen.has(k)) { seen.add(k); q.push(ns); note(ns); } }
|
|
}
|
|
for (const d2 of [-1, 1]) { const r = fall(L, br, s.fx, s.fy, d2); if (r && canStand(L, br, r.fx, r.fy)) { const k = key(r); if (!seen.has(k)) { seen.add(k); q.push(r); note(r); } } }
|
|
for (const vx of [-HSPD, 0, HSPD]) for (const dj of [0, 6, 12, 18, 24, 30]) {
|
|
const r = jump(L, br, s.fx, s.fy, vx, dj, 0);
|
|
if (r && canStand(L, br, r.fx, r.fy)) { const k = key(r); if (!seen.has(k)) { seen.add(k); q.push(r); note(r); } }
|
|
if (vx !== 0) { const r2 = jump(L, br, s.fx, s.fy, vx, dj, 22); if (r2 && canStand(L, br, r2.fx, r2.fy)) { const k = key(r2); if (!seen.has(k)) { seen.add(k); q.push(r2); note(r2); } } }
|
|
}
|
|
}
|
|
console.log(`${id} ${d.name} ${L.w}x${L.h} 可达站立行数=${byRow.size} 状态=${seen.size}`);
|
|
[...byRow.keys()].sort((a, b) => b - a).forEach(r => {
|
|
const v = byRow.get(r);
|
|
console.log(` row ${String(r).padStart(2)} (y=${r * TILE}): x ${(v.min / TILE).toFixed(1)} .. ${(v.max / TILE).toFixed(1)} 格`);
|
|
});
|
|
// 打印目标
|
|
for (const e of L.entities) if (e.type === 'goal' || e.type === 'save')
|
|
console.log(` ${e.type}#${e.no ?? ''} at col ${e.tx}, row ${e.ty}`);
|