Skip to content
Game 2 Unit 4 of 16 1 hr learning time

The Keep's Hand

One bordered box was baked into the code. The keep needs many shapes, so we describe a room as data: a tile palette, and a room map you can read as a picture in the source. The reward — pillars, and a thief who walks between them.

25% of Shadowkeep

So far the room has been baked into the code. draw_hall decided "wall on the edge, floor within" by testing each cell's position — and that makes exactly one shape, a bordered box. A keep is many rooms of many shapes, and we can't write a routine for each. So this unit hands the keep a tool: a way to describe a room as data, and a single routine that lays it out whatever its shape.

This is the groundwork unit — "the keep's hand" — and it pays off the moment you run it.

What you'll see by the end

A walled hall with four bright-blue stone pillars arranged in a square on the dark slate floor, the red thief standing among them.
Four stone pillars standing in the hall — a shape no position test could draw, laid out from a readable map. The thief walks between them and stops at each, on the same collision code as Unit 3.

A pillared hall — four blocks of stone standing in the room, walls a position test could never have drawn. And the thief, who moves exactly as he did in Unit 3, now walks between them and stops when he meets one. We didn't touch the movement or collision code. We changed where the room comes from.

A palette: glyphs that name tiles

A tile engine needs a vocabulary. The palette is it — one entry per kind of cell, each pairing a glyph (the name we'll use for it), the eight bytes that draw it, and the attribute that colours it:

palette:
            defb    '.'             ; floor
            defw    floor_tile
            defb    FLOOR_ATTR
            defb    '#'             ; wall
            defw    wall_tile
            defb    WALL_ATTR

Two tiles today. To teach the keep a new kind of cell — rubble, water, a doorway — you add one line here, and it's part of the vocabulary. That's what makes this a hand and not a one-off.

A room you can read

Here's the part worth pausing on. The room is twenty-four rows of thirty-two glyphs — and because the glyphs are # for wall and . for floor, the room is a picture in the source code:

room_data:
            defb    "################################"
            defb    "#..............................#"
            defb    "#..............................#"
            ; ...
            defb    "#........##..........##........#"
            defb    "#........##..........##........#"
            ; ...
            defb    "################################"

You can see the hall: the ring of wall, the pairs of ## that are the pillars. Designing a room is editing this picture. No coordinates, no maths — draw it, and the keep draws what you drew. (It costs 768 bytes a room, laid out plainly; later, when the keep has many rooms, we can pack them tighter. Readable first.)

Drawing from the map

draw_room walks the map cell by cell. For each glyph it asks the palette "what tile is this?", then draws it:

draw_room:
            ld      hl, room_data
            ld      (map_ptr), hl
            ; for each of 24 rows, 32 columns:
            ld      a, (hl)          ; the glyph here
            call    lookup_tile      ; glyph -> tile_ptr, tile_attr
            call    draw_tile        ; draw it at (row, col)
            ; ... advance map pointer, column, row ...

And lookup_tile is just a walk down the palette until the glyph matches, copying out that entry's pointer and attribute:

lookup_tile:
            ld      hl, palette
.scan:
            cp      (hl)             ; this entry's glyph?
            jr      z, .found
            inc     hl               ; skip glyph + pointer + attribute
            inc     hl
            inc     hl
            inc     hl
            jr      .scan
.found:
            inc     hl               ; -> the pointer, then the attribute
            ; ... copy into tile_ptr / tile_attr ...

draw_tile itself is unchanged from Unit 2. We've only changed what tells it where the walls go: a picture, not a position test.

Collision came along for free

The pillars are walls, and the thief stops at them — yet player_step and wall_at are byte-for-byte what they were in Unit 3. That's the quiet dividend of "a wall is known by its light": pillars are drawn with WALL_ATTR, so they're BRIGHT, so wall_at already calls them solid. Interior walls, edge walls — the collision never knew the difference. Build the room from data and the rules that were already there just work on the new shape.

Milestone — describe the room as data

We replace the position-testing draw_hall with a palette (each glyph names a tile) and a room_data map you can read as a picture, walked by draw_room / lookup_tile. draw_tile, player_step and wall_at are untouched — only the source of the room changed.

Step 1: a tile palette and a readable room map drive the room
+135-81
11 ; Shadowkeep — Unit 4: The Keep's Hand
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-00 = Unit 3's end: the thief walking the hand-coded bordered hall.
3+; step-01 describes the room as data — a tile palette and a readable map — and draws pillars.
44
55 org 32768
66
77 WALL_ATTR equ %01001000 ; BRIGHT — lit stone (solid)
88 FLOOR_ATTR equ %00001000 ; dim — dark slate (walkable)
99 THIEF equ %01001010 ; BRIGHT, PAPER 1 (blue), INK 2 (red)
10-WALL_BIT equ 6 ; BRIGHT marks solid stone
10+WALL_BIT equ 6
1111
1212 START_COL equ 15
1313 START_ROW equ 11
14-LAST_ROW equ 23
15-LAST_COL equ 31
1614
17-KEYS_OP equ $DFFE ; O = bit 1 (left), P = bit 0 (right)
18-KEYS_Q equ $FBFE ; Q = bit 0 (up)
19-KEYS_A equ $FDFE ; A = bit 0 (down)
15+KEYS_OP equ $DFFE
16+KEYS_Q equ $FBFE
17+KEYS_A equ $FDFE
2018
2119 ; ----------------------------------------------------------------------------
22-; SETUP — build the hall, save the floor under the start cell, draw the thief,
23-; then run the frame-locked loop, stepping him once per frame.
20+; SETUP.
2421 ; ----------------------------------------------------------------------------
2522 start:
2623 ld a, 0
...
3128 ld a, START_ROW
3229 ld (thief_row), a
3330
34- call draw_hall
31+ call draw_room
3532 call save_under
3633 call draw_thief
3734
...
4340 jr .loop
4441
4542 ; ----------------------------------------------------------------------------
46-; player_step — read a direction, test the target cell, and move only if it's
47-; not a wall. Pure Gloaming, pointed at the thief.
43+; draw_room — walk the map cell by cell, look each glyph up in the palette,
44+; draw the tile it names. The map pointer is kept in a variable because
45+; draw_tile uses HL for itself.
46+; ----------------------------------------------------------------------------
47+draw_room:
48+ ld hl, room_data
49+ ld (map_ptr), hl
50+ ld b, 0
51+.room_row:
52+ ld c, 0
53+.room_col:
54+ ld hl, (map_ptr)
55+ ld a, (hl) ; the glyph for this cell
56+ call lookup_tile ; -> tile_ptr, tile_attr
57+ call draw_tile ; draws at (B, C); preserves BC
58+ ld hl, (map_ptr)
59+ inc hl
60+ ld (map_ptr), hl
61+ inc c
62+ ld a, c
63+ cp 32
64+ jr nz, .room_col
65+ inc b
66+ ld a, b
67+ cp 24
68+ jr nz, .room_row
69+ ret
70+
71+; lookup_tile — glyph in A. Scan the palette for a matching glyph and copy its
72+; tile pointer and attribute into tile_ptr / tile_attr. Every glyph the map uses
73+; must exist in the palette.
74+lookup_tile:
75+ ld hl, palette
76+.scan:
77+ cp (hl) ; glyph match?
78+ jr z, .found
79+ inc hl ; step over glyph + pointer + attribute
80+ inc hl
81+ inc hl
82+ inc hl
83+ jr .scan
84+.found:
85+ inc hl ; -> pointer low byte
86+ ld e, (hl)
87+ inc hl
88+ ld d, (hl)
89+ ld (tile_ptr), de
90+ inc hl
91+ ld a, (hl)
92+ ld (tile_attr), a
93+ ret
94+
95+; ----------------------------------------------------------------------------
96+; draw_tile — colour cell (B,C), then lay its eight bitmap rows.
97+; ----------------------------------------------------------------------------
98+draw_tile:
99+ push bc
100+ call attr_addr_cr
101+ ld a, (tile_attr)
102+ ld (hl), a
103+ call scr_addr_cr
104+ ld de, (tile_ptr)
105+ ld b, 8
106+.tile_row:
107+ ld a, (de)
108+ ld (hl), a
109+ inc de
110+ inc h
111+ djnz .tile_row
112+ pop bc
113+ ret
114+
115+; ----------------------------------------------------------------------------
116+; player_step / wall_at — unchanged from Unit 3. Collision is still "is the
117+; target cell BRIGHT?", which now stops the thief at pillars as well as edges.
48118 ; ----------------------------------------------------------------------------
49119 player_step:
50120 ld a, (thief_col)
...
67137 bit 0, a
68138 jr z, .down
69139 ret
70-
71140 .left:
72141 ld hl, tcol
73142 dec (hl)
...
89158 ld a, (tcol)
90159 ld c, a
91160 call wall_at
92- ret nz ; a wall — stay put
161+ ret nz
93162
94- call restore_under ; put the floor back where he was
163+ call restore_under
95164 ld a, (tcol)
96165 ld (thief_col), a
97166 ld a, (trow)
98167 ld (thief_row), a
99- call save_under ; remember the floor he's stepping onto
168+ call save_under
100169 call draw_thief
101170 ret
102171
103-; wall_at — row in B, column in C. Z set (walkable) if the cell isn't BRIGHT.
104172 wall_at:
105173 call attr_addr_cr
106174 bit WALL_BIT, (hl)
107175 ret
108176
109177 ; ----------------------------------------------------------------------------
110-; The thief's save / restore / draw — Gloaming's, renamed. The nine-byte buffer
111-; holds the eight bitmap rows of dithered stone plus the floor's attribute.
178+; Save / restore / draw the thief — unchanged from Unit 3.
112179 ; ----------------------------------------------------------------------------
113180 pos_bc:
114181 ld a, (thief_row)
...
165232 inc de
166233 inc h
167234 djnz .thief_row
168- ret
169-
170-; ----------------------------------------------------------------------------
171-; draw_hall / pick_tile / draw_tile — unchanged from Unit 2.
172-; ----------------------------------------------------------------------------
173-draw_hall:
174- ld b, 0
175-.hall_row:
176- ld c, 0
177-.hall_col:
178- call pick_tile
179- call draw_tile
180- inc c
181- ld a, c
182- cp LAST_COL + 1
183- jr nz, .hall_col
184- inc b
185- ld a, b
186- cp LAST_ROW + 1
187- jr nz, .hall_row
188- ret
189-
190-pick_tile:
191- ld a, b
192- or a
193- jr z, .wall
194- cp LAST_ROW
195- jr z, .wall
196- ld a, c
197- or a
198- jr z, .wall
199- cp LAST_COL
200- jr z, .wall
201-.floor:
202- ld hl, floor_tile
203- ld (tile_ptr), hl
204- ld a, FLOOR_ATTR
205- ld (tile_attr), a
206- ret
207-.wall:
208- ld hl, wall_tile
209- ld (tile_ptr), hl
210- ld a, WALL_ATTR
211- ld (tile_attr), a
212- ret
213-
214-draw_tile:
215- push bc
216- call attr_addr_cr
217- ld a, (tile_attr)
218- ld (hl), a
219- call scr_addr_cr
220- ld de, (tile_ptr)
221- ld b, 8
222-.tile_row:
223- ld a, (de)
224- ld (hl), a
225- inc de
226- inc h
227- djnz .tile_row
228- pop bc
229235 ret
230236
231237 ; ----------------------------------------------------------------------------
...
263269 ret
264270
265271 ; ----------------------------------------------------------------------------
266-; Data.
272+; The palette — one entry per kind of tile: glyph, eight-byte pattern, colour.
273+; To give the keep a new sort of cell, add a line here and use its glyph below.
274+; entry = glyph (1) + tile pointer (2) + attribute (1)
275+; ----------------------------------------------------------------------------
276+palette:
277+ defb '.'
278+ defw floor_tile
279+ defb FLOOR_ATTR
280+ defb '#'
281+ defw wall_tile
282+ defb WALL_ATTR
283+
284+; ----------------------------------------------------------------------------
285+; The room — read it like a picture. '#' is lit wall, '.' is dark floor. The
286+; outer ring walls the hall; the four blocks are pillars the thief walks around.
287+; ----------------------------------------------------------------------------
288+room_data:
289+ defb "################################"
290+ defb "#..............................#"
291+ defb "#..............................#"
292+ defb "#..............................#"
293+ defb "#..............................#"
294+ defb "#..............................#"
295+ defb "#..............................#"
296+ defb "#..............................#"
297+ defb "#........##..........##........#"
298+ defb "#........##..........##........#"
299+ defb "#..............................#"
300+ defb "#..............................#"
301+ defb "#..............................#"
302+ defb "#..............................#"
303+ defb "#........##..........##........#"
304+ defb "#........##..........##........#"
305+ defb "#..............................#"
306+ defb "#..............................#"
307+ defb "#..............................#"
308+ defb "#..............................#"
309+ defb "#..............................#"
310+ defb "#..............................#"
311+ defb "#..............................#"
312+ defb "################################"
313+
314+; ----------------------------------------------------------------------------
315+; Tiles and the thief.
267316 ; ----------------------------------------------------------------------------
268317 floor_tile:
269318 defb %10101010
...
295344 defb %00111100
296345 defb %00100100
297346
347+; ----------------------------------------------------------------------------
348+; Variables.
349+; ----------------------------------------------------------------------------
298350 thief_col:
299351 defb START_COL
300352 thief_row:
...
303355 defb 0
304356 trow:
305357 defb 0
358+map_ptr:
359+ defw 0
306360 tile_ptr:
307361 defw 0
308362 tile_attr:
The complete program
; Shadowkeep — Unit 4: The Keep's Hand
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 describes the room as data — a tile palette and a readable map — and draws pillars.

            org     32768

WALL_ATTR   equ     %01001000       ; BRIGHT — lit stone (solid)
FLOOR_ATTR  equ     %00001000       ; dim — dark slate (walkable)
THIEF       equ     %01001010       ; BRIGHT, PAPER 1 (blue), INK 2 (red)
WALL_BIT    equ     6

START_COL   equ     15
START_ROW   equ     11

KEYS_OP     equ     $DFFE
KEYS_Q      equ     $FBFE
KEYS_A      equ     $FDFE

; ----------------------------------------------------------------------------
; SETUP.
; ----------------------------------------------------------------------------
start:
            ld      a, 0
            out     ($FE), a

            ld      a, START_COL
            ld      (thief_col), a
            ld      a, START_ROW
            ld      (thief_row), a

            call    draw_room
            call    save_under
            call    draw_thief

            im      1
            ei
.loop:
            halt
            call    player_step
            jr      .loop

; ----------------------------------------------------------------------------
; draw_room — walk the map cell by cell, look each glyph up in the palette,
; draw the tile it names. The map pointer is kept in a variable because
; draw_tile uses HL for itself.
; ----------------------------------------------------------------------------
draw_room:
            ld      hl, room_data
            ld      (map_ptr), hl
            ld      b, 0
.room_row:
            ld      c, 0
.room_col:
            ld      hl, (map_ptr)
            ld      a, (hl)          ; the glyph for this cell
            call    lookup_tile      ; -> tile_ptr, tile_attr
            call    draw_tile        ; draws at (B, C); preserves BC
            ld      hl, (map_ptr)
            inc     hl
            ld      (map_ptr), hl
            inc     c
            ld      a, c
            cp      32
            jr      nz, .room_col
            inc     b
            ld      a, b
            cp      24
            jr      nz, .room_row
            ret

; lookup_tile — glyph in A. Scan the palette for a matching glyph and copy its
; tile pointer and attribute into tile_ptr / tile_attr. Every glyph the map uses
; must exist in the palette.
lookup_tile:
            ld      hl, palette
.scan:
            cp      (hl)             ; glyph match?
            jr      z, .found
            inc     hl               ; step over glyph + pointer + attribute
            inc     hl
            inc     hl
            inc     hl
            jr      .scan
.found:
            inc     hl               ; -> pointer low byte
            ld      e, (hl)
            inc     hl
            ld      d, (hl)
            ld      (tile_ptr), de
            inc     hl
            ld      a, (hl)
            ld      (tile_attr), a
            ret

; ----------------------------------------------------------------------------
; draw_tile — colour cell (B,C), then lay its eight bitmap rows.
; ----------------------------------------------------------------------------
draw_tile:
            push    bc
            call    attr_addr_cr
            ld      a, (tile_attr)
            ld      (hl), a
            call    scr_addr_cr
            ld      de, (tile_ptr)
            ld      b, 8
.tile_row:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .tile_row
            pop     bc
            ret

; ----------------------------------------------------------------------------
; player_step / wall_at — unchanged from Unit 3. Collision is still "is the
; target cell BRIGHT?", which now stops the thief at pillars as well as edges.
; ----------------------------------------------------------------------------
player_step:
            ld      a, (thief_col)
            ld      (tcol), a
            ld      a, (thief_row)
            ld      (trow), a

            ld      bc, KEYS_OP
            in      a, (c)
            bit     1, a
            jr      z, .left
            bit     0, a
            jr      z, .right
            ld      bc, KEYS_Q
            in      a, (c)
            bit     0, a
            jr      z, .up
            ld      bc, KEYS_A
            in      a, (c)
            bit     0, a
            jr      z, .down
            ret
.left:
            ld      hl, tcol
            dec     (hl)
            jr      .move
.right:
            ld      hl, tcol
            inc     (hl)
            jr      .move
.up:
            ld      hl, trow
            dec     (hl)
            jr      .move
.down:
            ld      hl, trow
            inc     (hl)
.move:
            ld      a, (trow)
            ld      b, a
            ld      a, (tcol)
            ld      c, a
            call    wall_at
            ret     nz

            call    restore_under
            ld      a, (tcol)
            ld      (thief_col), a
            ld      a, (trow)
            ld      (thief_row), a
            call    save_under
            call    draw_thief
            ret

wall_at:
            call    attr_addr_cr
            bit     WALL_BIT, (hl)
            ret

; ----------------------------------------------------------------------------
; Save / restore / draw the thief — unchanged from Unit 3.
; ----------------------------------------------------------------------------
pos_bc:
            ld      a, (thief_row)
            ld      b, a
            ld      a, (thief_col)
            ld      c, a
            ret

save_under:
            call    pos_bc
            call    scr_addr_cr
            ld      de, under_thief
            ld      b, 8
.save_row:
            ld      a, (hl)
            ld      (de), a
            inc     de
            inc     h
            djnz    .save_row
            call    pos_bc
            call    attr_addr_cr
            ld      a, (hl)
            ld      (under_thief + 8), a
            ret

restore_under:
            call    pos_bc
            call    scr_addr_cr
            ld      de, under_thief
            ld      b, 8
.restore_row:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .restore_row
            call    pos_bc
            call    attr_addr_cr
            ld      a, (under_thief + 8)
            ld      (hl), a
            ret

draw_thief:
            call    pos_bc
            call    attr_addr_cr
            ld      (hl), THIEF
            call    pos_bc
            call    scr_addr_cr
            ld      de, thief
            ld      b, 8
.thief_row:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .thief_row
            ret

; ----------------------------------------------------------------------------
; scr_addr_cr / attr_addr_cr — carried from Gloaming.
; ----------------------------------------------------------------------------
scr_addr_cr:
            ld      a, b
            and     %00011000
            or      %01000000
            ld      h, a
            ld      a, b
            and     %00000111
            rrca
            rrca
            rrca
            or      c
            ld      l, a
            ret

attr_addr_cr:
            ld      a, b
            ld      l, a
            ld      h, 0
            add     hl, hl
            add     hl, hl
            add     hl, hl
            add     hl, hl
            add     hl, hl
            ld      de, $5800
            add     hl, de
            ld      a, c
            ld      e, a
            ld      d, 0
            add     hl, de
            ret

; ----------------------------------------------------------------------------
; The palette — one entry per kind of tile: glyph, eight-byte pattern, colour.
; To give the keep a new sort of cell, add a line here and use its glyph below.
;   entry = glyph (1) + tile pointer (2) + attribute (1)
; ----------------------------------------------------------------------------
palette:
            defb    '.'
            defw    floor_tile
            defb    FLOOR_ATTR
            defb    '#'
            defw    wall_tile
            defb    WALL_ATTR

; ----------------------------------------------------------------------------
; The room — read it like a picture. '#' is lit wall, '.' is dark floor. The
; outer ring walls the hall; the four blocks are pillars the thief walks around.
; ----------------------------------------------------------------------------
room_data:
            defb    "################################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#........##..........##........#"
            defb    "#........##..........##........#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#........##..........##........#"
            defb    "#........##..........##........#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "################################"

; ----------------------------------------------------------------------------
; Tiles and the thief.
; ----------------------------------------------------------------------------
floor_tile:
            defb    %10101010
            defb    %01010101
            defb    %10101010
            defb    %01010101
            defb    %10101010
            defb    %01010101
            defb    %10101010
            defb    %01010101

wall_tile:
            defb    %00010001
            defb    %00000000
            defb    %01000100
            defb    %00000000
            defb    %00010001
            defb    %00000000
            defb    %01000100
            defb    %00000000

thief:
            defb    %00011000
            defb    %00111100
            defb    %01111110
            defb    %01111110
            defb    %01111110
            defb    %01111110
            defb    %00111100
            defb    %00100100

; ----------------------------------------------------------------------------
; Variables.
; ----------------------------------------------------------------------------
thief_col:
            defb    START_COL
thief_row:
            defb    START_ROW
tcol:
            defb    0
trow:
            defb    0
map_ptr:
            defw    0
tile_ptr:
            defw    0
tile_attr:
            defb    0
under_thief:
            defb    0, 0, 0, 0, 0, 0, 0, 0, 0

            end     start

Walk the thief around the pillars with Q A O P. He threads between them and stops at every face of stone — the hall is a real space now, not an empty box.

Try this: redraw the hall

Open room_data and draw a different room. Move the pillars; make a long thin gallery by walling off the middle; carve an alcove into the top wall by turning a run of # into .. Reassemble and walk it. You're designing levels by editing a picture — the most direct level editor there is.

Try this: a third kind of stone

Add a rubble_tile (a denser dither than the floor) and a palette entry naming it :. Now scatter a few : glyphs across the floor in room_data — a drift of broken stone. One new tile, one new glyph, and the keep has a new texture you place by typing. That's the palette earning its keep.

Try this: a doorway that goes nowhere (yet)

Turn one # in an outer wall into a .. The wall now has a gap — a doorway. Walk the thief up to it and through... and off the edge of the room into whatever lies beyond. He doesn't come back, because there's no "next room" yet. That gap is exactly where Unit 6 will hang a flick-screen transition.

When it's wrong, see why

  • The room is all one tile. lookup_tile isn't matching glyphs — check the palette entries are glyph / defw pointer / defb attribute, in that order, four bytes each.
  • The screen is garbage or the machine hangs. A glyph in room_data isn't in the palette, so lookup_tile scans off the end of it forever. Every glyph in the map must have a palette entry (here, only # and .).
  • The room is shifted or wraps. Each room_data row must be exactly 32 characters. A row one short or long slides every row after it sideways.
  • The thief starts inside a pillar. START_COL / START_ROW must land on a . in the map. (15, 11) is the open centre; if you redraw the room, keep his start clear.
  • Pillars don't stop him. They must be drawn with WALL_ATTR (BRIGHT). If you gave a "wall" glyph a dim attribute, wall_at won't see it as solid.

Before and after

You started with a room baked into a routine — one shape, a bordered box — and finished with the keep's first real tool: a room drawn from a map you can read and redraw as a picture. The drawing routine never learned a new trick; you gave it a vocabulary (the palette) and a script (the map), and it laid out pillars it could never have been coded for. And collision came along untouched, because solidity was already read from the tile. This is the groundwork the whole keep is built on: rooms as data, laid by one hand.

What you've learnt

  • A room is data, not code. One draw_room lays out any shape; the shape lives in a map you can change without touching the routine.
  • A palette is a vocabulary. Each glyph names a tile — pattern plus colour — and new kinds of cell are one line away.
  • Readable data is a tool. A map written as a picture is a level editor you already know how to use: a text editor.
  • Good structure makes new things free. Collision worked on pillars with no change, because solidity was read from the tile, and the tile came from the map.

What's next

One room is a picture; a keep is many pictures, and a sense of which lies through each door. In Unit 5, "The Room Graph," we give the keep more than one room and the data that says how they join — which room waits beyond the north wall, the south, the east, the west. It's the map of the map: the structure that, in Unit 6, lets the thief step through a doorway and arrive somewhere new.