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

The Room Graph

One room is a picture; a keep is many pictures and a sense of which lies through each door. Build the room table — each room's map plus its four edge-links — and let the thief walk through a doorway into a second room.

31% of Shadowkeep

A keep is more than one room, and the rooms know each other: north of this hall lies that one, east lies another. This unit gives the keep that structure — a graph of rooms — and lets the thief travel it. It's the map of the map: the data that says how the pictures from Unit 4 join up.

What you'll see by the end

A second, plainer chamber with a single block near the top and a doorway gap in its west wall; the red thief stands in it, having walked east out of the pillared hall and into here.
The second chamber — reached by walking east out of the pillared hall. A block of its own, a doorway back west, and the thief arrived. The keep has somewhere else in it now.

Walk the thief east, out through the doorway in the pillared hall's right-hand wall, and the screen flicks to a new room — a plainer chamber with a block of its own and a doorway back the way you came. Walk west and you return. Two rooms, joined, and a thief who travels between them. The keep has become a place with elsewhere in it.

A room, and its neighbours

In Unit 4 a room was a map pointer. Now each room is a small table entry: that map pointer, followed by four links — the room that lies North, South, East and West — or $FF for "solid wall, no way through":

rooms:
            defw    room0_data
            defb    NO_EXIT, NO_EXIT, 1, NO_EXIT   ; east -> room 1
            defw    room1_data
            defb    NO_EXIT, NO_EXIT, NO_EXIT, 0   ; west -> room 0

Two rooms, joined east-west. Room 0's east link is 1; room 1's west link is 0. That pair of numbers is the connection — the whole graph in eight bytes. A current_room byte says which one we're in, and draw_room now draws whichever that is, fetching its map from the table:

room_entry_addr:                ; HL = rooms + current_room * 6
            ld      a, (current_room)
            ; ... * 6 (each entry is a 2-byte pointer + 4 link bytes) ...
            ld      de, rooms
            add     hl, de
            ret

A doorway is a gap

How does the thief leave? Through a doorway — and a doorway is nothing more than a . where the wall would be a #. Room 0's east wall has one floor cell, at row 11; room 1's west wall has one too. Because a doorway is dim floor, not bright wall, the thief can walk onto it — collision already lets him.

The moment he stands on an edge cell, we look up that edge's link and travel:

check_exit:
            ld      a, (thief_col)
            or      a
            jr      z, .west        ; on column 0?
            cp      31
            jr      z, .east        ; on column 31?
            ; ... row 0 -> north, row 23 -> south ...
            ret                     ; not on an edge

Each branch reads its link from the room table and, if it isn't $FF, travels there:

.travel:
            cp      NO_EXIT
            ret     z               ; a doorway to nowhere — stay
            ld      (current_room), a
            call    draw_room        ; repaint the whole screen as the new room
            ld      a, START_COL     ; drop him in the middle (for now)
            ld      (thief_col), a
            ; ...

The flick, and an honest seam

Travelling repaints the entire screen — all 768 cells — as the new room. That repaint takes a handful of frames, so you see it happen: a brief flick as the keep redraws. That's not a flaw to hide; it's the sound of the genre. Atic Atac, Sabre Wulf, Knight Lore — every flick-screen game paused to redraw as you crossed a threshold. Ours is in good company.

There's one rough edge, though, and it's deliberate. When the thief travels, we drop him in the centre of the new room — a small teleport. Walk east through a door and you ought to arrive at the west edge of the next room, mid-stride, as if you'd stepped through. Right now you appear in the middle instead. It works — you can roam the whole keep — but it doesn't yet feel like a doorway. This unit is the map; making the step feel like a step is the next one.

Milestone — a graph of rooms

We turn the single map into a room table — each entry a map pointer plus four links (N/S/E/W, or NO_EXIT) — add a current_room, and have check_exit read the link of whatever edge the thief stands on and travel there, repainting the screen as the new room.

