Files
HexTrails/labs/variants/generate.mjs
T

47 lines
3.3 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.
import { writeFileSync } from 'node:fs';
import { hexagon, random, randomTracks, DIRS, key, trace } from '../../src/core.js';
import { createState, solve, act, routeFor } from './model.js';
const levels = [];
const names = { color: ['双色试航', '转向归站', '先让一路', '曲线分流', '各归其位'], switch: ['第一枚开关', '弯道解锁', '先清再切', '释放内圈', '换轨接力'], relay: ['中途停一停', '停靠再转向', '借位让路', '弯道接驳', '分段出发'] };
for (const mode of ['color', 'switch', 'relay']) {
for (let slot = 0; slot < 5; slot++) {
let accepted = false;
for (let attempt = 0; attempt < 2500 && !accepted; attempt++) {
const seed = 901 + slot * 10000 + attempt + ['color','switch','relay'].indexOf(mode) * 100000;
const rng = random(seed), cells = hexagon(2).map((c, i) => ({ ...c, id: i, tracks: randomTracks(rng) }));
const coords = new Set(cells.map(c => key(c.q, c.r)));
const gates = cells.flatMap(c => DIRS.flatMap(([dq, dr], dir) => coords.has(key(c.q+dq,c.r+dr)) ? [] : [{ q:c.q, r:c.r, dir, color: (dir + c.q - c.r + 12) % 2 }]));
const shuffled = [...cells]; // Fisher–Yates keeps recipes reproducible.
for(let i=shuffled.length-1;i>0;i--){const j=Math.floor(rng()*(i+1));[shuffled[i],shuffled[j]]=[shuffled[j],shuffled[i]];}
const tiles = shuffled.slice(0, 3 + Math.floor(slot / 2)).map((c,i) => ({ id:`p${i}`, q:c.q,r:c.r,dir:Math.floor(rng()*6),color:i%2 }));
if (mode === 'switch') {
tiles[0].trigger = 'A';
const target = cells.find(c => c.q === 0 && c.r === 0);
target.switchId = 'A'; target.alternate = randomTracks(rng);
}
const level = { version:1, id:`${mode}-${slot+1}`, mode, name:names[mode][slot], seed, cells, tiles, gates, stops:[{q:0,r:0}], moveLimit:tiles.length };
const start = createState(level), solution = solve(start, 2500);
if (!solution.solved) continue;
let state = start, stopCount = 0, bends = 0, changedRoute = false, openedRoute = false;
for (const action of solution.actions) {
const result = act(state, action);
if (result.status === 'stop') stopCount++;
if (result.status === 'exit') bends += result.steps.filter(s => s.enter !== null && (s.enter+3)%6 !== s.exit).length;
if (result.state.fired.length > state.fired.length) {
openedRoute = result.state.tiles.some(t => routeFor(state, t).status !== 'exit' && routeFor(result.state, t).status === 'exit');
changedRoute = result.state.tiles.some(t => JSON.stringify(trace(state,t,[],true).steps) !== JSON.stringify(trace(result.state,t,[],true).steps));
}
state = result.state;
}
if (!bends || !solution.actions.some(a => a.type === 'rotate')) continue;
if (mode === 'relay' && !stopCount) continue;
if (mode === 'switch' && (!changedRoute || !openedRoute)) continue;
levels.push({ ...level, solution:solution.actions, evidence:{ bends, stopCount, changedRoute, openedRoute, states:solution.states } });
console.log(level.id, seed, solution.actions.length);
accepted = true;
}
if (!accepted) throw new Error(`No candidate: ${mode} ${slot}`);
}
}
writeFileSync(new URL('./levels.json', import.meta.url), JSON.stringify(levels, null, 2)+'\n');