369 lines
14 KiB
JavaScript
369 lines
14 KiB
JavaScript
// 跳刺(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 },
|
||
],
|
||
},
|
||
];
|