Skip to content
Game 2Unit 9 of 191 hr learning time

The Keep's Gold

Three rooms you can wander, and nothing to do in them. Scatter gold through the keep, let the thief lift each coin as he steps on it, and make the last one matter: collect them all and the keep stands.

47% of Shadowkeep

You have a keep. Three rooms, joined by doorways, a hooded thief who walks them and cannot pass through walls. It is a place — but it is not yet a game, because there is nothing in it to want. You can wander the Hall, thread the Gallery’s gap, circle the Vault’s altar, and then… wander back. A place you can leave exactly as you found it is a room, not a level.

This unit gives the keep a point. Gold — coins scattered through the rooms — and a reason to move: lift every one, and the keep is yours. It is the oldest bargain in games, the one Atic Atac and a hundred others struck: here is treasure, here is somewhere to carry it from, now go. And with the gold comes the shape every finished game needs — a win: a state the keep can reach where the playing stops and the flourish plays.

What you’ll see by the end

The Hall, bare stone and four pillars, with the hooded thief near the centre and two yellow coins glinting on the right — one high, one low. The east doorway stands open in the right-hand wall.
The same Hall as last unit, with something to want in it: two coins on the right, a counter quietly waiting on six. Walk onto a coin and it lifts with a chime; lift the last and the keep stands.

Two coins glint in the Hall, more in the rooms beyond — six in all. Walk the thief onto one and it is gone: a bright ting, and the counter drops. Clear all six and the screen turns black to tell you so — THE KEEP STANDS — then hands you back to the title to do it again. A goal, a tally, an ending: the parts that turn walking into playing.

Gold is a glyph you can stand on

You have placed tiles in the room map before — walls, torches-to-come, the chalk you leave. Gold is one more, G, a coin drawn in plain yellow. The difference is in its attribute: where a wall is bright (and so reads as solid to wall_at), gold is dim — walkable — so the thief steps onto it, not into it:

GOLD_ATTR   equ     %00000110         ; yellow ink, no BRIGHT -> walkable gold

Drop a few into the room maps like any other letter:

            defb    "#.........................G....#"   ; a coin on the right

Lift it as you step

Every successful move already ends the same way: the thief lands on a new cell, and we save what’s beneath him and draw him on top. Collecting is one question asked at that moment — is the cell I just stepped onto gold? — and if so, we lift it:

collect_gold:
            call    cell_state_addr  ; the map cell under the thief
            ld      a, (hl)
            cp      'G'
            ret     nz               ; not gold — nothing to do
            ld      (hl), '.'        ; taken — floor from now on
            call    draw_floor_cell  ; repaint the cell empty
            call    sfx_pickup       ; a bright two-blip chime
            ld      hl, gold_remaining
            dec     (hl)             ; one fewer to find
            ret

We change the coin in the map, not just on screen — G becomes . — so it stays gone when the room is redrawn, and so save_under grabs floor, not treasure, as the thief moves off. The coin is lifted once and for all.

The last coin matters

A counter you can drive to zero is a game waiting for an ending. collect_gold ticks gold_remaining down; when it reaches zero, the keep is clear, and we raise a flag — won — that the main loop is watching:

            dec     (hl)
            ld      a, (hl)
            or      a
            call    z, win           ; the last coin — the keep stands

That flag is the hinge of the whole program. Until now the loop has done one thing forever: read the keys, move the thief, repeat. Now it is a small state machine — the same title → play → win → title shape you built in your first game, brought to the keep:

main_title:
            call    show_title       ; the name, and PRESS SPACE
            call    new_game         ; reset the rooms, place the gold
.game_loop:
            halt
            ld      a, (won)
            or      a
            jr      nz, .won         ; cleared? -> the flourish
            call    player_step
            jr      .game_loop
.won:
            call    show_win         ; THE KEEP STANDS
            jr      main_title       ; round again

new_game copies the room templates back into working memory — so the gold you lifted last time returns — resets the counter to six, and stands the thief at the door. The keep is replayable because every game begins by rebuilding it.

