diff --git a/lib/map.js b/lib/map.js index 886bc31..720bf3e 100755 --- a/lib/map.js +++ b/lib/map.js @@ -1163,7 +1163,52 @@ const DUNGEON_ROWS = [ '#..........................................................#', '#..........................~~~~~~...........................#', '############################################################' -].map((row) => row.padEnd(62, '#')) +// The ruins were drawn a few columns short on the right and every row ended +// at a different wall column; padEnd used to hide that from defineMap()'s +// rectangularity check (#10). Normalizing to one fixed width gives the ruins +// a straight east wall, so the check means something again. +].map((row) => (row + '###').slice(0, 60) + '#') + +/** + * The ruins are not empty anymore: three residents camp different chambers, + * reusing the exact NPC machinery the city already runs (nearbyNpc header, + * e-to-talk, interactNpc dialogue) - issue #10's minimum fix. + */ +const DUNGEON_NPCS = [ + { + id: 'eco', + name: 'Eco de las ruinas', + role: 'vidente', + x: 10, + y: 7, + sprite: NPC_SPRITES.resident, + anchorY: NPC_SPRITES.resident.length - 1, + color: 'cyan', + line: 'los muros recuerdan mas pasos que los vivos' + }, + { + id: 'tadeo', + name: 'Tadeo', + role: 'minero', + x: 30, + y: 13, + sprite: NPC_SPRITES.resident, + anchorY: NPC_SPRITES.resident.length - 1, + color: 'yellow', + line: 'la V fue salida, no entrada: alguien selló el resto' + }, + { + id: 'gata', + name: 'Gata de las ruinas', + role: 'gato', + x: 40, + y: 17, + sprite: NPC_SPRITES.resident, + anchorY: NPC_SPRITES.resident.length - 1, + color: 'gray', + line: 'miau' + } +] /** * Turn art plus metadata into a map, checking the art is rectangular. @@ -1212,7 +1257,8 @@ const MAPS = { name: 'las ruinas bajo el castillo', rows: DUNGEON_ROWS, spawn: { x: 3, y: 2 }, - arrive: { x: 3, y: 2 } + arrive: { x: 3, y: 2 }, + npcs: DUNGEON_NPCS }), coliseum: defineMap({ id: 'coliseum', diff --git a/test/dungeon-content.test.js b/test/dungeon-content.test.js new file mode 100644 index 0000000..2f9cf57 --- /dev/null +++ b/test/dungeon-content.test.js @@ -0,0 +1,49 @@ +const { test } = require('brittle') +const { Runa } = require('../lib/game.js') +const M = require('../lib/map.js') + +function press(game, name) { + return game.onKey({ type: 'key', is: (...keys) => keys.includes(name) }) +} + +function startGame(game, name = 'Tomas') { + press(game, 'enter') + for (const ch of String(name)) { + game.onKey({ type: 'key', sequence: ch, ctrl: false, meta: false, is: () => false }) + } + press(game, 'enter') +} + +test('the ruins host three residents on walkable ground (#10)', (t) => { + const npcs = M.MAPS.dungeon.npcs + t.ok(Array.isArray(npcs) && npcs.length >= 3, 'dungeon defines its own residents') + for (const n of npcs) { + t.ok(!M.isSolid(M.MAPS.dungeon, n.x, n.y), `${n.name} stands on open floor at ${n.x},${n.y}`) + } +}) + +test('the east wall of the ruins is one straight line now (#10)', (t) => { + const city = M.MAPS.dungeon + const w = city.width + let aligned = true + for (let y = 0; y < city.height; y++) { + if (city.rows[y].length !== w || city.rows[y][w - 1] !== '#') aligned = false + if (!M.isSolid(city, w - 1, y)) aligned = false + } + t.is(w, 61, 'normalized to a single declared width') + t.ok(aligned, 'column 60 is wall on every row - no more jagged mouth') +}) + +test('talking inside the ruins reaches a resident instead of void (#10)', (t) => { + const game = new Runa({ presence: false }) + startGame(game) + + const npc = M.MAPS.dungeon.npcs[0] + game.walker.placeAt('dungeon', npc.x + 1, npc.y) + press(game, 'e') + + t.ok( + !game.log.some((line) => String(line).includes('aca no hay nada')), + 'the resident answers instead of the void' + ) +}) \ No newline at end of file diff --git a/test/index.js b/test/index.js index 7be3ead..47f34eb 100644 --- a/test/index.js +++ b/test/index.js @@ -19,6 +19,7 @@ const { const render = require('../lib/render.js') require('./sage.test.js') +require('./dungeon-content.test.js') function press(game, name) { return game.onKey({ type: 'key', is: (...keys) => keys.includes(name) })