Step 1: a room table with edge-links, and travel through a doorway
+135-33
11 ; Shadowkeep — Unit 5: The Room Graph
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-00 = Unit 4's end: the thief in a single data-driven pillared hall.
3+; step-01 gives the keep a graph of rooms — walk a doorway, flick to the next.
44
55 org 32768
66
7-WALL_ATTR equ %01001000 ; BRIGHT — lit stone (solid)
8-FLOOR_ATTR equ %00001000 ; dim — dark slate (walkable)
9-THIEF equ %01001010 ; BRIGHT, PAPER 1 (blue), INK 2 (red)
7+WALL_ATTR equ %01001000
8+FLOOR_ATTR equ %00001000
9+THIEF equ %01001010
1010 WALL_BIT equ 6
1111
1212 START_COL equ 15
1313 START_ROW equ 11
14+NO_EXIT equ $FF
1415
1516 KEYS_OP equ $DFFE
1617 KEYS_Q equ $FBFE
...
2324 ld a, 0
2425 out ($FE), a
2526
27+ xor a
28+ ld (current_room), a
2629 ld a, START_COL
2730 ld (thief_col), a
2831 ld a, START_ROW
...
4043 jr .loop
4144
4245 ; ----------------------------------------------------------------------------
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+; room_entry_addr — HL = rooms + current_room * 6 (entry = map ptr + 4 links).
47+; ----------------------------------------------------------------------------
48+room_entry_addr:
49+ ld a, (current_room)
50+ ld l, a
51+ ld h, 0
52+ ld d, h
53+ ld e, l ; DE = room number
54+ add hl, hl ; *2
55+ add hl, de ; *3
56+ add hl, hl ; *6
57+ ld de, rooms
58+ add hl, de
59+ ret
60+
61+; ----------------------------------------------------------------------------
62+; draw_room — draw the current room's map. Same walk as Unit 4, but the map
63+; pointer comes from the room table now, not a fixed label.
4664 ; ----------------------------------------------------------------------------
4765 draw_room:
48- ld hl, room_data
66+ call room_entry_addr
67+ ld a, (hl) ; map pointer, low
68+ inc hl
69+ ld h, (hl) ; map pointer, high
70+ ld l, a
4971 ld (map_ptr), hl
5072 ld b, 0
5173 .room_row:
5274 ld c, 0
5375 .room_col:
5476 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
77+ ld a, (hl)
78+ call lookup_tile
79+ call draw_tile
5880 ld hl, (map_ptr)
5981 inc hl
6082 ld (map_ptr), hl
...
6890 jr nz, .room_row
6991 ret
7092
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.
7493 lookup_tile:
7594 ld hl, palette
7695 .scan:
77- cp (hl) ; glyph match?
96+ cp (hl)
7897 jr z, .found
79- inc hl ; step over glyph + pointer + attribute
98+ inc hl
8099 inc hl
81100 inc hl
82101 inc hl
83102 jr .scan
84103 .found:
85- inc hl ; -> pointer low byte
104+ inc hl
86105 ld e, (hl)
87106 inc hl
88107 ld d, (hl)
...
92111 ld (tile_attr), a
93112 ret
94113
95-; ----------------------------------------------------------------------------
96-; draw_tile — colour cell (B,C), then lay its eight bitmap rows.
97-; ----------------------------------------------------------------------------
98114 draw_tile:
99115 push bc
100116 call attr_addr_cr
...
113129 ret
114130
115131 ; ----------------------------------------------------------------------------
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.
132+; player_step — move as before, then check whether he stepped onto a doorway.
118133 ; ----------------------------------------------------------------------------
119134 player_step:
120135 ld a, (thief_col)
...
167182 ld (thief_row), a
168183 call save_under
169184 call draw_thief
185+ call check_exit
170186 ret
171187
172188 wall_at:
...
175191 ret
176192
177193 ; ----------------------------------------------------------------------------
178-; Save / restore / draw the thief — unchanged from Unit 3.
194+; check_exit — if the thief is standing on an edge, follow that edge's link in
195+; the room table and travel there.
196+; ----------------------------------------------------------------------------
197+check_exit:
198+ ld a, (thief_col)
199+ or a
200+ jr z, .west ; column 0
201+ cp 31
202+ jr z, .east ; column 31
203+ ld a, (thief_row)
204+ or a
205+ jr z, .north ; row 0
206+ cp 23
207+ jr z, .south ; row 23
208+ ret ; not on an edge
209+.north:
210+ call room_entry_addr
211+ inc hl
212+ inc hl ; +2 = North link
213+ ld a, (hl)
214+ jr .travel
215+.south:
216+ call room_entry_addr
217+ inc hl
218+ inc hl
219+ inc hl ; +3 = South link
220+ ld a, (hl)
221+ jr .travel
222+.east:
223+ call room_entry_addr
224+ ld de, 4 ; +4 = East link
225+ add hl, de
226+ ld a, (hl)
227+ jr .travel
228+.west:
229+ call room_entry_addr
230+ ld de, 5 ; +5 = West link
231+ add hl, de
232+ ld a, (hl)
233+.travel:
234+ cp NO_EXIT
235+ ret z ; a doorway to nowhere — stay
236+ ld (current_room), a
237+ call draw_room
238+ ld a, START_COL ; crude: drop him in the centre (Unit 6 fixes this)
239+ ld (thief_col), a
240+ ld a, START_ROW
241+ ld (thief_row), a
242+ call save_under
243+ call draw_thief
244+ ret
245+
246+; ----------------------------------------------------------------------------
247+; Save / restore / draw the thief — unchanged.
179248 ; ----------------------------------------------------------------------------
180249 pos_bc:
181250 ld a, (thief_row)
...
234303 djnz .thief_row
235304 ret
236305
237-; ----------------------------------------------------------------------------
238-; scr_addr_cr / attr_addr_cr — carried from Gloaming.
239-; ----------------------------------------------------------------------------
240306 scr_addr_cr:
241307 ld a, b
242308 and %00011000
...
269335 ret
270336
271337 ; ----------------------------------------------------------------------------
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)
338+; Palette.
275339 ; ----------------------------------------------------------------------------
276340 palette:
277341 defb '.'
...
282346 defb WALL_ATTR
283347
284348 ; ----------------------------------------------------------------------------
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.
349+; The room graph. Each entry: map pointer, then North, South, East, West links
350+; ($FF = no door). Two rooms, joined east-west: room 0's east door leads to
351+; room 1, room 1's west door leads back.
287352 ; ----------------------------------------------------------------------------
288-room_data:
353+rooms:
354+ defw room0_data
355+ defb NO_EXIT, NO_EXIT, 1, NO_EXIT
356+ defw room1_data
357+ defb NO_EXIT, NO_EXIT, NO_EXIT, 0
358+
359+; Room 0 — the pillared hall, with a doorway in the east wall (row 11).
360+room0_data:
289361 defb "################################"
290362 defb "#..............................#"
291363 defb "#..............................#"
...
296368 defb "#..............................#"
297369 defb "#........##..........##........#"
298370 defb "#........##..........##........#"
299- defb "#..............................#"
300371 defb "#..............................#"
372+ defb "#..............................."
301373 defb "#..............................#"
302374 defb "#..............................#"
303375 defb "#........##..........##........#"
304376 defb "#........##..........##........#"
377+ defb "#..............................#"
378+ defb "#..............................#"
379+ defb "#..............................#"
380+ defb "#..............................#"
381+ defb "#..............................#"
382+ defb "#..............................#"
383+ defb "#..............................#"
384+ defb "################################"
385+
386+; Room 1 — a plainer chamber with a block near the top, doorway in the west
387+; wall (row 11) leading back to room 0.
388+room1_data:
389+ defb "################################"
390+ defb "#..............................#"
391+ defb "#..............................#"
392+ defb "#..............................#"
393+ defb "#..............##..............#"
394+ defb "#..............##..............#"
395+ defb "#..............................#"
396+ defb "#..............................#"
397+ defb "#..............................#"
398+ defb "#..............................#"
399+ defb "#..............................#"
400+ defb "...............................#"
401+ defb "#..............................#"
402+ defb "#..............................#"
403+ defb "#..............................#"
404+ defb "#..............................#"
305405 defb "#..............................#"
306406 defb "#..............................#"
307407 defb "#..............................#"
...
347447 ; ----------------------------------------------------------------------------
348448 ; Variables.
349449 ; ----------------------------------------------------------------------------
450+current_room:
451+ defb 0
350452 thief_col:
351453 defb START_COL
352454 thief_row:
The complete program
; Shadowkeep — Unit 5: The Room Graph
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 gives the keep a graph of rooms — walk a doorway, flick to the next.

            org     32768