Milestone — a goal and a win

Gold is a walkable glyph you lift by stepping on it; a counter tracks how many remain; and the loop that only ever played now knows how to end — the last coin raises the win flag, the flourish plays, and the title returns for another run. Two steps: first the gold and the collecting, then the win that makes it mean something.

Step 1: gold in the rooms, and lifting it as you step
+85-6
11 ; Shadowkeep — Unit 9: The Keep's Gold
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-00 = Unit 8's end: three rooms you can explore, but nothing to seek.
3+; step-01 scatters gold through the rooms and lets the thief collect it.
44
55 org 32768
66
...
88 FLOOR_ATTR equ %00001000
99 MARK_ATTR equ %00001111
1010 THIEF equ %01001010
11+GOLD_ATTR equ %00000110 ; yellow ink, no BRIGHT -> walkable gold
12+TOTAL_GOLD equ 6
1113 WALL_BIT equ 6
1214
1315 START_COL equ 15
...
4244 ld (thief_col), a
4345 ld a, START_ROW
4446 ld (thief_row), a
47+ ld a, TOTAL_GOLD
48+ ld (gold_remaining), a
4549
4650 call draw_room
4751 call save_under
...
221225 ld (thief_col), a
222226 ld a, (trow)
223227 ld (thief_row), a
228+ call collect_gold ; the new cell might be gold — lift it first
224229 call save_under
225230 call draw_thief
226231 call check_exit
...
348353 inc de
349354 inc h
350355 djnz .thief_row
356+ ret
357+
358+; ----------------------------------------------------------------------------
359+; The gold — the keep's goal. Step onto a coin and it lifts: the map cell turns
360+; to floor (gone for good), the cell repaints as plain floor (no lighting yet),
361+; a chime sounds, and the counter ticks down.
362+; ----------------------------------------------------------------------------
363+collect_gold:
364+ call cell_state_addr ; HL -> map cell at the thief's position
365+ ld a, (hl)
366+ cp 'G'
367+ ret nz
368+ ld (hl), '.' ; the gold is taken — floor from now on
369+ call pos_bc ; B = row, C = col
370+ call draw_floor_cell ; repaint the cell as plain floor
371+ call sfx_pickup
372+ ld hl, gold_remaining
373+ dec (hl)
374+ ret
375+
376+; draw_floor_cell — paint the floor tile at (row B, col C). Lighting (Unit 13)
377+; will replace this with a shade chosen by distance from the nearest torch.
378+draw_floor_cell:
379+ ld hl, floor_tile
380+ ld (tile_ptr), hl
381+ ld a, FLOOR_ATTR
382+ ld (tile_attr), a
383+ call draw_tile
384+ ret
385+
386+; ----------------------------------------------------------------------------
387+; beep — the one tone primitive (the tiny game's blip, reused). B = cycles,
388+; C = the delay between speaker toggles (the pitch; larger C = lower).
389+; ----------------------------------------------------------------------------
390+beep:
391+.bp_cycle:
392+ ld a, %00010000
393+ out ($FE), a
394+ ld e, c
395+.bp_hi:
396+ dec e
397+ jr nz, .bp_hi
398+ xor a
399+ out ($FE), a
400+ ld e, c
401+.bp_lo:
402+ dec e
403+ jr nz, .bp_lo
404+ djnz .bp_cycle
405+ ret
406+
407+; sfx_pickup — a bright two-blip chime: the sound of gold.
408+sfx_pickup:
409+ ld b, 8
410+ ld c, 20
411+ call beep
412+ ld b, 8
413+ ld c, 14
414+ call beep
351415 ret
416+
417+gold_tile:
418+ defb %00000000
419+ defb %00111100
420+ defb %01111110
421+ defb %01111110
422+ defb %01111110
423+ defb %01111110
424+ defb %00111100
425+ defb %00000000
352426
353427 scr_addr_cr:
354428 ld a, b
...
391465 defb '+'
392466 defw mark_tile
393467 defb MARK_ATTR
468+ defb 'G'
469+ defw gold_tile
470+ defb GOLD_ATTR
394471
395472 ; ----------------------------------------------------------------------------
396473 ; The keep: three rooms. Hall -east-> Gallery -north-> Vault, and back.
...
407484 ; The Great Hall — four pillars, east door at row 11.
408485 room0_template:
409486 defb "################################"
410- defb "#..............................#"
411487 defb "#..............................#"
412488 defb "#..............................#"
413489 defb "#..............................#"
414490 defb "#..............................#"
415491 defb "#..............................#"
492+ defb "#.........................G....#"
416493 defb "#..............................#"
417494 defb "#........##..........##........#"
418495 defb "#........##..........##........#"
...
422499 defb "#..............................#"
423500 defb "#........##..........##........#"
424501 defb "#........##..........##........#"
425- defb "#..............................#"
426502 defb "#..............................#"
427503 defb "#..............................#"
428504 defb "#..............................#"
429505 defb "#..............................#"
506+ defb "#.........................G....#"
430507 defb "#..............................#"
431508 defb "#..............................#"
432509 defb "################################"
...
443520 defb "#..............................#"
444521 defb "#..............................#"
445522 defb "###############.################"
446- defb "#..............................#"
447523 defb "#..............................#"
448- defb "...............................#"
449524 defb "#..............................#"
525+ defb "........................G......#"
450526 defb "#..............................#"
451527 defb "#..............................#"
452528 defb "#..............................#"
...
455531 defb "#..............................#"
456532 defb "#..............................#"
457533 defb "#..............................#"
534+ defb "#.......................G......#"
458535 defb "#..............................#"
459536 defb "#..............................#"
460537 defb "################################"
...
476553 defb "#.............####.............#"
477554 defb "#.............####.............#"
478555 defb "#.............####.............#"
479- defb "#..............................#"
480556 defb "#..............................#"
481557 defb "#..............................#"
482558 defb "#..............................#"
483559 defb "#..............................#"
484560 defb "#..............................#"
485561 defb "#..............................#"
562+ defb "#.........G.........G..........#"
486563 defb "#..............................#"
487564 defb "#..............................#"
488565 defb "###############.################"
...
545622 defb 0
546623 under_thief:
547624 defb 0, 0, 0, 0, 0, 0, 0, 0, 0
625+gold_remaining:
626+ defb TOTAL_GOLD
548627
549628 room0_state:
550629 defs 768
Step 2: the last coin wins — a title -> play -> win -> title loop
+165-11
11 ; Shadowkeep — Unit 9: The Keep's Gold
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-01 scatters gold through the rooms and lets the thief collect it.
3+; step-02 makes the last coin win: THE KEEP STANDS, in a title -> play -> win loop.
44
55 org 32768
66
...
2424 start:
2525 ld a, 0
2626 out ($FE), a
27+ im 1
28+
29+; --- TITLE -> PLAY -> WIN -> TITLE ------------------------------------------
30+main_title:
31+ call show_title ; returns (interrupts off) when SPACE pressed
32+ call new_game ; reset the keep, place the gold, draw
33+ ei
34+.game_loop:
35+ halt
36+ ld a, (won)
37+ or a
38+ jr nz, .won
39+ call player_step
40+ call mark_step
41+ jr .game_loop
42+.won:
43+ call show_win ; "THE KEEP STANDS", flourish, wait for SPACE
44+ jr main_title ; round again
2745
46+; new_game — copy the room templates back into working state (so collected gold
47+; returns), reset the thief, the gold counter and the win flag, draw the keep.
48+new_game:
2849 ld hl, room0_template
2950 ld de, room0_state
3051 ld bc, 768
...
4061
4162 xor a
4263 ld (current_room), a
64+ ld (won), a
65+ ld a, TOTAL_GOLD
66+ ld (gold_remaining), a
4367 ld a, START_COL
4468 ld (thief_col), a
4569 ld a, START_ROW
4670 ld (thief_row), a
47- ld a, TOTAL_GOLD
48- ld (gold_remaining), a
4971
5072 call draw_room
5173 call save_under
5274 call draw_thief
53-
54- im 1
55- ei
56-.loop:
57- halt
58- call player_step
59- call mark_step
60- jr .loop
75+ ret
6176
6277 mark_step:
6378 ld bc, KEYS_SPACE
...
371386 call sfx_pickup
372387 ld hl, gold_remaining
373388 dec (hl)
389+ ld a, (hl)
390+ or a
391+ call z, win ; the last coin — the keep stands
374392 ret
375393
376394 ; draw_floor_cell — paint the floor tile at (row B, col C). Lighting (Unit 13)
...
423441 defb %01111110
424442 defb %00111100
425443 defb %00000000
444+
445+; ----------------------------------------------------------------------------
446+; show_title — black screen, the keep's name and a prompt; wait for SPACE (and
447+; release). Silent for now — the composed theme arrives in Unit 18. Words use the
448+; ROM's own 8x8 font at $3D00.
449+; ----------------------------------------------------------------------------
450+show_title:
451+ di
452+ call clear_screen
453+ ld hl, str_title
454+ ld b, 8
455+ ld c, 11
456+ call print_string
457+ ld hl, str_prompt
458+ ld b, 16
459+ ld c, 6
460+ call print_string
461+.tt_wait:
462+ ld bc, KEYS_SPACE
463+ in a, (c)
464+ bit 0, a
465+ jr nz, .tt_wait
466+.tt_rel:
467+ ld bc, KEYS_SPACE
468+ in a, (c)
469+ bit 0, a
470+ jr z, .tt_rel
471+ ret
472+
473+; show_win — the keep is cleared. Black screen, the words, the flourish, then
474+; wait for SPACE (and release) to return to the title.
475+show_win:
476+ di
477+ call clear_screen
478+ ld hl, str_won
479+ ld b, 9
480+ ld c, 8
481+ call print_string
482+ ld hl, str_again
483+ ld b, 15
484+ ld c, 5
485+ call print_string
486+ call sfx_win
487+.sw_wait:
488+ ld bc, KEYS_SPACE
489+ in a, (c)
490+ bit 0, a
491+ jr nz, .sw_wait
492+.sw_rel:
493+ ld bc, KEYS_SPACE
494+ in a, (c)
495+ bit 0, a
496+ jr z, .sw_rel
497+ ret
498+
499+; win — the last coin is gone. Flourish, and raise the flag the loop watches.
500+win:
501+ ld a, 1
502+ ld (won), a
503+ call sfx_win
504+ ret
505+
506+; sfx_win — an ascending flourish: a low note climbing in steps to a high one.
507+sfx_win:
508+ ld c, 60
509+.swf_loop:
510+ ld b, 12
511+ push bc
512+ call beep
513+ pop bc
514+ ld a, c
515+ sub 8
516+ ld c, a
517+ cp 16
518+ jr nc, .swf_loop
519+ ret
520+
521+; clear_screen — fill bitmap and attributes with zero: a black screen.
522+clear_screen:
523+ ld hl, $4000
524+ ld (hl), 0
525+ ld de, $4001
526+ ld bc, 6911
527+ ldir
528+ ret
529+
530+; print_string — HL -> zero-terminated text, B = row, C = col.
531+print_string:
532+.ps_loop:
533+ ld a, (hl)
534+ or a
535+ ret z
536+ push hl
537+ call print_char
538+ pop hl
539+ inc hl
540+ inc c
541+ jr .ps_loop
542+
543+; print_char — A = character (32..127), B = row, C = col. Copies the ROM font
544+; glyph at $3D00 + (char-32)*8 to the screen, bright white.
545+print_char:
546+ push bc
547+ sub 32
548+ ld l, a
549+ ld h, 0
550+ add hl, hl
551+ add hl, hl
552+ add hl, hl
553+ ld de, $3D00
554+ add hl, de
555+ push hl
556+ call scr_addr_cr
557+ pop de
558+ ld b, 8
559+.pc_row:
560+ ld a, (de)
561+ ld (hl), a
562+ inc de
563+ inc h
564+ djnz .pc_row
565+ pop bc
566+ call attr_addr_cr
567+ ld (hl), %01000111
568+ ret
569+
570+str_title:
571+ defb "SHADOWKEEP", 0
572+str_prompt:
573+ defb "PRESS SPACE TO ENTER", 0
574+str_won:
575+ defb "THE KEEP STANDS", 0
576+str_again:
577+ defb "PRESS SPACE TO RETURN", 0
426578
427579 scr_addr_cr:
428580 ld a, b
...
624776 defb 0, 0, 0, 0, 0, 0, 0, 0, 0
625777 gold_remaining:
626778 defb TOTAL_GOLD
779+won:
780+ defb 0
627781
628782 room0_state:
629783 defs 768
The complete program
; Shadowkeep — Unit 9: The Keep's Gold
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-02 makes the last coin win: THE KEEP STANDS, in a title -> play -> win loop.

            org     32768

