Files
kidgo/js/levels/jump.js
T
2026-10-20 22:21:26 +08:00

369 lines
14 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 关
// 设计基调:地图尽可能大、落脚点尽可能小、存档点尽可能稀疏。
// 物理上限(改动前务必记住):
// 平跳水平 ≈ 4 格(127px),二段跳 ≈ 6 格(206px)
// 单跳上升 ≈ 2.8 格(90px),二段跳上升 ≈ 5 格(162px)
// 天花板刺放在「平台行 -5」:允许完整单跳、二段跳必死 → 只能走 4 格以内的坑
import { Grid } from './kit.js';
/** 落脚台序列:run(起始列, 起始宽, [[间隙, 台宽], ...]) => [{x,w}, ...] */
function run(x0, w0, specs) {
const out = [{ x: x0, w: w0 }];
let x = x0 + w0;
for (const [g, w] of specs) { x += g; out.push({ x, w }); x += w; }
return out;
}
const putAll = (m, row, pads, ch = '#') => pads.forEach(p => m.rect(p.x, row, p.w, 1, ch));
/** 竖井:在 [x0,x1] 列范围内交错上行,每级上升 2 格、横移 step 列 */
function shaft(m, rowBottom, rowTop, x0, x1, step = 6, w = 2) {
const out = [];
let row = rowBottom, x = x0, dir = 1;
while (row >= rowTop) {
m.rect(x, row, w, 1, '#');
out.push({ x, row });
row -= 2;
const nx = x + dir * step;
if (nx < x0 || nx + w - 1 > x1) dir = -dir;
else x = nx;
}
return out;
}
export default [
// ================= 1 =================
{
id: 'j1', name: 'SPIKE ALLEY', type: 'jump', diff: 2, bg: 'jump',
width: 104, hint: '刺海长廊。落脚点越来越小,坑越来越宽。104 格,只有三个存档点。',
grid: (() => {
const W = 104, H = 20, R = 17;
const m = new Grid(W, H);
m.border();
m.hline(18, 1, W - 2, '^'); // 刺海
const pads = run(2, 6, [
[4, 2], [4, 2], [4, 2], [5, 2], [5, 1],
[4, 1], [5, 1], [6, 1], [5, 2], [4, 1],
]);
putAll(m, R, pads);
// 尽头接竖井,终点在半空
const sp = shaft(m, 15, 5, 72, 96, 6, 2);
for (let r = 7; r < 16; r += 3) { m.set(70, r, '>'); m.set(98, r, '<'); }
const top = sp[sp.length - 1];
m.set(3, R - 1, 'S');
m.set(pads[3].x, R - 1, 'G');
m.set(pads[7].x, R - 1, 'G');
m.set(sp[1].x, sp[1].row - 1, 'G');
m.set(sp[3].x, sp[3].row - 1, 'G');
m.set(top.x, top.row - 1, 'X');
return m.out();
})(),
},
// ================= 2 =================
{
id: 'j2', name: 'VERTICAL HELL', type: 'jump', diff: 3, bg: 'jump',
width: 68, hint: '68×52 的高塔。每级上升 2 格、横移 6 格,头顶还悬着掉刺。',
grid: (() => {
const W = 68, H = 52;
const m = new Grid(W, H);
m.border();
m.hline(H - 2, 1, W - 2, '^');
let row = H - 3, x = 4, dir = 1;
const pads = [];
while (row >= 5) {
m.rect(x, row, 2, 1, '#');
pads.push({ x, row });
row -= 2;
const nx = x + dir * 6;
if (nx + 1 > W - 8) dir = -1; else if (nx < 4) dir = 1; else x = nx;
}
for (let r = 10; r < H - 4; r += 3) { m.set(1, r, '>'); m.set(W - 2, r, '<'); }
pads.forEach((p, i) => { if (i % 3 === 1 && p.row - 6 > 2) m.set(p.x, p.row - 6, 'D'); });
pads.forEach((p, i) => { if (i > 0 && i % 6 === 0) m.set(p.x, p.row - 1, 'G'); });
m.set(5, H - 4, 'S');
const top = pads[pads.length - 1];
m.set(top.x, top.row - 1, 'X');
return m.out();
})(),
},
// ================= 3 =================
{
id: 'j3', name: 'TIGHTROPE', type: 'jump', diff: 4, bg: 'jump',
width: 140, hint: '140 格的一格宽钢丝,头顶全是刺:只能单跳,二段跳必死。三个存档点。',
grid: (() => {
const W = 140, H = 22, R = 17;
const m = new Grid(W, H);
m.border();
m.hline(18, 1, W - 2, '^');
const pads = run(2, 5, Array.from({ length: 14 }, () => [4, 1]));
putAll(m, R, pads);
m.hline(R - 5, 8, 78, 'v'); // 天花板刺
const sp = shaft(m, 15, 5, 80, 108, 6, 2);
[7, 10].forEach(r => m.set(78, r, '>'));
[7, 10, 13].forEach(r => m.set(110, r, '<'));
const top = sp[sp.length - 1];
m.set(3, R - 1, 'S');
m.set(pads[4].x, R - 1, 'G');
m.set(pads[10].x, R - 1, 'G');
m.set(sp[1].x, sp[1].row - 1, 'G');
m.set(sp[3].x, sp[3].row - 1, 'G');
m.set(top.x, top.row - 1, 'X');
return m.out();
})(),
},
// ================= 4 =================
{
id: 'j4', name: 'ICE NEEDLES', type: 'jump', diff: 4, bg: 'jump',
width: 130, hint: '前半段冰面打滑、坑有五格宽;后半段一格宽冰柱悬在刺海上——头顶有刺,只能单跳。',
grid: (() => {
const W = 130, H = 24, R = 19;
const m = new Grid(W, H);
m.border();
m.hline(20, 1, W - 2, '^');
m.rect(2, R, 6, 1, '%');
[{ x: 13, w: 6 }, { x: 24, w: 6 }, { x: 35, w: 6 }, { x: 46, w: 5 }]
.forEach(s => m.rect(s.x, R, s.w, 1, '%'));
const pads = run(56, 4, Array.from({ length: 8 }, () => [4, 1]));
putAll(m, R, pads);
m.hline(R - 5, 56, 100, 'v');
const sp = shaft(m, 17, 5, 103, 126, 6, 2);
[8, 11].forEach(r => m.set(101, r, '>'));
[8, 11, 14, 17].forEach(r => m.set(128, r, '<'));
const top = sp[sp.length - 1];
m.set(4, R - 1, 'S');
m.set(14, R - 1, 'G');
m.set(36, R - 1, 'G');
m.set(pads[4].x, R - 1, 'G');
m.set(sp[2].x, sp[2].row - 1, 'G');
m.set(top.x, top.row - 1, 'X');
return m.out();
})(),
},
// ================= 5 =================
{
id: 'j5', name: 'THE GAUNTLET', type: 'jump', diff: 5, bg: 'jump',
width: 120, hint: '两格宽的移动平台横跨刺海,旁边还有炮台点名。等它靠岸,别抢。',
grid: (() => {
const W = 120, H = 24, R = 19;
const m = new Grid(W, H);
m.border();
m.hline(20, 1, W - 2, '^');
[{ x: 2, w: 6 }, { x: 24, w: 3 }, { x: 46, w: 3 }, { x: 68, w: 3 }, { x: 90, w: 3 }]
.forEach(s => m.rect(s.x, R, s.w, 1, '#'));
[35, 57, 79].forEach(x => m.rect(x, R, 1, 1, '#'));
[30, 52, 74, 86].forEach(x => m.set(x, R - 3, 'O'));
const sp = shaft(m, 17, 5, 96, 116, 6, 2);
for (let r = 9; r < 18; r += 3) { m.set(94, r, '>'); m.set(118, r, '<'); }
const top = sp[sp.length - 1];
m.set(4, R - 1, 'S');
m.set(25, R - 1, 'G');
m.set(47, R - 1, 'G');
m.set(69, R - 1, 'G');
m.set(sp[2].x, sp[2].row - 1, 'G');
m.set(top.x, top.row - 1, 'X');
return m.out();
})(),
movers: [
{ x: 10, y: 19, w: 2, h: 1, path: [[10, 19], [22, 19]], speed: 1.5 },
{ x: 29, y: 19, w: 2, h: 1, path: [[29, 19], [44, 19]], speed: 1.7 },
{ x: 51, y: 19, w: 2, h: 1, path: [[51, 19], [66, 19]], speed: 1.9 },
{ x: 73, y: 19, w: 2, h: 1, path: [[73, 19], [88, 19]], speed: 2.1 },
],
turrets: [
{ dir: 'left', period: 70, speed: 2.6, offset: 0 },
{ dir: 'left', period: 62, speed: 2.8, offset: 20 },
{ dir: 'left', period: 58, speed: 3.0, offset: 40 },
{ dir: 'left', period: 54, speed: 3.2, offset: 10 },
],
},
// ================= 6 =================
{
id: 'j6', name: 'NEEDLE CATHEDRAL', type: 'jump', diff: 5, bg: 'jump',
width: 132, hint: '下层长廊 → 竖井爬 9 层 → 上层长廊横穿回去。132×34,五个存档点。',
grid: (() => {
const W = 132, H = 34;
const m = new Grid(W, H);
m.border();
m.hline(H - 2, 1, W - 2, '^');
// --- 下层长廊 row 29(列 2..86)---
const low = run(2, 6, [
[4, 2], [4, 2], [4, 1], [5, 1], [4, 2], [5, 1],
[4, 1], [5, 2], [4, 1], [5, 1], [4, 2], [5, 1],
[4, 3],
]);
putAll(m, 29, low);
m.hline(30, 1, W - 2, '^');
// --- 竖井:列 88..120,row 27 → 11 ---
const sp = shaft(m, 27, 11, 88, 120, 6, 2);
for (let r = 12; r < 28; r += 4) { m.set(86, r, '>'); m.set(122, r, '<'); }
// --- 上层长廊 row 11,从竖井顶往左横穿 ---
const up = [];
let x = sp[sp.length - 1].x;
while (x > 6) { m.rect(x, 11, 2, 1, '#'); up.push(x); x -= 6; }
m.hline(12, 6, 84, '^'); // 上层地板刺(避开竖井列)
m.hline(6, 6, 84, 'v'); // 上层天花板刺
m.rect(2, 11, 6, 1, '#');
m.set(4, 10, 'X');
m.set(4, 28, 'S');
m.set(low[3].x, 28, 'G');
m.set(low[7].x, 28, 'G');
m.set(low[11].x, 28, 'G');
m.set(sp[3].x, sp[3].row - 1, 'G');
m.set(up[3], 10, 'G');
m.set(up[8], 10, 'G');
return m.out();
})(),
},
// ================= 7 =================
{
id: 'j7', name: 'SKY SPIKES', type: 'jump', diff: 5, bg: 'jump',
width: 72, hint: '72×52。两格宽的平台、六格横移,每两级就有一个平台只有一格宽,两边墙上全是刺。',
grid: (() => {
const W = 72, H = 52;
const m = new Grid(W, H);
m.border();
m.hline(H - 2, 1, W - 2, '^');
let row = H - 3, x = 4, dir = 1;
const pads = [];
while (row >= 5) {
const w = (pads.length % 4 === 3) ? 1 : 2;
m.rect(x, row, w, 1, '#');
pads.push({ x, row });
row -= 2;
const nx = x + dir * 6;
if (nx + 1 > W - 8) dir = -1; else if (nx < 4) dir = 1; else x = nx;
}
for (let r = 8; r < H - 4; r += 2) { m.set(1, r, '>'); m.set(W - 2, r, '<'); }
pads.forEach((p, i) => { if (i % 2 === 0 && p.row - 5 > 2) m.set(p.x, p.row - 5, 'D'); });
pads.forEach((p, i) => { if (i > 0 && i % 5 === 0) m.set(p.x, p.row - 1, 'G'); });
m.set(5, H - 4, 'S');
const top = pads[pads.length - 1];
m.set(top.x, top.row - 1, 'X');
return m.out();
})(),
},
// ================= 8 =================
{
id: 'j8', name: 'TRIPLE THREAT', type: 'jump', diff: 5, bg: 'jump',
width: 130, hint: '冰 → 24 格长的塌陷浮桥 → 一格宽钢丝 → 单向平台直上。四连击。',
grid: (() => {
const W = 130, H = 26, R = 21;
const m = new Grid(W, H);
m.border();
m.hline(22, 1, W - 2, '^');
// 1) 冰面
m.rect(2, R, 6, 1, '%');
[{ x: 13, w: 6 }, { x: 24, w: 6 }, { x: 35, w: 5 }].forEach(s => m.rect(s.x, R, s.w, 1, '%'));
// 2) 消失浮桥
m.rect(41, R, 24, 1, 'V');
// 3) 一格宽钢丝(顶刺,只能单跳)
const pads = run(69, 3, [[4, 1], [4, 1], [4, 1], [4, 1], [4, 1]]);
putAll(m, R, pads);
m.hline(R - 5, 69, 92, 'v');
// 4) 单向平台直上
const sp = shaft(m, 19, 7, 96, 124, 6, 2);
const top = sp[sp.length - 1];
m.set(4, R - 1, 'S');
m.set(14, R - 1, 'G');
m.set(36, R - 1, 'G');
m.set(pads[2].x, R - 1, 'G');
m.set(sp[2].x, sp[2].row - 1, 'G');
m.set(top.x, top.row - 1, 'X');
return m.out();
})(),
},
// ================= 9 =================
{
id: 'j9', name: 'CHERRY BLOSSOM', type: 'jump', diff: 5, bg: 'jump',
width: 128, hint: '苹果挂在钢丝之间的空中。不拿能通关,拿了才算完整——你会为它死很多次。',
grid: (() => {
const W = 128, H = 34;
const m = new Grid(W, H);
m.border();
m.hline(H - 2, 1, W - 2, '^');
// 下层长廊 row 29(列 2..84)
const low = run(2, 6, [
[4, 2], [5, 1], [4, 1], [5, 2], [4, 1], [5, 1], [4, 2], [5, 1], [4, 1],
[5, 2], [4, 1], [5, 2], [4, 1],
]);
putAll(m, 29, low);
m.hline(30, 1, W - 2, '^');
// 竖井:列 88..118,row 27 → 13
const sp = shaft(m, 27, 13, 88, 118, 6, 2);
for (let r = 14; r < 28; r += 4) { m.set(86, r, '>'); m.set(120, r, '<'); }
// 上层钢丝 row 11,从竖井顶往左
const up = [];
let x = sp[sp.length - 1].x;
while (x > 8) { m.rect(x, 11, 1, 1, '#'); up.push(x); x -= 4; }
m.hline(12, 8, 84, '^');
m.hline(6, 8, 84, 'v');
m.rect(2, 11, 6, 1, '#');
// 苹果挂在钢丝之间的空中(要跳过去才拿得到)
[3, 7, 11].forEach(i => { if (up[i]) m.set(up[i] - 2, 8, 'F'); });
m.set(4, 10, 'X');
m.set(4, 28, 'S');
m.set(low[3].x, 28, 'G');
m.set(low[6].x, 28, 'G');
m.set(sp[3].x, sp[3].row - 1, 'G');
m.set(up[4], 10, 'G');
m.set(up[9], 10, 'G');
return m.out();
})(),
},
// ================= 10 =================
{
id: 'j10', name: 'FINAL NEEDLE', type: 'jump', diff: 5, bg: 'jump',
width: 150, hint: '150×40。冰 → 钢丝 → 移动平台 → 竖井 → 钢丝。跳刺的毕业考,祝你活着出去。',
grid: (() => {
const W = 150, H = 40;
const m = new Grid(W, H);
m.border();
m.hline(H - 2, 1, W - 2, '^'); // row 38 刺海
// 1) 冰面 row 35
m.rect(2, 35, 6, 1, '%');
[{ x: 13, w: 6 }, { x: 24, w: 6 }, { x: 35, w: 5 }, { x: 45, w: 5 }]
.forEach(s => m.rect(s.x, 35, s.w, 1, '%'));
m.hline(36, 1, W - 2, '^');
// 2) 钢丝 row 35(顶刺,只能单跳)
const w1 = run(54, 4, [[4, 1], [4, 1], [4, 1], [4, 1], [4, 1], [4, 1], [4, 1]]);
putAll(m, 35, w1);
m.hline(30, 54, 92, 'v');
// 3) 移动平台跨刺海
m.rect(97, 35, 4, 1, '#');
m.rect(118, 35, 4, 1, '#');
// 4) 竖井 列 118..144,row 33 → 9
const sp = shaft(m, 33, 9, 118, 144, 6, 2);
for (let r = 10; r < 34; r += 4) { m.set(116, r, '>'); m.set(146, r, '<'); }
// 5) 上层钢丝 row 7,往左到终点
const w2 = [];
let x = sp[sp.length - 1].x - 2;
while (x > 8) { m.rect(x, 7, 1, 1, '#'); w2.push(x); x -= 4; }
m.hline(8, 6, 114, '^');
m.hline(2, 6, 114, 'v');
m.rect(2, 7, 6, 1, '#');
m.set(4, 6, 'X');
m.set(4, 34, 'S');
m.set(14, 34, 'G');
m.set(36, 34, 'G');
m.set(w1[3].x, 34, 'G');
m.set(98, 34, 'G');
m.set(sp[4].x, sp[4].row - 1, 'G');
m.set(w2[5], 6, 'G');
m.set(w2[12], 6, 'G');
return m.out();
})(),
movers: [
{ x: 104, y: 35, w: 2, h: 1, path: [[104, 35], [116, 35]], speed: 2.0 },
],
},
];