import assert from 'node:assert/strict'; import test from 'node:test'; import { Inbound, catalog, checkLanding, reading, validateInbound } from '../labs/inbound/core.js'; import { LEVELS } from '../labs/inbound/levels.js'; test('slice levels are unambiguous and replay the standard dispatch', () => { for (const level of LEVELS) { assert.deepEqual(validateInbound(level), [], level.id); const game = new Inbound(level); for (const action of level.solution) { const result = game.act(action); assert.notEqual(result.status, 'blocked', `${level.id} ${action.type} ${action.id || ''}`); assert.notEqual(result.status, 'ambiguous', level.id); assert.notEqual(result.status, 'invalid', level.id); } assert.equal(game.won, true, level.id); assert.equal(game.state.stars, level.standard, level.id); } }); test('a turn is countable before preview, and does not move geometry', () => { const turn = LEVELS[1]; const before = reading(turn, 0); assert.equal(before[0].open, false); assert.equal(before[0].right, 1); assert.equal(before[0].side, '右上方'); const after = reading(turn, 1); assert.equal(after[0].open, true); assert.equal(after[0].side, before[0].side); assert.equal(after[0].exitSide, before[0].exitSide); assert.equal(catalog(turn)[0].stand.q, 1); }); test('one turn opens the near mouth and closes the far one', () => { const level = LEVELS[2]; const start = reading(level, 0); const right = start.find(mouth => mouth.side === '右方'); const left = start.find(mouth => mouth.side === '左方'); assert.equal(right.open, true); assert.equal(left.open, false); assert.equal(left.left, 1); const turned = reading(level, 5); assert.equal(turned.find(mouth => mouth.side === '左方').open, true); assert.equal(turned.find(mouth => mouth.side === '右方').open, false); const game = new Inbound(level); assert.equal(game.act({ type: 'land', id: 'cocoa' }).status, 'no-route'); assert.equal(game.state.stars, 0); }); test('parking the near plane blocks the path that crosses its stand', () => { const level = LEVELS[3]; const game = new Inbound(level); assert.equal(game.act({ type: 'land', id: 'berry' }).status, 'land'); const blocked = game.act({ type: 'land', id: 'cocoa' }); assert.equal(blocked.status, 'blocked'); assert.equal(blocked.blocker, 'berry'); assert.equal(game.state.stars, 1); }); test('relocate exit side does not depend on the dial', () => { const level = LEVELS[5]; const game = new Inbound(level); assert.equal(game.act({ type: 'land', id: 'berry' }).status, 'land'); const first = game.act({ type: 'relocate', id: 'berry' }); assert.equal(first.sideName, '右下方'); assert.equal(game.state.planes.find(plane => plane.id === 'berry').side, 2); const other = new Inbound(level); other.act({ type: 'confirm', dial: 3, steps: 3 }); other.act({ type: 'confirm', dial: 0, steps: 3 }); other.act({ type: 'land', id: 'berry' }); assert.equal(other.act({ type: 'relocate', id: 'berry' }).sideName, '右下方'); const mouths = catalog(level); assert.equal(mouths.find(mouth => mouth.id === 'berry-up').exitSideName, '右下方'); assert.equal(mouths.find(mouth => mouth.id === 'cocoa-right').sideName, '右方'); }); test('a closed mouth stays closed for all six dials if that side has no tick', () => { const level = LEVELS[0]; for (let dial = 0; dial < 6; dial++) { const open = reading(level, dial).filter(mouth => mouth.open); if (dial === 0) assert.equal(open.length, 1); else assert.equal(open.length, 0); } const game = new Inbound(level); game.act({ type: 'confirm', dial: 1, steps: 1 }); assert.equal(checkLanding(level, game.state, 'cocoa').status, 'no-route'); });