WALL_ATTR   equ     %01001000
FLOOR_ATTR  equ     %00001000
MARK_ATTR   equ     %00001111
THIEF       equ     %01001010
GOLD_ATTR   equ     %00000110         ; yellow ink, no BRIGHT -> walkable gold
TOTAL_GOLD  equ     6
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
KEYS_SPACE  equ     $7FFE

start:
            ld      a, 0
            out     ($FE), a
            im      1

; --- TITLE -> PLAY -> WIN -> TITLE ------------------------------------------
main_title:
            call    show_title       ; returns (interrupts off) when SPACE pressed
            call    new_game         ; reset the keep, place the gold, draw
            ei
.game_loop:
            halt
            ld      a, (won)
            or      a
            jr      nz, .won
            call    player_step
            call    mark_step
            jr      .game_loop
.won:
            call    show_win         ; "THE KEEP STANDS", flourish, wait for SPACE
            jr      main_title       ; round again

; new_game — copy the room templates back into working state (so collected gold
; returns), reset the thief, the gold counter and the win flag, draw the keep.
new_game:
            ld      hl, room0_template
            ld      de, room0_state
            ld      bc, 768
            ldir
            ld      hl, room1_template
            ld      de, room1_state
            ld      bc, 768
            ldir
            ld      hl, room2_template
            ld      de, room2_state
            ld      bc, 768
            ldir

            xor     a
            ld      (current_room), a
            ld      (won), a
            ld      a, TOTAL_GOLD
            ld      (gold_remaining), 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
            ret

