diff --git a/lib/map.js b/lib/map.js index de3673a..6902237 100755 --- a/lib/map.js +++ b/lib/map.js @@ -911,6 +911,21 @@ function makeHighResolutionCityRows() { const garden = (x, y, w, h) => { fill(x, y, w, h, ';') border(x, y, w, h) + // Four open approaches keep the flowerbeds reachable instead of becoming + // another sealed compound (#5), the same idea civicSquare() applies to the + // plaza. Gates are two cells wide so the wide hero sprite reads cleanly + // while passing through, and they are carved in cobble to blend with the + // garden paths; the hedge stays continuous everywhere else. + const cxg = x + Math.floor(w / 2) + const cyg = y + Math.floor(h / 2) + set(cxg, y, ';') + set(cxg - 1, y, ';') + set(cxg, y + h - 1, ';') + set(cxg - 1, y + h - 1, ';') + set(x, cyg, ';') + set(x, cyg - 1, ';') + set(x + w - 1, cyg, ';') + set(x + w - 1, cyg - 1, ';') for (let row = y + 3; row < y + h - 3; row += 5) { for (let col = x + 4; col < x + w - 4; col += 7) set(col, row, (row + col) % 2 ? '*' : '"') } diff --git a/test/index.js b/test/index.js index 478ca41..1789897 100644 --- a/test/index.js +++ b/test/index.js @@ -17,6 +17,7 @@ const { const render = require('../lib/render.js') require('./sage.test.js') +require('./open-city-gardens.test.js') function press(game, name) { return game.onKey({ type: 'key', is: (...keys) => keys.includes(name) }) diff --git a/test/open-city-gardens.test.js b/test/open-city-gardens.test.js new file mode 100644 index 0000000..fc58e3b --- /dev/null +++ b/test/open-city-gardens.test.js @@ -0,0 +1,55 @@ +const { test } = require('brittle') +const M = require('../lib/map.js') + +function walkableComponentFromSpawn(city) { + const seen = new Set() + const queue = [[city.spawn.x, city.spawn.y]] + while (queue.length) { + const [x, y] = queue.pop() + const key = x + ',' + y + if (seen.has(key)) continue + if (x < 0 || y < 0 || x >= city.width || y >= city.height) continue + if (M.isSolid(city, x, y)) continue + seen.add(key) + queue.push([x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]) + } + return seen +} + +test('both city gardens open into the main walkable component (#5)', (t) => { + const city = M.MAPS.city + const reached = walkableComponentFromSpawn(city) + + // The exact compounds the issue measured as sealed: interiors of + // garden(5, 5, 65, 43) and garden(250, 5, 65, 43). + const gardens = [ + { x: 5, y: 5, w: 65, h: 43 }, + { x: 250, y: 5, w: 65, h: 43 } + ] + + for (const g of gardens) { + let reachedInside = 0 + for (let y = g.y + 1; y < g.y + g.h - 1; y++) { + for (let x = g.x + 1; x < g.x + g.w - 1; x++) { + if (!M.isSolid(city, x, y) && reached.has(x + ',' + y)) reachedInside++ + } + } + t.ok( + reachedInside > 1000, + `interior of garden at ${g.x},${g.y} joins the city (${reachedInside} cells reached)` + ) + } +}) + +test('every garden side has a carved gate in the hedge (#5)', (t) => { + const city = M.MAPS.city + for (const gx of [5, 250]) { + const cxg = gx + 32 + const cyg = 5 + 21 + t.is(M.isSolid(city, cxg, 5), false, 'north gate open') + t.is(M.isSolid(city, cxg - 1, 5), false) + t.is(M.isSolid(city, cxg, 47), false, 'south gate open') + t.is(M.isSolid(city, gx, cyg), false, 'west gate open') + t.is(M.isSolid(city, gx + 64, cyg), false, 'east gate open') + } +}) \ No newline at end of file