import { describe, expect, it } from 'vitest'; import { THE_ROYAL } from '../../src/scenes/floor/layouts/theRoyal'; import { isWalkable, tileAt, worldToTile } from '../../src/scenes/floor/venueMap'; import { assertLayout } from './layoutInvariants'; const map = THE_ROYAL.map; const countTiles = (predicate: (kind: ReturnType) => boolean): number => { let n = 0; for (let ty = 0; ty < map.height; ty++) { for (let tx = 0; tx < map.width; tx++) if (predicate(tileAt(map, tx, ty))) n++; } return n; }; const propsOf = (kind: string) => THE_ROYAL.props.filter((p) => p.kind === kind); describe('THE_ROYAL layout', () => { it('satisfies the shared venue invariants', () => { assertLayout(THE_ROYAL, { bar: 6, dance: 4, booth: 2, toilet: 2, smoke: 2, entry: 1, exit: 1 }); }); it('matches the venue id the ladder looks it up by', () => { // data/venues.ts finds a night's room by VenueDef.id — a typo here is a // venue that silently falls back to Voltage. expect(THE_ROYAL.id).toBe('theRoyal'); }); // ---- the horseshoe --------------------------------------------------------- it('wraps the public bar in counter on three sides with a workable pit inside', () => { expect(tileAt(map, 22, 17)).toBe('bar'); // north run expect(tileAt(map, 22, 25)).toBe('bar'); // south run expect(tileAt(map, 28, 21)).toBe('bar'); // the bend // The open west end is the whole point: the taps post lives in the pit, so // walling this in freezes the bar shift for the night. expect(isWalkable(map, 15, 21)).toBe(true); const taps = THE_ROYAL.posts.taps; expect(taps).toBeDefined(); expect(tileAt(map, taps!.tx, taps!.ty)).toBe('floor'); }); // ---- the DJ corner --------------------------------------------------------- it('gives the DJ a folding table on the carpet, not a booth', () => { expect(countTiles((k) => k === 'djbooth' || k === 'djfloor')).toBe(0); const decks = THE_ROYAL.posts.decks; expect(decks).toBeDefined(); expect(isWalkable(map, decks!.tx, decks!.ty)).toBe(true); for (const p of [...propsOf('deck'), ...propsOf('mixer'), ...propsOf('highTable')]) { if (p.tx < 50 || p.ty < 33) continue; // the pool-room leaner is not DJ gear expect(isWalkable(map, p.tx, p.ty), `'${p.id}' is not on plain floor`).toBe(true); } }); // ---- the dunnies ----------------------------------------------------------- it('has two cubicles, one of them plainly out of order', () => { expect(map.stalls).toHaveLength(2); expect(map.anchors.toilet).toHaveLength(2); for (const stall of map.stalls) { const inside = worldToTile(stall.inside.x, stall.inside.y); expect(tileAt(map, inside.tx, inside.ty)).toBe('stall'); } // The story prop: a wet floor sign and a mop bucket parked at cubicle two. const wet = propsOf('wetFloor'); expect(wet).toHaveLength(1); expect(propsOf('mopBucket')).toHaveLength(1); const secondDoor = worldToTile(map.stalls[1]!.door.x, map.stalls[1]!.door.y); expect(Math.abs(wet[0]!.tx - secondDoor.tx)).toBeLessThanOrEqual(2); }); it('plumbs the dunnies with a trough and a single basin', () => { expect(propsOf('trough')).toHaveLength(1); expect(propsOf('sinkRow')).toHaveLength(1); // Both stand on 'sink' tiles, which are furniture: you cannot walk the trough. for (const p of [...propsOf('trough'), ...propsOf('sinkRow')]) { expect(tileAt(map, p.tx, p.ty), `'${p.id}' is not plumbed in`).toBe('sink'); } }); // ---- the beer garden ------------------------------------------------------- it('opens the beer garden to the sky inside a fence, with one gate', () => { const yard = countTiles((k) => k === 'yard'); expect(yard).toBeGreaterThan(200); // Every yard tile must sit inside the fence rectangle, or the rain falls in // the pub (the renderer keys the weather off 'yard', not off a room id). for (let ty = 0; ty < map.height; ty++) { for (let tx = 0; tx < map.width; tx++) { if (tileAt(map, tx, ty) !== 'yard') continue; expect(tx).toBeGreaterThan(12); expect(tx).toBeLessThan(45); expect(ty).toBeGreaterThan(33); expect(ty).toBeLessThan(43); } } expect(countTiles((k) => k === 'smokeDoor')).toBe(1); expect(tileAt(map, 29, 33)).toBe('smokeDoor'); expect(tileAt(map, 29, 32)).toBe('floor'); expect(tileAt(map, 29, 34)).toBe('yard'); // It is the nicest room in the pub, so it is furnished like one. expect(propsOf('picnicTable').length).toBeGreaterThanOrEqual(4); expect(propsOf('beerUmbrella').length).toBeGreaterThanOrEqual(2); expect(propsOf('patioHeater')).toHaveLength(1); expect(propsOf('buttBin')).toHaveLength(1); }); // ---- the rooms that are not the dance floor --------------------------------- it('keeps the dance floor smaller than the pool room it plays second fiddle to', () => { const dance = countTiles((k) => k === 'dance'); expect(dance).toBeGreaterThan(0); // Voltage's floor is 375 tiles. This one is an afterthought bolted on in 2004. expect(dance).toBeLessThan(150); expect(propsOf('poolTable')).toHaveLength(2); expect(propsOf('cueRack')).toHaveLength(1); expect(propsOf('dartboard')).toHaveLength(1); expect(propsOf('pokie')).toHaveLength(6); expect(propsOf('tabBoard').length).toBeGreaterThanOrEqual(2); expect(propsOf('bistroTable').length).toBeGreaterThanOrEqual(4); expect(propsOf('bainMarie')).toHaveLength(1); expect(propsOf('raffleDrum')).toHaveLength(1); expect(propsOf('jukebox')).toHaveLength(1); expect(propsOf('discoball')).toHaveLength(1); }); // ---- light signature ------------------------------------------------------- it('lights the pub amber and spends its one signature colour on the pokies', () => { const AMBER = 0xd8a020; const POKIE_BLUE = 0x4aa0c0; const allowed = new Set([ AMBER, POKIE_BLUE, 0x30a050, // pool-table cone 0x50c060, // dunnies fluoro 0xd03470, // dance + entry pink 0x3060c0, // DJ 0x6080a0, // beer garden ]); for (const l of THE_ROYAL.lights) { expect(allowed.has(l.colour), `light '${l.id}' invents a colour`).toBe(true); } expect(THE_ROYAL.lights.filter((l) => l.colour === AMBER).length).toBeGreaterThanOrEqual(6); expect(THE_ROYAL.lights.filter((l) => l.colour === POKIE_BLUE).length).toBeGreaterThanOrEqual(2); expect(THE_ROYAL.lights.filter((l) => l.colour === 0x30a050)).toHaveLength(2); // No pink anywhere but the dance corner and the way out (VENUES.md §2). for (const l of THE_ROYAL.lights) { if (l.colour !== 0xd03470) continue; const onDance = tileAt(map, l.tx, l.ty) === 'dance'; expect(onDance || l.id === 'entry', `pink light '${l.id}' is loose in the pub`).toBe(true); if (onDance) expect(l.pulse).toBe('beat'); } }); it('lays pub carpet rather than club floor', () => { expect(THE_ROYAL.palette?.floor).toBeDefined(); // Warm: more red than blue, which is the whole difference from Voltage. const floor = THE_ROYAL.palette!.floor!; expect((floor >> 16) & 0xff).toBeGreaterThan(floor & 0xff); }); });