mark_step:
            ld      bc, KEYS_SPACE
            in      a, (c)
            bit     0, a
            ret     nz
            call    cell_state_addr
            ld      (hl), '+'
            ld      hl, mark_tile
            ld      de, under_thief
            ld      bc, 8
            ldir
            ld      a, MARK_ATTR
            ld      (under_thief + 8), a
            ret

cell_state_addr:
            call    room_entry_addr
            ld      a, (hl)
            inc     hl
            ld      h, (hl)
            ld      l, a
            push    hl
            ld      a, (thief_row)
            ld      l, a
            ld      h, 0
            add     hl, hl
            add     hl, hl
            add     hl, hl
            add     hl, hl
            add     hl, hl
            ld      a, (thief_col)
            ld      e, a
            ld      d, 0
            add     hl, de
            pop     de
            add     hl, de
            ret

room_entry_addr:
            ld      a, (current_room)
            ld      l, a
            ld      h, 0
            ld      d, h
            ld      e, l
            add     hl, hl
            add     hl, de
            add     hl, hl
            ld      de, rooms
            add     hl, de
            ret

draw_room:
            call    room_entry_addr
            ld      a, (hl)
            inc     hl
            ld      h, (hl)
            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:
            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    collect_gold     ; the new cell might be gold — lift it first
            call    save_under
            call    draw_thief
            call    check_exit
            ret