WALL_ATTR   equ     %01001000
FLOOR_ATTR  equ     %00001000
THIEF       equ     %01001010
WALL_BIT    equ     6

START_COL   equ     15
START_ROW   equ     11
NO_EXIT     equ     $FF

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

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

            xor     a
            ld      (current_room), 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

; ----------------------------------------------------------------------------
; room_entry_addr — HL = rooms + current_room * 6 (entry = map ptr + 4 links).
; ----------------------------------------------------------------------------
room_entry_addr:
            ld      a, (current_room)
            ld      l, a
            ld      h, 0
            ld      d, h
            ld      e, l            ; DE = room number
            add     hl, hl          ; *2
            add     hl, de          ; *3
            add     hl, hl          ; *6
            ld      de, rooms
            add     hl, de
            ret

; ----------------------------------------------------------------------------
; draw_room — draw the current room's map. Same walk as Unit 4, but the map
; pointer comes from the room table now, not a fixed label.
; ----------------------------------------------------------------------------
draw_room:
            call    room_entry_addr
            ld      a, (hl)         ; map pointer, low
            inc     hl
            ld      h, (hl)         ; map pointer, high
            ld      l, a
            ld      (map_ptr), hl
            ld      b, 0
.room_row:
            ld      c, 0
.room_col:
            ld      hl, (map_ptr)
            ld      a, (hl)
            call    lookup_tile
            call    draw_tile
            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:
            ld      hl, palette
