Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion lib/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const { style } = require('bare-tui')
const { makeMovingHeroSprite } = require('./sprites.js')
const { WORLD_BOSS } = require('./world-boss.js')
const { bossCamera } = require('./world-boss-event.js')
const { isSolid } = require('./map.js')

/** Smallest terminal the split layout is designed for. */
const MIN_WIDTH = 64
Expand Down Expand Up @@ -798,6 +799,10 @@ function mapPane(map, w, h, opts = {}) {
: sprite.length - 1
const top = hy - clamp(heroAnchorY, 0, sprite.length - 1)

// isSolid reads the model shape ({ rows, width, height }); this pane gets
// pre-render data ({ tiles }), so adapt the view once per frame (#16).
const solidView = { rows: tiles, width: mapW, height: mapH }
Comment thread
gitar-bot[bot] marked this conversation as resolved.

for (let sy = 0; sy < sprite.length; sy++) {
const line = ascii(sprite[sy]).padEnd(spriteW)
const first = line.search(/\S/)
Expand All @@ -806,7 +811,14 @@ function mapPane(map, w, h, opts = {}) {
while (last > first && line[last] === ' ') last--
for (let sx = first; sx <= last; sx++) {
if (line[sx] === ' ') continue
const cell = top + sy + ',' + (left + sx)
const wx = left + sx
const wy = top + sy
// The body hangs above the feet and can reach over a wall or past the
// map edge. A hero glyph on a solid tile reads as a hole in that wall,
// so those cells stay untouched (#16). With no tiles at all there is
// nothing to clip against and the fallback keeps rendering whole.
if (tiles.length > 0 && isSolid(solidView, wx, wy)) continue
const cell = wy + ',' + wx
over.set(cell, line[sx])
heroCells.add(cell)
}
Expand Down
44 changes: 44 additions & 0 deletions test/hero-clip.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { test } = require('brittle')
const { style } = require('bare-tui')
const render = require('../lib/render.js')

test('the hero never paints through a wall (#16)', (t) => {
// Dungeon-style arrival: the three-row hero stands with its feet two rows
// below a full wall, like arriving at (3,2) in the issue. The head row of
// the sprite lands on the wall and must be clipped away.
const tiles = ['######', '#....#', '#....#', '######']
const pane = style.stripAnsi(
render.mapPane({ tiles, actors: [], hero: { x: 3, y: 2, sprite: [' O ', '/|\\', '/ \\'] } }, 24, 12, {
cellW: 1
})
)

t.ok(pane.includes('######'), 'the top wall stays a continuous wall')
t.ok(!pane.includes('O'), 'the head is clipped instead of punching a hole in the wall')
t.ok(pane.includes('|'), 'the torso still shows on the free row below')
})

test('a hero arriving at the top edge loses only the off-map rows (#16)', (t) => {
// Field arrival at (40,1): the upper body reaches past row 0. Off-map cells
// read as solid, so they get skipped, while the feet stay put.
const tiles = ['........', '........', '########']
const pane = style.stripAnsi(
render.mapPane({ tiles, actors: [], hero: { x: 4, y: 0, sprite: ['O', '|', '^'] } }, 20, 8, {
cellW: 1
})
)

t.ok(!pane.includes('O'), 'nothing is painted above the map')
t.ok(pane.includes('^'), 'the feet remain exactly where the walker stands')
t.ok(style.stripAnsi(render.mapPane({ tiles, actors: [], hero: { x: 4, y: 0 } }, 20, 8)).includes('@'), 'glyph heroes keep rendering')
})

test('a hero without map data keeps rendering (fallback path unchanged)', (t) => {
const pane = style.stripAnsi(
render.mapPane({ tiles: [], actors: [], hero: { x: 5, y: 5, sprite: ['O', '|', '^'] } }, 20, 8, {
cellW: 1
})
)

t.ok(pane.includes('O'), 'with no tiles there is nothing solid to clip against')
})
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const {
const render = require('../lib/render.js')

require('./sage.test.js')
require('./hero-clip.test.js')

function press(game, name) {
return game.onKey({ type: 'key', is: (...keys) => keys.includes(name) })
Expand Down
Loading