wall_at:
            call    attr_addr_cr
            bit     WALL_BIT, (hl)
            ret

check_exit:
            ld      a, (thief_col)
            or      a
            jr      z, .west
            cp      31
            jr      z, .east
            ld      a, (thief_row)
            or      a
            jr      z, .north
            cp      23
            jr      z, .south
            ret
.east:
            call    room_entry_addr
            ld      de, 4
            add     hl, de
            ld      a, (hl)
            cp      NO_EXIT
            ret     z
            ld      (current_room), a
            ld      a, 1
            ld      (thief_col), a
            jr      .enter
.west:
            call    room_entry_addr
            ld      de, 5
            add     hl, de
            ld      a, (hl)
            cp      NO_EXIT
            ret     z
            ld      (current_room), a
            ld      a, 30
            ld      (thief_col), a
            jr      .enter
.north:
            call    room_entry_addr
            inc     hl
            inc     hl
            ld      a, (hl)
            cp      NO_EXIT
            ret     z
            ld      (current_room), a
            ld      a, 22
            ld      (thief_row), a
            jr      .enter
.south:
            call    room_entry_addr
            inc     hl
            inc     hl
            inc     hl
            ld      a, (hl)
            cp      NO_EXIT
            ret     z
            ld      (current_room), a
            ld      a, 1
            ld      (thief_row), a
