Files
kidgo/js/levels/jump.js
T

520 lines
25 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.
// 跳刺(Needle)10 关 —— IWBT 经典密集针刺风格
// 设计语法(已经 tools/solve.mjs 验证):
// 刺毯缺口每 3 格一个(跳 2 连刺 = 64px 单跳节奏);钻石中心 = 地板上方 2~3 格;
// 垂刺离最低落脚点 5 格(单跳安全、二段跳必死);2 格高矮洞可用撞头跳;
// 竖井台阶每级升 2~3 格横移 3 格内;钢丝柱间距 ≤4(单跳)/ ≤6(二段)。
import { Grid } from './kit.js';
import { vgap, hgap, diamond, spikeRun, block, corner, ceil, wall, well } from './needle.js';
// 刺毯:从 x0 起 n 格留落脚缺口。
// 缺口间距统一 3 格(跳 2 连刺 = 64px)。
// 2026-10-23:JUMP 7.5 + GRAV_FALL 0.55 下单跳水平实测 118.8px(tools/jumpdist.mjs),
// 但落地前一帧竖直速度已达 8.9px/帧,3 连刺(96px)的起拍窗被压到 0~1 帧(不可玩);
// 2 连刺留 55px 射程余量,落地窗恢复充裕。井口/左端右行出口处的 3 连刺须显式拆断(见各关 gaps 注释)
const carpet = (m, x0, y, n, gaps = []) => {
const g = [];
let i = 3;
while (i < n) { g.push(i); i += 3; }
return spikeRun(m, x0, y, n, '^', g.concat(gaps));
};
export const JUMP_LEVELS = [
// ============================================================ j1 刺巷
{
id: 'j1', name: 'SPIKE ALLEY', cn: '刺巷', type: 'jump', diff: 2,
hint: '针刺入门:刺毯单跳节奏、钻石缝、矮洞撞头跳、竖井、对刺门。',
width: 104,
grid: (() => {
const W = 104, H = 26, R = 22;
const m = new Grid(W, H);
m.border('#');
m.rect(1, R, W - 2, 1, '#'); // 主地板
// --- x1-9 起步 ---
m.set(3, R - 1, 'S');
m.set(8, R - 1, 'G');
// --- x10-26 刺毯(单跳节奏,垂刺压顶禁二段,两道上下夹缝)---
carpet(m, 10, R - 1, 17);
ceil(m, 10, 26, R - 6);
vgap(m, 15, R - 2); vgap(m, 21, R - 2);
// --- x28-43 钻石门三连(中心 R-2,单跳穿缝)---
carpet(m, 28, R - 1, 16, [0, 4, 8, 12, 15]); // 加密缺口便于站位
ceil(m, 28, 43, R - 6);
vgap(m, 30, R - 2); vgap(m, 34, R - 2); vgap(m, 38, R - 2);
m.set(28, R - 1, 'G');
// --- x44-56 矮洞(顶 row 18,3 格高,撞头跳 + 对刺门)---
m.rect(44, R - 4, 13, 1, '#');
carpet(m, 45, R - 1, 11);
hgap(m, 48, R - 1); hgap(m, 53, R - 1);
m.set(45, R - 1, 'G');
// --- x57-71 刺坑上的钢丝柱 ---
for (let x = 57; x <= 70; x++) { m.set(x, R, '.'); m.set(x, R + 1, '^'); }
const wires = [[58, 20], [61, 19], [64, 20], [67, 18], [70, 20]];
for (const [x, top] of wires) m.vline(x, top, R - 1, '#');
m.set(72, R - 1, 'G');
// --- x74-79 竖井上上层(井底留 3 格入口)---
m.vline(74, R - 10, R - 4, '#');
m.vline(79, R - 12, R - 1, '#');
block(m, 75, R - 3, 2); // 19
block(m, 77, R - 6, 2); // 16
block(m, 75, R - 9, 2); // 13
wall(m, 79, R - 5, R - 5, '<'); wall(m, 74, R - 7, R - 7, '>');
// --- 上层回廊(地板 row 12)x80-97 ---
m.rect(80, R - 10, 18, 1, '#');
carpet(m, 81, R - 11, 15, [0, 8]); // 8=x89:给对刺门留左侧起拍位(新物理下跳台落点偏右,须能走进 x90)
ceil(m, 81, 95, R - 16); // row 6 垂刺
hgap(m, 92, R - 11); // 对刺门
m.set(82, R - 11, 'G'); m.set(94, R - 11, 'G');
// --- x98-102 下落井 + 终点 ---
wall(m, 99, R - 7, R - 3, '>');
wall(m, 102, R - 5, R - 2, '<');
m.set(100, R - 1, 'X');
return m.out();
})(),
},
// ============================================================ j2 垂直地狱
{
id: 'j2', name: 'VERTICAL HELL', cn: '垂直地狱', type: 'jump', diff: 3,
hint: '52 格高塔:四段之字竖井,每层刺毯+上下夹缝,垂刺压顶、掉落刺追身。',
width: 68,
grid: (() => {
const W = 68, H = 52;
const m = new Grid(W, H);
m.border('#');
// ---- L0 地板 row 48(向右:刺毯 + 三道上下夹缝)----
m.rect(1, 48, 66, 1, '#');
m.set(3, 47, 'S');
carpet(m, 5, 47, 50, [1, 18]); // 1=x6 出生旁缺口;18=x23 放宽落点窗(3 连刺落点贴右腰会被 6px 状态桶卡死)
ceil(m, 5, 54, 43);
vgap(m, 16, 46); vgap(m, 30, 46); vgap(m, 46, 46);
m.set(11, 47, 'G'); m.set(39, 47, 'G');
// ---- 右井 x58..65:L0 → L1(井底 3 格敞口,之字每级升 2 格)----
well(m, 58, 65, 40, 48, 'l');
m.set(65, 45, '^'); m.set(58, 43, '^'); // 踏石外沿刺,逼迫精准落点
// ---- L1 地板 row 40(向左;垂刺避开左井 x2..9)----
m.rect(2, 40, 56, 1, '#');
carpet(m, 10, 39, 44);
ceil(m, 10, 54, 35);
vgap(m, 42, 38); vgap(m, 26, 38); vgap(m, 12, 38);
m.set(47, 39, 'G'); m.set(19, 39, 'G');
// ---- 左井 x2..8:L1 → L2 ----
well(m, 2, 8, 32, 40, 'r');
m.set(2, 37, '^'); m.set(8, 35, '^');
// ---- L2 地板 row 32(向右,井底延伸到右井下方)----
m.rect(9, 32, 57, 1, '#');
carpet(m, 11, 31, 46, [1, 11, 31]); // 1=x12 拆井口 3 连刺;11=x22/31=x42 是 hgap 夹缝通道列(必须为空,否则整门变 3 连刺、4 格跳超射程)
ceil(m, 11, 54, 27);
hgap(m, 22, 31); hgap(m, 42, 31);
vgap(m, 32, 30); vgap(m, 50, 30);
m.set(19, 31, 'G'); m.set(47, 31, 'G');
// ---- 右井 x58..65:L2 → L3 ----
well(m, 58, 65, 24, 32, 'l');
m.set(65, 29, '^'); m.set(58, 27, '^');
// ---- L3 地板 row 24(向左,掉落刺;垂刺避开左井 x2..9)----
m.rect(2, 24, 56, 1, '#');
carpet(m, 10, 23, 44, [0]); // 0=x10:拆掉左端 3 连刺(左行跳落点差 0.8px 蹭 x10 左坡;L1 同位置能过是因 vgap(12) 清空了 x10-14,L3 没有)
ceil(m, 10, 54, 19);
[14, 24, 34, 44].forEach(x => m.set(x, 18, 'D'));
vgap(m, 40, 22); vgap(m, 22, 22);
m.set(47, 23, 'G'); m.set(19, 23, 'G');
// ---- 左井 x2..8:L3 → L4 ----
well(m, 2, 8, 16, 24, 'r');
m.set(2, 21, '^'); m.set(8, 19, '^');
// ---- L4 地板 row 16(向右到终点)----
m.rect(9, 16, 57, 1, '#');
carpet(m, 11, 15, 52, [1, 41]); // 1=x12 拆井口 3 连刺;41=x52 是 hgap 夹缝通道列(必须为空)
ceil(m, 11, 60, 11);
hgap(m, 26, 15); vgap(m, 40, 14); hgap(m, 52, 15);
[18, 34, 56].forEach(x => m.set(x, 10, 'D'));
m.set(27, 15, 'G'); m.set(51, 15, 'G');
m.set(62, 15, 'X');
return m.out();
})(),
},
// ============================================================ j3 钢丝
{
id: 'j3', name: 'TIGHTROPE', cn: '钢丝', type: 'jump', diff: 3,
hint: '一格宽的柱子立在刺海上,高矮间距都不同,垂刺压着头顶。',
width: 132,
grid: (() => {
const W = 132, H = 24, R = 20;
const m = new Grid(W, H);
m.border('#');
m.rect(1, R, W - 2, 1, '#'); // 基座
spikeRun(m, 4, R - 1, W - 6, '^'); // 刺海
// 钢丝柱(顶可站 1 格),高矮错落、间距 3~5
const cols = [
[4, 18], [8, 17], [12, 19], [15, 17], [19, 18], [23, 16], [27, 19],
[30, 17], [34, 18], [38, 16], [42, 19], [46, 17], [50, 15], [54, 18],
[58, 17], [62, 19], [66, 16], [70, 18], [74, 17], [78, 19], [82, 15],
[86, 18], [90, 17], [94, 19], [98, 16], [102, 18], [106, 17], [110, 19],
[114, 16], [118, 18], [122, 17],
];
for (const [x, top] of cols) m.vline(x, top, R - 1, '#');
// 起点/终点平台
m.rect(1, R - 1, 3, 1, '#'); m.set(2, R - 2, 'S');
m.rect(126, R - 1, 4, 1, '#'); m.set(128, R - 2, 'X');
// 垂刺段(离最低柱顶 5 格:单跳安全,二段跳死)
ceil(m, 6, 28, 11); // 段内最低顶 16
ceil(m, 32, 56, 10); // 最低顶 15
ceil(m, 60, 88, 10);
ceil(m, 92, 120, 11);
// 存档(柱顶)
m.set(23, 15, 'G'); m.set(58, 16, 'G'); m.set(90, 16, 'G'); m.set(122, 16, 'G');
return m.out();
})(),
},
// ============================================================ j4 冰刺
{
id: 'j4', name: 'ICE NEEDLES', cn: '冰刺', type: 'jump', diff: 4,
hint: '冰面刹不住车:滑坑、冰上钻石、冰柱钢丝,最后还有一段冰顶矮洞。',
width: 132,
grid: (() => {
const W = 132, H = 26, R = 22;
const m = new Grid(W, H);
m.border('#');
m.rect(1, R, W - 2, 1, '%'); // 冰地板
m.set(3, R - 1, 'S');
// --- x6-30 冰面刺坑(3 格宽:新物理单跳 108.8px > 96px;旧 4~5 格坑跳不过)---
for (const [a, b] of [[7, 9], [13, 15], [19, 21], [25, 27]])
for (let x = a; x <= b; x++) { m.set(x, R, '.'); m.set(x, R + 1, '^'); }
ceil(m, 6, 24, R - 5); // 压顶垂刺:冰面上禁二段跳
m.set(12, R - 1, 'G');
// --- x32-52 冰台阶 + 角刺 + 钻石 ---
block(m, 33, R - 2, 2, '%');
block(m, 38, R - 2, 2);
block(m, 43, R - 4, 2, '%');
ceil(m, 31, 52, R - 5);
vgap(m, 48, R - 2);
m.set(41, R - 1, 'G');
// --- x54-78 冰柱钢丝 ---
spikeRun(m, 54, R - 1, 25, '^');
const ice = [[56, 19], [60, 18], [64, 20], [68, 17], [72, 19], [76, 18]];
for (const [x, top] of ice) m.vline(x, top, R - 1, '%');
ceil(m, 55, 77, 12); // 最低柱顶 17 → 12
m.set(64, 19, 'G');
// --- x80-100 冰顶矮洞(2 格高,撞头跳越刺)---
m.rect(80, R - 3, 21, 1, '%'); // 冰顶 row 19
spikeRun(m, 83, R - 1, 15, '^', [0, 4, 8, 12, 14]);
m.set(81, R - 1, 'G'); m.set(99, R - 1, 'G');
// --- x102-124 冰上钻石阵 ---
carpet(m, 102, R - 1, 23, '%'.length ? [] : []); // 冰面刺毯
ceil(m, 102, 124, R - 5);
vgap(m, 106, R - 2); vgap(m, 111, R - 2);
vgap(m, 116, R - 2); vgap(m, 121, R - 2);
m.set(103, R - 1, 'G');
m.set(126, R - 1, 'G');
m.set(129, R - 1, 'X');
return m.out();
})(),
},
// ============================================================ j5 关口
{
id: 'j5', name: 'THE GAUNTLET', cn: '关口', type: 'jump', diff: 4,
hint: '移动平台跨刺海,炮台点名,矮洞与钻石阵穿插。',
width: 122,
grid: (() => {
const W = 122, H = 28, R = 24;
const m = new Grid(W, H);
m.border('#');
m.rect(1, R, W - 2, 1, '#');
m.set(3, R - 1, 'S');
// --- x5-22 热身刺毯(三道上下夹缝)---
carpet(m, 5, R - 1, 17);
ceil(m, 5, 21, R - 6);
vgap(m, 9, R - 2); vgap(m, 15, R - 2); vgap(m, 21, R - 2);
m.set(7, R - 1, 'G');
// --- x23-49 移动平台跨海 ---
for (let x = 23; x <= 49; x++) { m.set(x, R, '.'); m.set(x, R + 1, '^'); }
m.set(50, R - 1, 'G');
// --- x51-72 矮洞 + 炮台 ---
m.rect(51, R - 4, 22, 1, '#'); // 顶 row 20
carpet(m, 52, R - 1, 20, [0]);
hgap(m, 57, R - 1); hgap(m, 64, R - 1); hgap(m, 70, R - 1);
m.set(56, R - 5, 'O');
m.set(52, R - 1, 'G');
// --- x73-97 浮块跨刺海 + 交叉火力 ---
spikeRun(m, 73, R - 1, 24, '^');
block(m, 76, R - 2, 2); block(m, 82, R - 4, 2); block(m, 88, R - 2, 2); block(m, 93, R - 3, 2);
ceil(m, 75, 96, R - 9); // 浮块区压顶:高块上二段跳必死
m.set(79, R - 6, 'O'); m.set(91, R - 6, 'O');
m.set(95, R - 1, 'G');
// --- x98-118 钻石收尾 ---
carpet(m, 98, R - 1, 20, [0, 1, 19]);
ceil(m, 98, 117, R - 6);
vgap(m, 101, R - 2); vgap(m, 106, R - 2); vgap(m, 111, R - 2); vgap(m, 116, R - 2);
m.set(98, R - 1, 'G');
m.set(119, R - 1, 'G');
m.set(120, R - 1, 'X');
return m.out();
})(),
movers: [
{ x: 25, y: 22, w: 2, h: 1, path: [[25, 22], [35, 22]], speed: 1.6 },
{ x: 39, y: 20, w: 2, h: 1, path: [[39, 20], [47, 20]], speed: 1.9 },
],
turrets: [
{ dir: 'left', period: 70, speed: 2.8, offset: 0 },
{ dir: 'left', period: 62, speed: 3.0, offset: 24 },
{ dir: 'left', period: 66, speed: 2.7, offset: 40 },
],
},
// ============================================================ j6 针刺教堂
{
id: 'j6', name: 'NEEDLE CATHEDRAL', cn: '针刺教堂', type: 'jump', diff: 5,
hint: '三层殿堂:底层向右,竖井爬升,中层折返向左,再爬升,顶层冲刺。',
width: 132,
grid: (() => {
const W = 132, H = 36;
const m = new Grid(W, H);
m.border('#');
// ============ L0 底层(地板 row 30)============
m.rect(1, 30, W - 2, 1, '#');
m.set(3, 29, 'S');
carpet(m, 5, 29, 116);
ceil(m, 5, 120, 24);
vgap(m, 18, 28); vgap(m, 46, 28); vgap(m, 74, 28); vgap(m, 102, 28);
block(m, 32, 29, 2); block(m, 60, 29, 2);
block(m, 88, 29, 2); block(m, 112, 29, 2);
m.set(11, 29, 'G'); m.set(55, 29, 'G'); m.set(95, 29, 'G');
// ============ 右井 x122-129 → L1(左墙底部留 3 格入口)============
m.vline(122, 17, 26, '#'); m.vline(129, 16, 29, '#');
block(m, 124, 27, 2); block(m, 126, 24, 2); block(m, 124, 21, 2); block(m, 126, 18, 2);
wall(m, 129, 26, 26, '<'); wall(m, 122, 23, 23, '>'); wall(m, 129, 20, 20, '<');
// ============ L1 中层(地板 row 16,向左折返,延伸到两侧井底)============
m.rect(1, 16, 122, 1, '#');
carpet(m, 9, 15, 112, [86]); // 86=x95:补 hgap(95) 通道列(否则门芯留一根 '^',GRAV_FALL 0.40 下回跳轨迹下降慢正好嵌进去)
ceil(m, 9, 118, 10);
vgap(m, 108, 14); vgap(m, 82, 14); vgap(m, 56, 14); vgap(m, 30, 14);
hgap(m, 95, 15); hgap(m, 43, 15);
m.set(44, 15, '.'); // 拆掉对刺门左刺'<':GRAV 0.33 下从 x45 起跳第 3 帧必被刺尖别住(起拍窗窄到 2px),清掉后留 ^+> 双刺门,整格起拍都能过
block(m, 69, 15, 2); block(m, 17, 15, 2);
m.set(113, 15, 'G'); m.set(63, 15, 'G'); m.set(21, 15, 'G');
// ============ 左井 x2-7 → L2(右墙底部留 3 格入口,桥接块接 L2 地板边)============
m.vline(8, 9, 12, '#');
block(m, 4, 13, 2); block(m, 2, 10, 2); block(m, 4, 7, 2);
block(m, 6, 7, 2); // 桥
wall(m, 8, 11, 11, '<'); wall(m, 2, 10, 10, '>');
// ============ L2 顶层(地板 row 8,向右到终点)============
m.rect(8, 8, 118, 1, '#');
carpet(m, 9, 7, 114);
m.set(8, 7, '.'); m.set(9, 7, '.'); m.set(10, 7, '.'); m.set(11, 7, '.'); // 井口净空
ceil(m, 9, 122, 2);
vgap(m, 24, 6); vgap(m, 60, 6); vgap(m, 96, 6);
hgap(m, 36, 7); hgap(m, 84, 7);
m.set(15, 7, 'G'); m.set(51, 7, 'G'); m.set(87, 7, 'G');
m.set(122, 7, 'X');
return m.out();
})(),
},
// ============================================================ j7 天降刺
{
id: 'j7', name: 'SKY SPIKES', cn: '天降刺', type: 'jump', diff: 4,
hint: '之字爬塔,每层头顶悬着掉落刺。走近就掉。别停。',
width: 72,
grid: (() => {
const W = 72, H = 52;
const m = new Grid(W, H);
m.border('#');
m.rect(1, 48, W - 2, 1, '#');
m.set(3, 47, 'S');
// 6 层之字平台:每层刺毯 + 钻石 + 掉落刺
const tiers = [
{ y: 44, x0: 4, x1: 30 }, { y: 38, x0: 34, x1: 62 },
{ y: 32, x0: 6, x1: 34 }, { y: 26, x0: 38, x1: 64 },
{ y: 20, x0: 8, x1: 36 }, { y: 14, x0: 40, x1: 64 },
];
for (const t of tiers) {
m.rect(t.x0, t.y, t.x1 - t.x0 + 1, 1, '#');
// 3/4 格交替刺毯;两端各留 2 格净空作为上一层跃入 / 下一层起跳的落脚点
const n = t.x1 - t.x0 + 1;
carpet(m, t.x0, t.y - 1, n, [0, 1, n - 2, n - 1]);
for (let x = t.x0 + 5; x <= t.x1 - 3; x += 7) m.set(x, t.y - 8, 'D');
ceil(m, t.x0 + 2, t.x1 - 2, t.y - 5); // 压顶垂刺:层内二段跳必死
vgap(m, Math.floor((t.x0 + t.x1) / 2), t.y - 2);
}
// 层间连接块(升 2 格,落点对准缺口)
block(m, 32, 46, 2);
block(m, 32, 42, 2); block(m, 34, 40, 2);
block(m, 32, 36, 2); block(m, 30, 34, 2);
block(m, 36, 30, 2); block(m, 38, 28, 2);
block(m, 36, 24, 2); block(m, 34, 22, 2);
block(m, 38, 18, 2); block(m, 40, 16, 2);
// 存档(各层缺口上)
m.set(7, 43, 'G'); m.set(37, 37, 'G'); m.set(9, 31, 'G');
m.set(41, 25, 'G'); m.set(11, 19, 'G'); m.set(43, 13, 'G');
m.set(60, 13, 'X');
return m.out();
})(),
},
// ============================================================ j8 三重威胁
{
id: 'j8', name: 'TRIPLE THREAT', cn: '三重威胁', type: 'jump', diff: 4,
hint: '冰面滑行、塌陷浮桥、单向台攀爬三连考,中间不许喘气。',
width: 130,
grid: (() => {
const W = 130, H = 26, R = 22;
const m = new Grid(W, H);
m.border('#');
m.rect(1, R, W - 2, 1, '#');
m.set(3, R - 1, 'S');
// --- x1-34 冰面段 ---
m.rect(1, R, 36, 1, '%');
for (const [a, b] of [[7, 9], [13, 15], [19, 21], [26, 29]])
for (let x = a; x <= b; x++) { m.set(x, R, '.'); m.set(x, R + 1, '^'); }
vgap(m, 11, R - 2); vgap(m, 17, R - 2); vgap(m, 24, R - 2); vgap(m, 30, R - 2);
m.set(11, R - 1, 'G'); m.set(32, R - 1, 'G');
// --- x38-74 塌陷浮桥(桥下刺海)---
for (let x = 38; x <= 74; x++) { m.set(x, R, '.'); m.set(x, R + 1, '^'); }
for (let x = 38; x <= 74; x++) m.set(x, R - 3, 'V');
m.set(46, R - 4, '^'); m.set(50, R - 4, '^'); m.set(54, R - 4, '^');
m.set(58, R - 4, '^'); m.set(62, R - 4, '^'); m.set(66, R - 4, '^'); m.set(70, R - 4, '^');
ceil(m, 40, 72, R - 9); // 垂刺限高
m.set(51, R - 4, 'G');
// --- x76-100 单向台攀爬 ---
for (let x = 76; x <= 99; x++) { m.set(x, R, '.'); m.set(x, R + 1, '^'); }
m.set(78, R - 2, '-'); m.set(79, R - 2, '-');
m.set(83, R - 4, '-'); m.set(84, R - 4, '-');
m.set(87, R - 6, '-'); m.set(88, R - 6, '-'); m.set(89, R - 6, '-');
m.set(93, R - 4, '-'); m.set(94, R - 4, '-');
m.set(97, R - 2, '-'); m.set(98, R - 2, '-');
// x86 只留压顶刺:JUMP 8.0 下 apex 盒底 479 与 '^' 顶点 480 只差 1px,
// 上升航线有 4 帧顶点落在盒内必死(8.5 时 apex 466 可从容擦过),故去掉地刺
m.set(86, R - 9, 'v');
vgap(m, 93, R - 6);
m.set(101, R - 1, 'G');
// --- x102-126 钻石收尾 ---
carpet(m, 102, R - 1, 24, [0, 1, 20, 21]);
ceil(m, 102, 125, R - 5);
vgap(m, 105, R - 2); vgap(m, 110, R - 2);
vgap(m, 115, R - 2); vgap(m, 120, R - 2);
m.set(102, R - 1, 'G');
m.set(126, R - 1, 'X');
return m.out();
})(),
},
// ============================================================ j9 樱花
{
id: 'j9', name: 'CHERRY BLOSSOM', cn: '樱花', type: 'jump', diff: 5,
hint: '双层结构收 3 个苹果:钻石缝中央一个,矮洞里一个,中层对刺门后一个。',
width: 128,
grid: (() => {
const W = 128, H = 34;
const m = new Grid(W, H);
m.border('#');
// ============ L0 下层(地板 row 28)============
m.rect(1, 28, W - 2, 1, '#');
m.set(3, 27, 'S');
carpet(m, 5, 27, 116, [54]); // 54=x59:拆开矮洞入口的四格跳(洞顶 row24 会挡跳的横向漂移)
ceil(m, 5, 120, 22);
vgap(m, 14, 26); vgap(m, 38, 26);
vgap(m, 86, 26); vgap(m, 110, 26);
block(m, 26, 27, 2); block(m, 50, 27, 2);
block(m, 74, 27, 2); block(m, 98, 27, 2);
m.set(11, 27, 'G'); m.set(55, 27, 'G'); m.set(95, 27, 'G');
// 井口前净空(否则刺毯把竖井入口封死)
m.set(117, 27, '.'); m.set(118, 27, '.'); m.set(119, 27, '.'); m.set(120, 27, '.');
// 苹果 1:钻石缝中央
m.set(62, 26, 'F');
// ============ L0 中段矮洞(顶 row 24,3 格高;撞头跳射程≈3 格,洞内缺口加密到 3)============
m.rect(60, 24, 16, 1, '#');
[63, 66, 69, 72, 75].forEach(x => m.set(x, 27, '.'));
// 苹果 2:矮洞里(跳跃途中拾取)
m.set(66, 26, 'F');
// ============ 右井 x120-126 → L1(左墙底部留 3 格入口)============
m.vline(119, 15, 24, '#'); m.vline(126, 14, 27, '#');
block(m, 121, 25, 2); block(m, 124, 22, 2); block(m, 121, 19, 2); block(m, 124, 16, 2);
wall(m, 126, 24, 24, '<'); wall(m, 119, 21, 21, '>'); wall(m, 126, 18, 18, '<');
// ============ L1 上层(地板 row 14,向左折返,延伸到井底)============
m.rect(2, 14, 118, 1, '#');
carpet(m, 9, 13, 108, [27, 59]); // 27=x36 / 59=x68:补对刺门通道列(否则整门是一面 3 连刺,往回跳必被 < 刺别住)
ceil(m, 9, 116, 8);
hgap(m, 100, 13); hgap(m, 68, 13); hgap(m, 36, 13);
vgap(m, 84, 12); vgap(m, 52, 12); vgap(m, 20, 12);
m.set(105, 13, 'G'); m.set(59, 13, 'G');
// 苹果 3:对刺门后
m.set(44, 12, 'F');
// ============ 终点(上层左端)============
m.set(10, 13, 'X');
return m.out();
})(),
},
// ============================================================ j10 毕业
{
id: 'j10', name: 'FINAL NEEDLE', cn: '毕业针刺', type: 'jump', diff: 5,
hint: '四层大考场:刺毯长廊 → 冰面折返 → 矮洞 → 高空钢丝。全程无尿点。',
width: 150,
grid: (() => {
const W = 150, H = 44;
const m = new Grid(W, H);
m.border('#');
// ============ L0 刺毯长廊(地板 row 38,向右)============
m.rect(1, 38, W - 2, 1, '#');
m.set(3, 37, 'S');
carpet(m, 5, 37, 134);
ceil(m, 5, 138, 32);
vgap(m, 20, 36); vgap(m, 48, 36); vgap(m, 76, 36);
vgap(m, 104, 36); vgap(m, 128, 36);
block(m, 34, 37, 2); block(m, 62, 37, 2);
block(m, 90, 37, 2); block(m, 118, 37, 2);
m.set(11, 37, 'G'); m.set(59, 37, 'G'); m.set(107, 37, 'G');
// ============ 右井 A x140-147 → L1(左墙底部留 3 格入口)============
m.vline(139, 29, 34, '#'); m.vline(147, 28, 37, '#');
block(m, 141, 35, 2); block(m, 144, 32, 2); block(m, 141, 29, 2); block(m, 144, 26, 2);
wall(m, 147, 34, 34, '<'); wall(m, 139, 31, 31, '>');
// ============ L1 冰面层(地板 row 28,向左,延伸到井底)============
m.rect(2, 28, 138, 1, '%');
carpet(m, 11, 27, 126);
m.set(136, 27, '.'); m.set(137, 27, '.'); m.set(138, 27, '.'); // 井 A 出口落点净空
ceil(m, 11, 136, 22);
vgap(m, 116, 26); vgap(m, 88, 26); vgap(m, 60, 26); vgap(m, 32, 26);
block(m, 102, 27, 2); block(m, 74, 27, 2); block(m, 46, 27, 2);
m.set(121, 27, 'G'); m.set(69, 27, 'G'); m.set(23, 27, 'G');
// ============ 左井 B x2-8 → L2(右墙底部留 3 格入口)============
m.vline(8, 17, 24, '#');
block(m, 4, 25, 2); block(m, 2, 22, 2); block(m, 4, 19, 2); block(m, 5, 17, 2);
wall(m, 8, 21, 21, '<'); wall(m, 2, 20, 20, '>'); wall(m, 8, 18, 18, '<');
// ============ L2 矮洞层(地板 row 16,向右,延伸到井 C 底)============
m.rect(8, 16, 138, 1, '#');
m.rect(10, 12, 128, 1, '#'); // 矮洞顶(3 格高洞)
carpet(m, 10, 15, 128);
m.set(8, 15, '.'); m.set(9, 15, '.'); // 井 B 出口净空
hgap(m, 72, 15);
m.set(19, 15, 'G'); m.set(67, 15, 'G'); m.set(115, 15, 'G');
// ============ 右井 C x140-147 → L3(左墙底部留 3 格入口,桥接块接 L3)============
m.vline(139, 9, 12, '#'); m.vline(147, 8, 15, '#');
block(m, 141, 13, 2); block(m, 144, 10, 2); block(m, 141, 7, 2);
block(m, 137, 7, 4); // 桥:向左落到 L3 基座
wall(m, 147, 12, 12, '<'); wall(m, 139, 10, 10, '>');
// ============ L3 高空钢丝(基座 row 8 + 刺毯 row 7,开放高空,向左到终点)============
m.rect(8, 8, 130, 1, '#');
spikeRun(m, 9, 7, 128, '^'); // 刺毯基底
for (let x = 133; x <= 137; x++) m.set(x, 7, '.'); // 井口落脚区
// 钢丝柱:间距 3~6 不规则,柱顶 row 5~7 错落
const wires = [];
const steps = [4, 5, 3, 6, 4, 3, 5, 4, 6, 3, 5, 4, 4, 6, 3, 5, 4, 6, 4, 3, 5, 4, 6, 3, 5, 4, 4, 3];
let wx = 132;
wires.push(wx);
for (const s of steps) { wx -= s; if (wx < 14) break; wires.push(wx); }
wires.forEach((x, i) => m.vline(x, [6, 5, 7, 6, 7, 5][i % 6], 7, '#'));
m.vline(55, 5, 7, '#'); // 过渡柱:x58→x52 原为 6 格跳,HSPD 2.6 下"晚二段"落点整体左移 8px 掉过柱顶摔死;x55 拆成 3+3 格两跳(dj=12 轨迹实测两跳都能精确接住)
// 存档放在两根钢丝顶上
const g1 = wires[6], g2 = wires[Math.floor(wires.length / 2) + 4];
const topOf = i => [6, 5, 7, 6, 7, 5][i % 6];
m.set(g1, topOf(6) - 1, 'G'); m.set(g2, topOf(wires.indexOf(g2)) - 1, 'G');
m.rect(8, 7, 4, 1, '#'); // 终点台
m.set(9, 6, 'X');
return m.out();
})(),
},
];
export default JUMP_LEVELS;