99 lines
4.6 KiB
JavaScript
99 lines
4.6 KiB
JavaScript
import { hexagon, DIRS, key, rotateTracks } from './core.js';
|
|
|
|
const rotatePoint = ([q, r], turns) => {
|
|
for (let i = 0; i < (turns % 6 + 6) % 6; i++) [q, r] = [-r || 0, q + r || 0];
|
|
return [q, r];
|
|
};
|
|
const direction = (a, b) => DIRS.findIndex(([q, r]) => b[0] - a[0] === q && b[1] - a[1] === r);
|
|
function ring(radius, [q, r] = [0, 0]) {
|
|
const points = [];
|
|
let at = [q, r + radius];
|
|
for (let d = 0; d < 6; d++) for (let i = 0; i < radius; i++) {
|
|
points.push(at);
|
|
at = at.map((v, axis) => v + DIRS[(d + 5) % 6][axis]);
|
|
}
|
|
return points;
|
|
}
|
|
|
|
// Two nested petals per sector. Neighbouring outer petals cross without joining.
|
|
const PETALS = [ring(2, [0, -3]), ring(1, [0, -3])];
|
|
const featured = [];
|
|
function createFlower() {
|
|
const cells = hexagon(5).map(c => ({ ...c, tracks: Array(6).fill(null) }));
|
|
const board = new Map(cells.map(c => [key(c.q, c.r), c]));
|
|
function pair(at, a, b) {
|
|
const cell = board.get(key(...at));
|
|
if ((cell.tracks[a] !== null && cell.tracks[a] !== b) || (cell.tracks[b] !== null && cell.tracks[b] !== a)) throw new Error('Flower tracks conflict');
|
|
cell.tracks[a] = b; cell.tracks[b] = a;
|
|
featured.push({ q: at[0], r: at[1], a, b });
|
|
}
|
|
for (let petal = 0; petal < 6; petal++) {
|
|
for (const shape of PETALS) {
|
|
const loop = shape.map(pt => rotatePoint(pt, petal));
|
|
for (let i = 0; i < loop.length; i++) {
|
|
const at = loop[i], a = direction(at, loop[(i + loop.length - 1) % loop.length]), b = direction(at, loop[(i + 1) % loop.length]);
|
|
// Open each root: outer petal → inner petal → outward vein.
|
|
if (i === 0) { pair(at, a, (3 + petal) % 6); pair(at, b, petal); }
|
|
else pair(at, a, b);
|
|
}
|
|
}
|
|
for (const distance of [3, 4, 5]) pair(rotatePoint([0, -distance], petal), petal, (petal + 3) % 6);
|
|
}
|
|
for (const a of [0, 2, 4]) pair([0, 0], a, a + 1);
|
|
for (const radius of [1, 2, 3]) {
|
|
const loop = ring(radius);
|
|
for (let i = 0; i < loop.length; i++) pair(loop[i], direction(loop[i], loop[(i + loop.length - 1) % loop.length]), direction(loop[i], loop[(i + 1) % loop.length]));
|
|
}
|
|
|
|
// Fill the remaining ports in rotational orbits, preserving a regular pattern.
|
|
const done = new Set(['0,0']);
|
|
for (const cell of cells) {
|
|
if (done.has(key(cell.q, cell.r))) continue;
|
|
const free = cell.tracks.map((v, i) => v === null ? i : -1).filter(i => i >= 0);
|
|
while (free.length) {
|
|
const a = free.shift(), opposite = (a + 3) % 6;
|
|
const b = free.splice(free.includes(opposite) ? free.indexOf(opposite) : 0, 1)[0];
|
|
cell.tracks[a] = b; cell.tracks[b] = a;
|
|
}
|
|
let tracks = [...cell.tracks];
|
|
for (let turn = 0; turn < 6; turn++) {
|
|
const at = rotatePoint([cell.q, cell.r], turn), target = board.get(key(...at));
|
|
for (let d = 0; d < 6; d++) if (target.tracks[d] !== null && target.tracks[d] !== tracks[d]) throw new Error('Flower symmetry conflicts');
|
|
target.tracks = [...tracks]; done.add(key(...at)); tracks = rotateTracks(tracks);
|
|
}
|
|
}
|
|
// Splice ALL three rings into the petal routes at their six radial crossings.
|
|
// Re-pairing these same featured ports creates one woven, open network; no
|
|
// emphasized ring is left as an ornamental loop that pieces cannot escape.
|
|
for (const [distance, wiring] of [[1, [1, 0, 3, 2, 5, 4]], [2, [1, 0, 3, 2, 5, 4]], [3, [2, 5, 0, 4, 3, 1]]]) {
|
|
let tracks = wiring;
|
|
for (let turn = 0; turn < 6; turn++) {
|
|
board.get(key(...rotatePoint([0, -distance], turn))).tracks = [...tracks];
|
|
tracks = rotateTracks(tracks);
|
|
}
|
|
}
|
|
const tiles = Array.from({ length: 6 }, (_, i) => {
|
|
const [q, r] = rotatePoint([0, -5], i);
|
|
return { id: `petal-${i}`, q, r, dir: [0, 2].includes(i) ? i : (i + 3) % 6, color: i };
|
|
});
|
|
// Four interior departures introduce the weaving before the full-length runs.
|
|
for (const [i, [q, r, dir]] of [[-2, 1, 1], [1, 0, 2], [0, -2, 2], [0, 2, 2]].entries()) {
|
|
tiles.push({ id: `bloom-${i}`, q, r, dir, color: 6 + i });
|
|
}
|
|
return {
|
|
version: 1, id: 'study-flower-01', name: '花有出路 · 重瓣', rotationEnabled: true,
|
|
cells, tiles, moveLimit: tiles.length,
|
|
lesson: '先给出口让路,再解开花心中的棋子;转动底盘,把朝内的箭头接上另一片花瓣。',
|
|
};
|
|
}
|
|
|
|
export const FLOWER_LEVEL = createFlower();
|
|
export const FLOWER_PETALS = PETALS.flatMap(shape => Array.from({ length: 6 }, (_, i) => shape.map(pt => rotatePoint(pt, i))));
|
|
export function flowerTrackEmphasis(turns = 0) {
|
|
const keys = new Set(featured.flatMap(p => {
|
|
const [q, r] = rotatePoint([p.q, p.r], turns);
|
|
return [p.a, p.b].map(d => `${q},${r}:${(d + turns % 6 + 6) % 6}`);
|
|
}));
|
|
return (cell, port) => keys.has(`${cell.q},${cell.r}:${port}`);
|
|
}
|