.enter:
            call    draw_room
            call    save_under
            call    draw_thief
            ret

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

; ----------------------------------------------------------------------------
; The gold — the keep's goal. Step onto a coin and it lifts: the map cell turns
; to floor (gone for good), the cell repaints as plain floor (no lighting yet),
; a chime sounds, and the counter ticks down.
; ----------------------------------------------------------------------------
collect_gold:
            call    cell_state_addr  ; HL -> map cell at the thief's position
            ld      a, (hl)
            cp      'G'
            ret     nz
            ld      (hl), '.'        ; the gold is taken — floor from now on
            call    pos_bc           ; B = row, C = col
            call    draw_floor_cell  ; repaint the cell as plain floor
            call    sfx_pickup
            ld      hl, gold_remaining
            dec     (hl)
            ld      a, (hl)
            or      a
            call    z, win           ; the last coin — the keep stands
            ret

; draw_floor_cell — paint the floor tile at (row B, col C). Lighting (Unit 13)
; will replace this with a shade chosen by distance from the nearest torch.
draw_floor_cell:
            ld      hl, floor_tile
            ld      (tile_ptr), hl
            ld      a, FLOOR_ATTR
            ld      (tile_attr), a
            call    draw_tile
            ret

; ----------------------------------------------------------------------------
; beep — the one tone primitive (the tiny game's blip, reused). B = cycles,
; C = the delay between speaker toggles (the pitch; larger C = lower).
; ----------------------------------------------------------------------------
beep:
.bp_cycle:
            ld      a, %00010000
            out     ($FE), a
            ld      e, c
.bp_hi:
            dec     e
            jr      nz, .bp_hi
            xor     a
            out     ($FE), a
            ld      e, c
.bp_lo:
            dec     e
            jr      nz, .bp_lo
            djnz    .bp_cycle
            ret

; sfx_pickup — a bright two-blip chime: the sound of gold.
sfx_pickup:
            ld      b, 8
            ld      c, 20
            call    beep
            ld      b, 8
            ld      c, 14
            call    beep
            ret

gold_tile:
            defb    %00000000
            defb    %00111100
            defb    %01111110
            defb    %01111110
            defb    %01111110
            defb    %01111110
            defb    %00111100
            defb    %00000000

; ----------------------------------------------------------------------------
; show_title — black screen, the keep's name and a prompt; wait for SPACE (and
; release). Silent for now — the composed theme arrives in Unit 18. Words use the
; ROM's own 8x8 font at $3D00.
; ----------------------------------------------------------------------------
show_title:
            di
            call    clear_screen
            ld      hl, str_title
            ld      b, 8
            ld      c, 11
            call    print_string
            ld      hl, str_prompt
            ld      b, 16
            ld      c, 6
            call    print_string
