fix: enforce echo puzzle constraints and validate maps
This commit is contained in:
@@ -41,6 +41,76 @@ for (const [index, level] of levels.entries()) {
|
||||
assert.deepEqual([...plates].sort(), [...gates].sort(), `level ${index + 1} node/gate pairs must match`);
|
||||
}
|
||||
|
||||
function canReachTarget(level, target, openGates = new Set()) {
|
||||
const height = level.map.length;
|
||||
const width = level.map[0].length;
|
||||
const start = {};
|
||||
const destination = {};
|
||||
for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) {
|
||||
if (level.map[y][x] === 'S') Object.assign(start, { x, y });
|
||||
if (level.map[y][x] === target) Object.assign(destination, { x, y });
|
||||
}
|
||||
const queue = [start];
|
||||
const visited = new Set([`${start.x},${start.y}`]);
|
||||
while (queue.length) {
|
||||
const point = queue.shift();
|
||||
if (point.x === destination.x && point.y === destination.y) return true;
|
||||
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
||||
const x = point.x + dx;
|
||||
const y = point.y + dy;
|
||||
if (x < 0 || y < 0 || x >= width || y >= height) continue;
|
||||
const tile = level.map[y][x];
|
||||
if (tile === '#' || /[A-Z]/.test(tile) && tile !== 'S' && tile !== 'G' && !openGates.has(tile)) continue;
|
||||
const key = `${x},${y}`;
|
||||
if (!visited.has(key)) {
|
||||
visited.add(key);
|
||||
queue.push({ x, y });
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
levels.forEach((level, index) => assert.equal(canReachTarget(level, 'G'), false, `level ${index + 1} must require at least one powered gate`));
|
||||
const third = levels[2];
|
||||
assert.equal(canReachTarget(third, 'b'), false, 'the third experiment node b must be behind gate A');
|
||||
assert.equal(canReachTarget(third, 'b', new Set(['A'])), true, 'gate A must expose the third experiment node b');
|
||||
assert.equal(canReachTarget(third, 'G', new Set(['A'])), false, 'the third experiment must still require gate B');
|
||||
assert.equal(canReachTarget(third, 'G', new Set(['A', 'B'])), true, 'both powered gates must expose the third experiment exit');
|
||||
|
||||
function reachableSymbols(level) {
|
||||
const height = level.map.length;
|
||||
const width = level.map[0].length;
|
||||
const start = {};
|
||||
for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) if (level.map[y][x] === 'S') Object.assign(start, { x, y });
|
||||
const queue = [start];
|
||||
const visited = new Set([`${start.x},${start.y}`]);
|
||||
while (queue.length) {
|
||||
const point = queue.shift();
|
||||
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
||||
const x = point.x + dx;
|
||||
const y = point.y + dy;
|
||||
if (x < 0 || y < 0 || x >= width || y >= height) continue;
|
||||
if (level.map[y][x] === '#') continue;
|
||||
const key = `${x},${y}`;
|
||||
if (!visited.has(key)) {
|
||||
visited.add(key);
|
||||
queue.push({ x, y });
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...visited].map((key) => key.split(',').map(Number));
|
||||
}
|
||||
|
||||
levels.forEach((level, index) => {
|
||||
const reachable = new Set(reachableSymbols(level).map(([x, y]) => `${x},${y}`));
|
||||
[...level.map.join('')].filter((symbol) => /[a-z]/.test(symbol)).forEach((symbol) => {
|
||||
const row = level.map.findIndex((line) => line.includes(symbol));
|
||||
const column = level.map[row].indexOf(symbol);
|
||||
assert.ok(reachable.has(`${column},${row}`), `level ${index + 1} node ${symbol} must be reachable from start`);
|
||||
});
|
||||
});
|
||||
|
||||
function simulateFirstExperiment(level) {
|
||||
const map = level.map;
|
||||
const start = { x: 1, y: 1 };
|
||||
@@ -70,6 +140,10 @@ simulateFirstExperiment(first);
|
||||
|
||||
assert.match(script, /function resetEchoPositions\(\)/, 'multi-echo timelines must reset every echo to the start');
|
||||
assert.match(script, /resetEchoPositions\(\);\s*state\.actions = \[\];/, 'recording a new echo must restart the existing echo fleet');
|
||||
assert.match(script, /const occupiedByEcho = state\.echoes\.some/, 'only echoes may power memory nodes');
|
||||
assert.doesNotMatch(script, /const occupiedByPlayer = state\.player\.x/, 'the current player must not directly power memory nodes');
|
||||
assert.match(script, /if \(!isPlate\(currentPlate\)\)/, 'recording must require the player to stand on a memory node');
|
||||
assert.match(script, /state\.echoes\.length >= MAX_ECHOES/, 'the memory bank must enforce its visible capacity');
|
||||
|
||||
console.log('ECHO//LOOP verification passed');
|
||||
console.log(`- ${levels.length} level definitions validated`);
|
||||
|
||||
Reference in New Issue
Block a user