80 lines
4.0 KiB
JavaScript
80 lines
4.0 KiB
JavaScript
// 经典针刺阵型库(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 };
|