Files
jiazi-gala-poster-demo/assets/js/page3.js
T

167 lines
5.4 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.
/* 第 3 页场景数据
前景:演职员表;背景:电线、麻雀、草。
本页不铺「天」,空白格保持为空。 */
(() => {
/* 固定种子的噪声,保证每次渲染一致 */
const noise = (x, y) => {
let h = Math.imul(x, 374761393) + Math.imul(y, 668265263);
h = Math.imul(h ^ (h >>> 13), 1274126177);
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
};
/* 火山:占据下半屏,下 2/3 为山体、上 1/3 为熔岩棋盘格 */
const volcano = [];
const VOLCANO_TOP = 21;
const VOLCANO_BOTTOM = 42;
/* 每列的熔岩下沿,形成不规则的熔岩流 */
const lavaBottom = Array.from({ length: 28 }, (_, x) => {
return VOLCANO_TOP + 6 + Math.floor(noise(x, 3) * 3);
});
[5, 13, 21].forEach((x) => {
lavaBottom[x] += 4 + Math.floor(noise(x, 9) * 3);
});
/* 两层熔岩的分界:按列小幅随机起伏 */
const lavaSplit = Array.from({ length: 28 }, (_, x) => {
const offset = Math.round((noise(x, 13) - 0.5) * 4);
return Math.max(
VOLCANO_TOP + 2,
Math.min(VOLCANO_TOP + 5, VOLCANO_TOP + 3 + offset)
);
});
/* 主熔岩区以下为山体,两侧做对称平滑处理 */
const ROCK_START = VOLCANO_TOP + 7;
for (let y = VOLCANO_TOP; y <= VOLCANO_BOTTOM; y++) {
/* 距底部 5 格起铺满整幅宽度,避免山体过陡 */
const fullWidthAt = VOLCANO_BOTTOM - 5;
const t = (y - VOLCANO_TOP) / (fullWidthAt - VOLCANO_TOP);
const baseWidth =
y >= fullWidthAt ? 28 : Math.round(10 + 18 * Math.min(1, Math.max(0, t)));
let width;
let left;
if (y < ROCK_START) {
/* 熔岩区保持原有随机形状 */
const jitter =
y >= fullWidthAt ? 0 : Math.round((noise(0, y) - 0.5) * 2);
width = Math.max(4, Math.min(28, baseWidth + jitter));
left = Math.floor((28 - width) / 2);
left += Math.round((noise(1, y) - 0.5) * 2);
left = Math.max(0, Math.min(28 - width, left));
if (y === VOLCANO_TOP + 1) left = Math.max(0, left - 1);
} else {
/* 山体左右对称,宽度取偶数和,保留低频小幅随机 */
const wobble = Math.round((noise(7, Math.floor(y / 3)) - 0.5) * 2);
let w = y >= fullWidthAt ? 28 : Math.min(28, baseWidth + wobble);
w -= w % 2;
width = Math.max(4, w);
left = (28 - width) / 2;
}
/* 顶部火山口:仅两行凹下,第一行缺口更宽 */
const craterGap = y < ROCK_START ? [4, 2][y - VOLCANO_TOP] || 0 : 0;
const gapStart = craterGap ? Math.floor((width - craterGap) / 2) : 0;
for (let i = 0; i < width; i++) {
if (craterGap && i >= gapStart && i < gapStart + craterGap) continue;
const x = left + i;
const isLava = y <= lavaBottom[x];
const isUpperLava = isLava && y <= lavaSplit[x];
volcano.push({
text: isLava ? ((x + y) % 2 === 0 ? '熔' : '岩') : '石',
x,
y,
cls: isLava ? (isUpperLava ? 'lava-a' : 'lava-b') : 'rock',
});
}
}
/* 第十行左侧补一个「熔」 */
const row10 = volcano.filter((cell) => cell.y === VOLCANO_TOP + 9);
const row10Left = Math.min(...row10.map((cell) => cell.x));
volcano.push({
text: '熔',
x: row10Left - 1,
y: VOLCANO_TOP + 9,
cls: 'lava-b',
});
TextScene.render({
cols: 28,
rows: 44,
skyChar: '',
glyphs: [
/* 电线:整行贯穿页面 */
{ text: '电线'.repeat(14), x: 0, y: 8, cls: 'wire' },
/* 麻雀:竖排于从左到右第四格,落在电线上方 */
{ text: '麻雀', x: 3, y: 6, cls: 'sparrow', vertical: true },
/* 火山铺在底部背景,草、消防栓在其上层 */
...volcano,
/* 草:最底部一排 */
{ text: '草'.repeat(28), x: 0, y: 43, cls: 'grass' },
/* 消防栓:最后三行分散竖排九根,左侧留 2 格 */
...[2, 5, 8, 10, 13, 16, 19, 23, 25].map((x) => ({
text: '消防栓',
x,
y: 41,
cls: 'hydrant',
vertical: true,
})),
],
});
const scene = document.querySelector('.scene');
const credits = document.createElement('h1');
credits.className = 'credits';
credits.textContent = '演职员表';
scene.append(credits);
/* 36 个 nametag:4 列 × 9 行,补满 */
const NAMES = [
'郑滕飞', '秦雪莹', '关宸宇', '王天晓', '谢雨舟', '储明威', '庞熙若',
'刘婉莹', '陈彦淇', '古雅文', '周胤伯', '郑佳欣', '娄敬庭', '张加弛',
'李昀芷', '吴红霓', '鲍馨仪', '刘润泽', '李若熙', '马希予', '袁立桓',
'蒋子模', '李有睿', '王海心', '朱丽烨', '朱彦阳', '汪楚钧', '吴玥',
'张梓轩', '罗午阳', '郭柯言', '王之饶', '应玥', '湛茜茜', '黄钰涵',
'虞韫嘉',
];
const TAG = {
cols: 4,
rows: 9,
width: 5,
height: 2,
gapX: 1,
gapY: 1,
left: 2,
top: 12,
};
let nameIndex = 0;
for (let row = 0; row < TAG.rows; row++) {
for (let col = 0; col < TAG.cols; col++) {
const name = NAMES[nameIndex++];
if (!name) break;
const tag = document.createElement('div');
tag.className = 'nametag';
tag.style.left = `${TAG.left + col * (TAG.width + TAG.gapX)}em`;
tag.style.top = `${TAG.top + row * (TAG.height + TAG.gapY)}em`;
const label = document.createElement('span');
label.className = 'nametag-name';
label.textContent = name;
tag.append(label);
scene.append(tag);
}
}
})();