.tt_wait:
            ld      bc, KEYS_SPACE
            in      a, (c)
            bit     0, a
            jr      nz, .tt_wait
.tt_rel:
            ld      bc, KEYS_SPACE
            in      a, (c)
            bit     0, a
            jr      z, .tt_rel
            ret

; show_win — the keep is cleared. Black screen, the words, the flourish, then
; wait for SPACE (and release) to return to the title.
show_win:
            di
            call    clear_screen
            ld      hl, str_won
            ld      b, 9
            ld      c, 8
            call    print_string
            ld      hl, str_again
            ld      b, 15
            ld      c, 5
            call    print_string
            call    sfx_win
.sw_wait:
            ld      bc, KEYS_SPACE
            in      a, (c)
            bit     0, a
            jr      nz, .sw_wait
.sw_rel:
            ld      bc, KEYS_SPACE
            in      a, (c)
            bit     0, a
            jr      z, .sw_rel
            ret

; win — the last coin is gone. Flourish, and raise the flag the loop watches.
win:
            ld      a, 1
            ld      (won), a
            call    sfx_win
            ret

; sfx_win — an ascending flourish: a low note climbing in steps to a high one.
sfx_win:
            ld      c, 60
.swf_loop:
            ld      b, 12
            push    bc
            call    beep
            pop     bc
            ld      a, c
            sub     8
            ld      c, a
            cp      16
            jr      nc, .swf_loop
            ret

; clear_screen — fill bitmap and attributes with zero: a black screen.
clear_screen:
            ld      hl, $4000
            ld      (hl), 0
            ld      de, $4001
            ld      bc, 6911
            ldir
            ret

; print_string — HL -> zero-terminated text, B = row, C = col.
print_string:
.ps_loop:
            ld      a, (hl)
            or      a
            ret     z
            push    hl
            call    print_char
            pop     hl
            inc     hl
            inc     c
            jr      .ps_loop

; print_char — A = character (32..127), B = row, C = col. Copies the ROM font
; glyph at $3D00 + (char-32)*8 to the screen, bright white.
print_char:
            push    bc
            sub     32
            ld      l, a
            ld      h, 0
            add     hl, hl
            add     hl, hl
            add     hl, hl
            ld      de, $3D00
            add     hl, de
            push    hl
            call    scr_addr_cr
            pop     de
            ld      b, 8
.pc_row:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .pc_row
            pop     bc
            call    attr_addr_cr
            ld      (hl), %01000111
            ret

str_title:
            defb    "SHADOWKEEP", 0
str_prompt:
            defb    "PRESS SPACE TO ENTER", 0
str_won:
            defb    "THE KEEP STANDS", 0
str_again:
            defb    "PRESS SPACE TO RETURN", 0

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:
            defb    '.'
            defw    floor_tile
            defb    FLOOR_ATTR
            defb    '#'
            defw    wall_tile
            defb    WALL_ATTR
            defb    '+'
            defw    mark_tile
            defb    MARK_ATTR
            defb    'G'
            defw    gold_tile
            defb    GOLD_ATTR

; ----------------------------------------------------------------------------
; The keep: three rooms. Hall -east-> Gallery -north-> Vault, and back.
;   entry: map ptr, North, South, East, West
; ----------------------------------------------------------------------------
rooms:
            defw    room0_state
            defb    NO_EXIT, NO_EXIT, 1, NO_EXIT      ; Hall: east -> Gallery
            defw    room1_state
            defb    2, NO_EXIT, NO_EXIT, 0            ; Gallery: north -> Vault, west -> Hall
            defw    room2_state
            defb    NO_EXIT, 1, NO_EXIT, NO_EXIT      ; Vault: south -> Gallery

; The Great Hall — four pillars, east door at row 11.
room0_template:
            defb    "################################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#.........................G....#"
            defb    "#..............................#"
            defb    "#........##..........##........#"
            defb    "#........##..........##........#"
            defb    "#..............................#"
            defb    "#..............................."
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#........##..........##........#"
            defb    "#........##..........##........#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#.........................G....#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "################################"