.scan:
            cp      (hl)
            jr      z, .found
            inc     hl
            inc     hl
            inc     hl
            inc     hl
            jr      .scan
.found:
            inc     hl
            ld      e, (hl)
            inc     hl
            ld      d, (hl)
            ld      (tile_ptr), de
            inc     hl
            ld      a, (hl)
            ld      (tile_attr), a
            ret

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 — move as before, then check whether he stepped onto a doorway.
; ----------------------------------------------------------------------------
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
            call    check_exit
            ret

wall_at:
            call    attr_addr_cr
            bit     WALL_BIT, (hl)
            ret

; ----------------------------------------------------------------------------
; check_exit — if the thief is standing on an edge, follow that edge's link in
; the room table and travel there.
; ----------------------------------------------------------------------------
check_exit:
            ld      a, (thief_col)
            or      a
            jr      z, .west        ; column 0
            cp      31
            jr      z, .east        ; column 31
            ld      a, (thief_row)
            or      a
            jr      z, .north       ; row 0
            cp      23
            jr      z, .south       ; row 23
            ret                     ; not on an edge
.north:
            call    room_entry_addr
            inc     hl
            inc     hl              ; +2 = North link
            ld      a, (hl)
            jr      .travel
.south:
            call    room_entry_addr
            inc     hl
            inc     hl
            inc     hl              ; +3 = South link
            ld      a, (hl)
            jr      .travel
.east:
            call    room_entry_addr
            ld      de, 4           ; +4 = East link
            add     hl, de
            ld      a, (hl)
            jr      .travel
.west:
            call    room_entry_addr
            ld      de, 5           ; +5 = West link
            add     hl, de
            ld      a, (hl)
.travel:
            cp      NO_EXIT
            ret     z               ; a doorway to nowhere — stay
            ld      (current_room), a
            call    draw_room
            ld      a, START_COL    ; crude: drop him in the centre (Unit 6 fixes this)
            ld      (thief_col), a
            ld      a, START_ROW
            ld      (thief_row), a
            call    save_under
            call    draw_thief
            ret

; ----------------------------------------------------------------------------
; Save / restore / draw the thief — unchanged.
; ----------------------------------------------------------------------------
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:
            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

; ----------------------------------------------------------------------------
; Palette.
; ----------------------------------------------------------------------------
palette:
            defb    '.'
            defw    floor_tile
            defb    FLOOR_ATTR
            defb    '#'
            defw    wall_tile
            defb    WALL_ATTR

