import test from 'node:test'; import assert from 'node:assert/strict'; import { FLOWER_LEVEL, FLOWER_PETALS, flowerTrackEmphasis } from '../src/flower-study.js'; import { Game, analyze, available, trace, rotateBoard, validateLevel } from '../src/core.js'; import { inspectExperience } from '../src/experience-generator.js'; const analysis = analyze(FLOWER_LEVEL); test('the woven flower has twelve petals, interior departures and four full-length journeys', () => { const l = FLOWER_LEVEL, review = inspectExperience(l, analysis.actions); assert.deepEqual(validateLevel(l), []); assert.equal(l.cells.length, 91); assert.equal(l.tiles.length, 10); assert.equal(FLOWER_PETALS.length, 12); assert.equal(analysis.free, 2); assert.equal(analysis.rotations, 1); assert.ok(analysis.solvable && !analysis.exhausted && review.won); assert.equal(review.hidden.length, 0); assert.equal(analysis.pathUsage.curvedRemovals, 8); assert.deepEqual(analysis.pathUsage.paths.slice(-4).map(p => [p.bends, p.cells]), Array(4).fill([37, 59])); assert.ok(analysis.pathUsage.paths.slice(2, 6).every(p => p.bends >= 8 && p.cells >= 13)); }); test('all six orientations keep every piece on visible, open flower routes', () => { for (let turn = 0; turn < 6; turn++) { const level = rotateBoard(FLOWER_LEVEL, turn), emphasis = flowerTrackEmphasis(turn); for (const tile of level.tiles) { const route = trace(level, tile, level.tiles, true); assert.equal(route.status, 'exit'); for (const step of route.steps) { assert.ok(emphasis(step, step.exit), `${turn}: ${tile.id} outgoing track`); if (step.enter !== null) assert.ok(emphasis(step, step.enter), `${turn}: ${tile.id} incoming track`); } } } }); test('the dense flower and its three woven rings participate in the reference solution', () => { const emphasis = flowerTrackEmphasis(), featured = new Set(), used = new Set(); const pairKey = (q, r, a, b) => `${q},${r}:${Math.min(a, b)},${Math.max(a, b)}`; for (const cell of FLOWER_LEVEL.cells) for (let d = 0; d < 6; d++) { if (d < cell.tracks[d] && emphasis(cell, d)) featured.add(pairKey(cell.q, cell.r, d, cell.tracks[d])); } assert.equal(featured.size, 177); assert.ok(featured.size / (FLOWER_LEVEL.cells.length * 3) > .64); const g = new Game(FLOWER_LEVEL); for (const action of analysis.actions) { if (action.type === 'rotate') { g.rotate(action.direction); continue; } const route = g.tap(action.id); assert.equal(route.status, 'exit'); for (const step of route.steps) { // The starting piece travels only the outward half of its paired track. const cell = g.level.cells.find(c => c.q === step.q && c.r === step.r); const ports = [step.enter ?? cell.tracks[step.exit], step.exit].map(d => (d - g.orientation + 6) % 6); let q = step.q, r = step.r; for (let turn = 0; turn < (6 - g.orientation) % 6; turn++) [q, r] = [-r || 0, q + r || 0]; used.add(pairKey(q, r, ...ports)); } } assert.deepEqual(used, featured); }); test('a turn unlocks the last pair while arrows stay fixed, and undo restores the connection', () => { const g = new Game(FLOWER_LEVEL); for (const action of analysis.actions) { if (action.type === 'rotate') break; assert.equal(g.tap(action.id).status, 'exit'); } assert.equal(available(g.level, g.tiles).length, 0); const remaining = structuredClone(g.tiles), centre = g.level.cells.find(c => c.q === 0 && c.r === 0).tracks; assert.equal(g.tap(remaining[0].id).status, 'blocked'); assert.equal(g.moves, 2); assert.equal(g.lost, false); assert.equal(g.rotate(1).status, 'rotated'); assert.deepEqual(g.tiles, remaining); assert.notDeepEqual(g.level.cells.find(c => c.q === 0 && c.r === 0).tracks, centre); assert.equal(available(g.level, g.tiles).length, 2); assert.ok(g.undo()); assert.deepEqual(g.tiles, remaining); assert.deepEqual(g.level.cells.find(c => c.q === 0 && c.r === 0).tracks, centre); }); test('alternative starting orientations and removal orders still clear the flower', () => { for (let turn = 0; turn < 6; turn++) for (const reverse of [false, true]) { const g = new Game(rotateBoard(FLOWER_LEVEL, turn)); for (let step = 0; step < FLOWER_LEVEL.tiles.length; step++) { let free = available(g.level, g.tiles); if (!free.length) { g.rotate(reverse ? -1 : 1); free = available(g.level, g.tiles); } assert.ok(free.length); assert.equal(g.tap((reverse ? free.at(-1) : free[0]).id).status, 'exit'); } assert.ok(g.won); assert.equal(g.moves, 0); } });