; The Gallery — a dividing wall with one gap (column 15). West door (row 11)
; back to the Hall; north door (column 15) up to the Vault.
room1_template:
            defb    "###############.################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "###############.################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "........................G......#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#.......................G......#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "################################"

; The Vault — a great altar of stone in the middle, south door (column 15)
; down to the Gallery.
room2_template:
            defb    "################################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#.............####.............#"
            defb    "#.............####.............#"
            defb    "#.............####.............#"
            defb    "#.............####.............#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#.........G.........G..........#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "###############.################"

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

mark_tile:
            defb    %00000000
            defb    %00011000
            defb    %00011000
            defb    %01111110
            defb    %01111110
            defb    %00011000
            defb    %00011000
            defb    %00000000

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

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
gold_remaining:
            defb    TOTAL_GOLD
won:
            defb    0

room0_state:
            defs    768
room1_state:
            defs    768
room2_state:
            defs    768

            end     start

Collect the Hall’s two coins, cross to the Gallery and the Vault for the rest, and lift the sixth:

Six coins across three rooms, each lifting with a chime and dropping the tally, until the last turns the screen to THE KEEP STANDS. A place became a level the moment there was something to clear from it.

Try this: a coin behind the altar

Move a G in the Vault map to a cell hemmed in by the altar’s stone, or tuck one in a pillar’s shadow in the Hall. The keep is only cleared when every coin is lifted, so a coin you have to work to reach is a small puzzle — the beginning of designing a level, not just filling a room.

Try this: change the reward

sfx_pickup is two quick blips. Make the chime yours: a single low thunk for a heavy coin, or three rising blips for a richer prize. The sound a game makes when you succeed is half of why success feels good — spend a minute making yours feel good.

Try this: count up, not down

We count gold_remaining down to zero. Flip it: count coins collected up, and win when the total equals the number you placed. The same game, a different feeling — a score that grows, not a debt that shrinks. Which one suits a treasure hunt?

When it’s wrong, see why

  • The coin vanishes but comes back. You repainted the screen but not the map — the redraw put the G back. Change the map cell to . as well; that is what makes it gone.
  • You win the instant you touch one coin. The counter is starting at 1, or dec runs before the count is set. gold_remaining must be reset to the full count in new_game, before the loop begins.
  • The thief walks straight over gold without lifting it. collect_gold isn’t called after a move, or it’s checking the old cell. It has to run once the thief has landed on the new cell, reading that cell’s contents.
  • Second game has no gold. new_game isn’t copying the templates back — the working rooms still hold the floors you left. Rebuild the rooms every game and the treasure returns.
  • The coin is solid — the thief stops dead at it. GOLD_ATTR has its BRIGHT bit set, so wall_at reads it as wall. Gold must be dim to be walkable.

Before and after

You began with three rooms and nothing to do in them, and finished with a keep that can be cleared — six coins to lift, a tally that falls, and a black screen with two words that mean you did it. Nothing exotic did the work: a walkable glyph, a question asked after each step, a counter, and a flag the loop watches. That flag is the important thing. It turned a walk that never ended into a game that can — and a game that can end is a game you can win.

What you’ve learnt

  • A collectible is a walkable tile you lift on contact. Change it in the map, not just on screen, and it stays lifted.
  • A goal is a counter with an ending. Track what remains; when it hits zero, the game is won.
  • A win is a state, not a moment. A flag the loop watches, turning play into title → play → win → title — the shape every finished game shares.
  • Replayable means rebuilt. Every new game copies the keep back from its templates, so the treasure — and the challenge — return.

What’s next

The keep can be cleared, but nothing stops you. You can take all the time in the world, walk any route, never lose a thing — and a game you cannot lose is only half a game. In Unit 10, “Something in the Dark,” the keep stops being empty: a second figure moves in it, walking a slow patrol of its own. It cannot hurt you yet — first we give it a body and a route. But the coins are about to get harder to reach.