; ----------------------------------------------------------------------------
; The room graph. Each entry: map pointer, then North, South, East, West links
; ($FF = no door). Two rooms, joined east-west: room 0's east door leads to
; room 1, room 1's west door leads back.
; ----------------------------------------------------------------------------
rooms:
            defw    room0_data
            defb    NO_EXIT, NO_EXIT, 1, NO_EXIT
            defw    room1_data
            defb    NO_EXIT, NO_EXIT, NO_EXIT, 0

; Room 0 — the pillared hall, with a doorway in the east wall (row 11).
room0_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    "################################"

; Room 1 — a plainer chamber with a block near the top, doorway in the west
; wall (row 11) leading back to room 0.
room1_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.
; ----------------------------------------------------------------------------
current_room:
            defb    0
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 east with P to the gap in the right-hand wall and the screen flicks to the second room; walk west with O to its doorway and you flick back. You're navigating a graph of rooms with your feet:

East through the pillared hall's doorway, and the whole screen flicks to the second chamber; west through its doorway, and back again. The brief repaint is the flick — the sound of the genre, not a flaw. (He lands mid-room for now; Unit 6 makes him arrive at the threshold.)

Try this: a room to the north

Add a room2_data map with a doorway in its south wall (a . on row 23), give it a south link back to room 0, and set room 0's north link to 2 with a . in room 0's north wall (row 0). Now the keep branches: east to one room, north to another. The graph is yours to grow — each new room is a map plus four numbers.

Try this: a one-way door

Give room 0 an east link to room 1, but leave room 1's west link as NO_EXIT and wall up its west side. Now you can go east but not come back — a trapdoor, a portcullis that falls behind you. One number, and the keep has a one-way passage. (Make sure there's another way out, or the thief is stuck!)

Try this: lengthen the flick

The repaint is plain — the new room appears all at once. Before the draw_room in .travel, try blacking the screen (an LDIR wash over the bitmap) for a frame or two, so the old room clears before the new one paints. A heavier, more deliberate transition. Feel how much the timing of the flick changes the keep's mood.

When it's wrong, see why

  • He walks to the edge and nothing happens. The doorway cell must be floor (.), and that edge's link must not be $FF. A # in the wall blocks him before he reaches the edge; a $FF link is a door to nowhere.
  • He vanishes / the screen is half-painted. You caught the repaint mid-flick — it takes a few frames. In real play it settles immediately; it's only visible as the brief genre flick.
  • He travels to the wrong room. Check the link order in each entry: North, South, East, West. East is the third link byte, west the fourth.
  • The machine hangs on travel. A link points at a room number with no table entry, so room_entry_addr computes off into nowhere. Every link must be $FF or a real room index.
  • He arrives stuck in a wall. The centre cell (15, 11) must be floor in every room. If a room has a block there, he'll spawn inside it — Unit 6's edge re-entry will fix the spawn, but for now keep the centre clear.

Before and after

You started with a single room you could fill but never leave, and finished with a keep you can walk across — two rooms joined by doorways, the thief travelling between them by foot. The world turned out to be a graph: each room a map, each wall an edge that either holds or leads somewhere. A doorway is the absence of a wall; travel is a table lookup; the flick is the genre showing its hand. The keep has elsewhere in it now — and one honest seam left, the centre-landing, for the next unit to close.

What you've learnt

  • A world is a graph. Each room is a node — its map — and the edges are four links saying what lies through each wall. The whole structure is a small table.
  • A doorway is the absence of a wall. A floor cell in the wall ring; the thief walks onto it, and collision needs no special case.
  • Travel is a table lookup. Stand on an edge, read that edge's link, switch the current room, repaint. The graph does the routing.
  • The flick is the genre. Repainting the screen on entry is what a flick-screen adventure is — not a cost to hide, a texture to embrace.

What's next

You can cross the keep, but the crossing is a teleport — walk through a door and you appear in the middle of the next room, not at the threshold. In Unit 6, "Through the Doorway," we make it a real step: leave by the east edge and arrive at the west edge of the next room, at the same height, mid-stride. We'll carry his position across the seam so the flick reads as walking through a doorway, not blinking across the keep. The map becomes a walk.