Files
kidgo/js/levels/needle.js
T
2026-10-21 13:54:25 +08:00

80 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 经典针刺阵型库(IWBT 风格):所有函数基于 Grid,坐标单位 = 格
import { Grid } from './kit.js';
// 上下对刺夹缝(横向穿越 1 格高的缝):缝在行 y。
// 自动清理两侧地毯刺作起跳/落点(否则有效跳距翻倍,无解)。
export function vgap(m, x, y) {
m.set(x, y - 1, 'v'); m.set(x, y + 1, '^');
m.set(x - 1, y + 1, '.'); m.set(x + 1, y + 1, '.');
m.set(x - 2, y + 1, '.'); m.set(x + 2, y + 1, '.');
}
// 左右对刺夹缝(竖向穿越 1 格宽的缝):缝在列 x,自动清理两侧落点。
export function hgap(m, x, y) {
m.set(x - 1, y, '>'); m.set(x + 1, y, '<');
m.set(x - 2, y, '.'); m.set(x + 2, y, '.');
}
// 钻石阵:中心空格 + 四向刺(经典 diamond jump)
// 注意:矩形判定盒下,完整钻石的横向穿越窗口只有几像素——
// 作为"跳穿门"请用 vgap/hgap;diamond 只用作翻越/绕行的障碍或装饰。
export function diamond(m, x, y) { vgap(m, x, y); hgap(m, x, y); }
// 一排刺,gaps 为留空偏移(相对 x0)
export function spikeRun(m, x0, y, n, ch = '^', gaps = []) {
for (let i = 0; i < n; i++) if (!gaps.includes(i)) m.set(x0 + i, y, ch);
}
// 悬浮块,顶面铺刺(不可站)/ 或纯块
export function block(m, x, y, w = 1, h = 1, ch = '#') { m.rect(x, y, w, h, ch); }
// 悬浮块 + 顶刺
export function spikedBlock(m, x, y, w = 1) { m.rect(x, y, w, 1, '#'); spikeRun(m, x, y - 1, w, '^'); }
// 天花板垂刺
export function ceil(m, x0, x1, y) { m.hline(y, x0, x1, 'v'); }
// 墙刺竖段
export function wall(m, x, y0, y1, ch) { m.vline(x, y0, y1, ch); }
// 角刺块:w×h 实心块,某个外角带 L 形双刺(经典 corner)
// dir: 'tl' 左上角 / 'tr' 右上 / 'bl' 左下 / 'br' 右下
export function corner(m, x, y, w, h, dir) {
m.rect(x, y, w, h, '#');
if (dir === 'tl') { m.set(x, y - 1, '^'); m.set(x - 1, y, '>'); }
if (dir === 'tr') { m.set(x + w - 1, y - 1, '^'); m.set(x + w, y, '<'); }
if (dir === 'bl') { m.set(x, y + h, 'v'); m.set(x - 1, y + h - 1, '>'); }
if (dir === 'br') { m.set(x + w - 1, y + h, 'v'); m.set(x + w, y + h - 1, '<'); }
}
// 之字踏石竖井(可解性已用 tools/solve.mjs 验证)
// xL..xR 为井内列;topRow = 上层地板行,botRow = 下层地板行(差须为偶数且 ≥4)
// floorSide: 'l' 上层地板在井的左侧(井在右侧)/ 'r' 反之
// 规则:
// 1) 踏石每级升 2 格(单跳 90px > 64px),左右交替,宽度 w 使两级有 2 格重叠
// —— overlap 让"上升后横移"只需 1 格左右,避免横向距离超过滞空距离。
// 2) 靠地板一侧的井壁从 topRow+1 起到 botRow-4,底部留 3 格入口。
// 3) 顶台位于 topRow 且与上层地板紧邻(同高相接),不存在"洞口被顶台封死"。
export function well(m, xL, xR, topRow, botRow, floorSide = 'l', opts = {}) {
const w = opts.w || 5;
let i = 0;
for (let r = topRow; r <= botRow - 2; r += 2, i++) {
const onLeft = (i % 2 === 0) === (floorSide === 'l');
m.rect(onLeft ? xL : xR - w + 1, r, w, 1, '#');
}
const floorX = floorSide === 'l' ? xL - 1 : xR + 1;
const backX = floorSide === 'l' ? xR + 1 : xL - 1;
m.vline(backX, topRow, botRow, '#');
m.vline(floorX, topRow + 1, botRow - 4, '#');
return m;
}
// 竖井:在 (x0..x1) 之间开竖直通道,交替放小平台,两侧可布墙刺
// plats: [{y, side:'l'|'r', w}],spikeEvery: 每隔几行在对面墙上放刺
export function shaft(m, x0, x1, yTop, yBot, plats, opts = {}) {
for (const p of plats) {
const px = p.side === 'l' ? x0 : x1 - p.w + 1;
m.rect(px, p.y, p.w, 1, '#');
if (p.spike) spikeRun(m, px, p.y - 1, p.w, '^', p.spikeGaps || []);
}
if (opts.wallSpikes) {
const { side = 'r', every = 3, from = yTop + 1, to = yBot - 1 } = opts.wallSpikes;
for (let y = from; y <= to; y += every) {
if (plats.some(p => Math.abs(p.y - y) <= 1)) continue;
m.set(side === 'r' ? x1 : x0, y, side === 'r' ? '<' : '>');
}
}
}
export { Grid };