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.
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 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.
| 1 | 1 | ; Shadowkeep — Unit 4: The Keep's Hand | |
| 2 | 2 | ; 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. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| 7 | 7 | WALL_ATTR equ %01001000 ; BRIGHT — lit stone (solid) | |
| 8 | 8 | FLOOR_ATTR equ %00001000 ; dim — dark slate (walkable) | |
| 9 | 9 | THIEF equ %01001010 ; BRIGHT, PAPER 1 (blue), INK 2 (red) | |
| 10 | - | WALL_BIT equ 6 ; BRIGHT marks solid stone | |
| 10 | + | WALL_BIT equ 6 | |
| 11 | 11 | | |
| 12 | 12 | START_COL equ 15 | |
| 13 | 13 | START_ROW equ 11 | |
| 14 | - | LAST_ROW equ 23 | |
| 15 | - | LAST_COL equ 31 | |
| 16 | 14 | | |
| 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 | |
| 20 | 18 | | |
| 21 | 19 | ; ---------------------------------------------------------------------------- | |
| 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. | |
| 24 | 21 | ; ---------------------------------------------------------------------------- | |
| 25 | 22 | start: | |
| 26 | 23 | ld a, 0 | |
| ... | |||
| 31 | 28 | ld a, START_ROW | |
| 32 | 29 | ld (thief_row), a | |
| 33 | 30 | | |
| 34 | - | call draw_hall | |
| 31 | + | call draw_room | |
| 35 | 32 | call save_under | |
| 36 | 33 | call draw_thief | |
| 37 | 34 | | |
| ... | |||
| 43 | 40 | jr .loop | |
| 44 | 41 | | |
| 45 | 42 | ; ---------------------------------------------------------------------------- | |
| 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. | |
| 48 | 118 | ; ---------------------------------------------------------------------------- | |
| 49 | 119 | player_step: | |
| 50 | 120 | ld a, (thief_col) | |
| ... | |||
| 67 | 137 | bit 0, a | |
| 68 | 138 | jr z, .down | |
| 69 | 139 | ret | |
| 70 | - | | |
| 71 | 140 | .left: | |
| 72 | 141 | ld hl, tcol | |
| 73 | 142 | dec (hl) | |
| ... | |||
| 89 | 158 | ld a, (tcol) | |
| 90 | 159 | ld c, a | |
| 91 | 160 | call wall_at | |
| 92 | - | ret nz ; a wall — stay put | |
| 161 | + | ret nz | |
| 93 | 162 | | |
| 94 | - | call restore_under ; put the floor back where he was | |
| 163 | + | call restore_under | |
| 95 | 164 | ld a, (tcol) | |
| 96 | 165 | ld (thief_col), a | |
| 97 | 166 | ld a, (trow) | |
| 98 | 167 | ld (thief_row), a | |
| 99 | - | call save_under ; remember the floor he's stepping onto | |
| 168 | + | call save_under | |
| 100 | 169 | call draw_thief | |
| 101 | 170 | ret | |
| 102 | 171 | | |
| 103 | - | ; wall_at — row in B, column in C. Z set (walkable) if the cell isn't BRIGHT. | |
| 104 | 172 | wall_at: | |
| 105 | 173 | call attr_addr_cr | |
| 106 | 174 | bit WALL_BIT, (hl) | |
| 107 | 175 | ret | |
| 108 | 176 | | |
| 109 | 177 | ; ---------------------------------------------------------------------------- | |
| 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. | |
| 112 | 179 | ; ---------------------------------------------------------------------------- | |
| 113 | 180 | pos_bc: | |
| 114 | 181 | ld a, (thief_row) | |
| ... | |||
| 165 | 232 | inc de | |
| 166 | 233 | inc h | |
| 167 | 234 | 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 | |
| 229 | 235 | ret | |
| 230 | 236 | | |
| 231 | 237 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 263 | 269 | ret | |
| 264 | 270 | | |
| 265 | 271 | ; ---------------------------------------------------------------------------- | |
| 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. | |
| 267 | 316 | ; ---------------------------------------------------------------------------- | |
| 268 | 317 | floor_tile: | |
| 269 | 318 | defb %10101010 | |
| ... | |||
| 295 | 344 | defb %00111100 | |
| 296 | 345 | defb %00100100 | |
| 297 | 346 | | |
| 347 | + | ; ---------------------------------------------------------------------------- | |
| 348 | + | ; Variables. | |
| 349 | + | ; ---------------------------------------------------------------------------- | |
| 298 | 350 | thief_col: | |
| 299 | 351 | defb START_COL | |
| 300 | 352 | thief_row: | |
| ... | |||
| 303 | 355 | defb 0 | |
| 304 | 356 | trow: | |
| 305 | 357 | defb 0 | |
| 358 | + | map_ptr: | |
| 359 | + | defw 0 | |
| 306 | 360 | tile_ptr: | |
| 307 | 361 | defw 0 | |
| 308 | 362 | 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_tileisn't matching glyphs — check the palette entries are glyph /defwpointer /defbattribute, in that order, four bytes each. - The screen is garbage or the machine hangs. A glyph in
room_dataisn't in the palette, solookup_tilescans 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_datarow 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_ROWmust 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_atwon'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_roomlays 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.