“The Haunted Belltower” is a multi-room platform game in which the player guides a rabbit character through five rooms — entrance hall, wine cellar, staircase, bats’ bedroom, and haunted belfry — collecting bells while avoiding bats. The game uses 15 user-defined graphics (UDGs “a” through “o”) whose bitmaps are loaded via DATA statements and POKEd into UDG memory starting at USR “a”, with a guard at address 63000 to avoid reloading them unnecessarily. Two enemy bats move toward the player using attribute-based collision detection via the ATTR function, and a bouncing ball death sequence plays when the player is caught. Room layouts, platform positions, bell locations, and rope data are all encoded in DATA blocks at lines 8100–8503, with each room’s data selected by RESTOREing to offset 8000+100*z.
Program Analysis
Program Structure
The program is organized into clearly separated functional blocks, entered via GO TO 6000 at line 15. The main flow is:
- Initialization (6000–6004): Sets up strings, UDGs, room names, then chains to key setup, instructions, and the title screen.
- Title screen / attract mode (9000–9175): Draws a detailed PLOT/DRAW illustration of the belltower and loops music from addresses 63000–63063 until a key is pressed.
- Instructions (9200–9220): Multi-page text screens with a keypress-to-continue subroutine at 9400.
- Key definition (9800–9859): Prompts for three user-defined keys (LEFT, RIGHT, CLIMB ROPE) stored in string
m$. - Room setup (8000–8509): RESTOREs to room-specific DATA, draws platforms, ropes, bells, doors, and initializes entity positions.
- Main game loop (2000–2999): Alternates between updating the player (k=1) and one bat (k=2), using a
FOR k=1 TO 2/NEXT kstructure. - Bell collection (200–320): Checks ATTR for a bell, awards points, animates collection, and adds the bell to the score display.
- Death sequence (4000–4399): Plays a descending tone, bounces the player sprite around the screen using POKEd music data, then decrements lives.
- Game over (4401–4452): Flashing “GAME OVER” panel with restart prompt.
UDG Setup and Memory Use
Lines 6002 and 6005–6027 handle the user-defined graphics. A total of 15 UDGs (“a” through “o”, chars 144–158) are defined by reading 8-byte bitmaps from DATA and POKEing them into memory starting at USR "a". Address 63000 is used as both a sentinel (checked against value 17 to avoid re-POKEing on restart) and as the start of a 64-byte music table. Line 8800 similarly guards the music data reload with the same sentinel check.
The UDGs used in the game include:
\a,\b— rabbit sprite frames (two animation states, each two rows tall)\c,\d— alternate rabbit frames\e— platform block\f— rope segment\j,\k— bat sprites\l— bell collectible\m,\n— door graphic elements\o— additional graphic
Main Game Loop and Entity Movement
The loop at lines 2000–2999 uses a FOR k=1 TO 2 construct to process two entities per iteration, branching at line 2199 with IF k=2 THEN GO TO 2262 (which targets the second bat’s code block at 2300). This is an economical way to share loop overhead between player and enemy updates.
Player movement (lines 2040–2095) is entirely expression-driven without explicit IF branches for direction:
x1 = x + 3*((ATTR(x+2,y)=7) - (INKEY$=m$(3) AND ATTR(x-1,y)=4))— moves down 3 rows if the cell below has ATTR 7 (rope color), or up 3 rows if climbing key is pressed and rope is present above.y1 = y + (INKEY$=m$(2) AND y<31) - (INKEY$=m$(1) AND y>0)— horizontal movement, clamped to screen edges.
Each bat (variables a,b and c,d) tracks the player’s column (b1, d1) and closes horizontally in steps of 3, with ATTR checks (value <12) to avoid walking into walls. Vertical homing also uses a one-step increment/decrement expression. Line 2347 includes collision avoidance between the two bats, randomizing their positions if they coincide.
Collision Detection via ATTR
The game relies heavily on the ATTR function for all collision detection rather than tracking positions in arrays. Key attribute values used:
| ATTR value | Meaning |
|---|---|
| 6 | Bell (collectible) cell — triggers subroutine 200 |
| 7 | Rope — allows vertical movement |
| 4 | Rope above player — climb condition |
| 12 | Wall/platform threshold for bat pathfinding |
Line 2080 checks ATTR(x1+1,y1)=6 (one row below the proposed position) to trigger bell pickup, effectively detecting when the player lands on a bell.
Room Data System
Five rooms are encoded entirely in DATA statements. Line 8015 executes RESTORE 8000+100*z where z is the room number (1–5), selecting the correct block. Each room’s data section contains multiple sublists terminated by sentinel value 99, read sequentially for: platform positions, door positions, wall patches, rope positions, and bell positions. The room name strings are stored in a DIMensioned string array q$(5,15).
Music and Sound
A 64-byte table at addresses 63000–63063 stores note values (offset by 40 or 28 depending on context) used for background music and death sequences. The title screen plays this table in reverse (line 9150: FOR i=63063 TO 63000 STEP -1) as an attract-mode loop. The death sequence at line 4041 plays it forward with BEEP .05,(PEEK u)-28. The room-transition fanfare at line 8715 plays the first 8 bytes only.
Animation Technique
The rabbit uses two-row, two-frame animation. Arrays a$() and b$() each hold three entries (two animation frames plus a blank). Frame alternation is achieved by printing a$(k) and a$(3-k) — when k=1 frame 1 is shown at the new position and frame 2 (blank) erases the old, and vice versa for k=2. This XOR-free approach works because positions are tracked explicitly.
Key Definition Subroutine
Lines 9800–9859 implement a flexible key-definition system. Key names are read from DATA at line 9860 ("LEFT","RIGHT","CLIMB ROPE"). Each chosen key is appended to m$, and a duplicate-key check loop (lines 9832–9834) prevents assigning the same key twice. The flashing ? prompt uses FLASH 1 combined with CHR$ 8 (cursor left) to animate in place while waiting for input via PAUSE 0.
Notable Anomalies
- Line 2199 branches to
GO TO 2262which does not exist; execution falls through to line 2300 (the second bat’s update block). This is intentional — skipping line 2261’sGO TO 2361(also non-existent, falling to line 2999NEXT k) for the first bat. - Line 9455 uses the idiom
IF INKEY$<>"" THEN GO TO 9455to wait for key release afterPAUSE 0, preventing accidental skipping of instruction pages. - The
GO SUB 9400entry point is at line 9402 (line 9400 does not exist), a standard Sinclair BASIC memory optimization using GO TO / GO SUB to a non-existent line. - Line 8074 is referenced by
GO TO 8074at line 8072 but does not exist in the listing; execution would fall through to line 8080. This appears intentional.
Content
Source Code
1 REM by T Sherwood of West Bromwich,West Midlands
15 GO TO 6000
200 IF ATTR (x1,y1)<>6 THEN PRINT INK 5;AT x1+1,y1;"\l": GO SUB 300: LET e=e+1: PRINT INK 6; PAPER 0; FLASH 1; OVER 0;AT 0,e*3;"\l"
210 IF x1=1 AND y1=31 AND e=9 THEN LET z=z+1: LET s=s+100: GO TO 8000
299 RETURN
300 LET s=s+45: PRINT #0; OVER 0;AT 0,15-LEN STR$ s; INK 6; PAPER 1;s
310 FOR j=x1+1 TO 0 STEP -1: PRINT INK 8; FLASH 8;AT j,y1;"\l": BEEP .005,40-j*2: PRINT INK 8; FLASH 8;AT j,y1;"\l": NEXT j
320 RETURN
992 PLOT 64,0: DRAW 17,112: DRAW 2,-24: DRAW 24,22
2000 FOR k=1 TO 2
2040 LET x1=x+3*((ATTR (x+2,y)=7)-(INKEY$=m$(3) AND ATTR (x-1,y)=4))
2070 LET y1=y+(INKEY$=m$(2) AND y<31)-(INKEY$=m$(1) AND y>0)
2080 IF ATTR (x1+1,y1)=6 THEN GO SUB 200
2090 PRINT AT x,y;a$(k);AT x+1,y;b$(k)
2095 PRINT AT x1,y1;a$(3-k);AT x1+1,y1;b$(3-k): LET x=x1: LET y=y1
2199 IF k=2 THEN GO TO 2262
2200 LET b1=b+(y>b)-(y<b)
2230 LET a1=a+3*(((x+1)>a AND ATTR (a+1,b)<12)-((x+1)<a AND ATTR (a-2,b)<12))
2242 IF y=b1 THEN IF x+1=a1 THEN GO TO 4000
2245 IF b1=b AND RND>.8 AND b1<27 THEN LET b1=b1+5
2250 PRINT AT a,b;"\k";AT a1,b1;"\k"
2260 LET a=a1: LET b=b1
2261 GO TO 2361
2300 LET d1=d+(y>d)-(y<d)
2330 LET c1=c+3*(((x+1)>c AND ATTR (c+1,d)<12)-((x+1)<c AND ATTR (c-2,d)<12))
2342 IF y=d1 THEN IF x+1=c1 THEN GO TO 4000
2345 IF d1=d AND RND>.8 AND d1>4 THEN LET d1=d1-5
2347 IF d1=b1 AND a1=c1 THEN LET d1=INT (RND*32): LET c1=(3*(2+(INT (RND*6))))-1
2350 PRINT AT c,d;"\k";AT c1,d1;"\k"
2360 LET c=c1: LET d=d1
2999 NEXT k: GO TO 2000
4005 PRINT AT a,b;"\k";AT c,d;"\k"
4010 FOR j=41 TO 1 STEP -4: PRINT AT x,y;a$(3-k);AT x+1,y;b$(3-k): BEEP .014,j: NEXT j
4030 INK 8: PAPER 8: FLASH 8
4040 LET v=1: LET w=1: LET i=x: LET j=y: PRINT AT 1,j;"\k"
4041 FOR u=63031 TO 63000 STEP -1: BEEP .05,(PEEK u)-28
4044 IF i>20 OR i<1 THEN LET v=-v
4045 IF j>30 OR j<1 THEN LET w=-w
4046 PRINT AT i,j;"\k"
4047 LET i=i+v: LET j=j+w
4048 PRINT AT i,j;"\k"
4050 NEXT u
4051 PRINT AT i,j;"\k"
4052 INK 7: PAPER 0: FLASH 0
4060 LET l=l-1: IF l<1 THEN GO TO 4400
4080 FOR i=7 TO 0 STEP -1: BORDER i: PAUSE 2: NEXT i
4399 GO TO 8500
4401 LET t=0
4405 PRINT OVER 0; PAPER 2; INK t;AT 7,4;" ";AT 8,4;" GAME OVER ";AT 9,4;" ";AT 10,4;" PRESS KEY 0 TO RESTART ";AT 11,4;" "
4410 PRINT #0;AT 0,26; PAPER 1;" ";AT 1,26; PAPER 1;" "
4430 LET t=t+1: IF t>7 THEN LET t=0
4450 IF INKEY$<>"0" THEN GO TO 4405
4452 CLS : GO TO 8800
6000 LET p$="\h\g\h\i\o": LET a$="\c\a": LET b$="\d\b": LET s=0: LET h=0
6001 RESTORE : PAPER 0: BORDER 0: INK 7: OVER 0: CLS : DIM q$(5,15)
6002 IF PEEK 63000<>17 THEN FOR i=USR "a" TO USR "o"+7: READ j: POKE i,j: NEXT i
6003 LET q$(1)="entrance hall": LET q$(2)="wine cellar": LET q$(3)="staircase": LET q$(4)="bats' bedroom": LET q$(5)="haunted belfry"
6004 GO SUB 9800: GO SUB 9400: GO SUB 9200: GO TO 8800
6005 DATA 112,154,159,61,93,117,124,56,8,62,93,157,21,116,119,7,14,89,249,188,186,174,62,28,16,124,186,185,168,46,238,224
6010 DATA 187,187,187,0,238,238,238,0,16,8,24,16,8,24,16,8,24,24,60,126,98,98,98,126
6015 DATA 255,231,255,0,0,0,0,0,255,0,16,56,124,84,68,108,255,255,183,221,107,170,84,0
6020 DATA 129,219,255,126,24,0,0,0
6025 DATA 0,24,60,126,126,126,255,0
6026 DATA 60,126,255,255,255,255,255,159,255,0,0,0,0,0,0,0
6027 DATA 56,186,186,252,60,30,15,3
7700 LET z=1: IF s>h THEN LET h=s
7710 LET l=3: LET s=0
8005 IF z>5 THEN LET z=1
8006 OVER 0: INK 7: PAPER 0: CLS
8011 PRINT PAPER 4; INK 0;AT 21,0;" Room ";z;" .... The ";q$(z)
8014 FOR i=2 TO 20 STEP 3: PRINT INK 5;AT i,0;" ": NEXT i
8015 RESTORE 8000+100*z
8017 PRINT INK 6;AT 1,31;"\m";AT 2,31;"\ "
8020 READ x,y: IF x=99 THEN GO TO 8050
8025 PRINT INK 3; PAPER 6;AT x,y;"\e\e\e\e\e\e": GO TO 8020
8050 READ x,y: IF x=99 THEN GO TO 8061
8060 PRINT AT x,y; INK 0; PAPER 6; INVERSE 1;p$(z); INK 2; PAPER 6;"\e\e\e\e"; INK 0; PAPER 5; INVERSE 1;p$(z): GO TO 8050
8062 READ x,y: IF x=99 THEN GO TO 8065
8064 PRINT INK 4; PAPER 1;AT x,y;"\j\j\j\j\j\j": GO TO 8062
8070 READ x,y: IF x=99 THEN GO TO 8072
8071 FOR i=0 TO 4: PRINT PAPER 5; INK 0; INVERSE 1;AT x,y+i;p$(z): NEXT i: GO TO 8070
8072 READ x,y: IF x=99 THEN GO TO 8074
8073 PRINT INK 4;AT x,y;"\f";AT x+1,y;"\f";AT x+2,y;"\f";AT x,y; OVER 1;"\n": GO TO 8072
8080 LET e=0
8093 PRINT AT 0,0; INK 0; PAPER 4; INVERSE 1;"\h\h\h\f\h\h\f\h\h\f\h\h\f\h\h\f\h\h\f\h\h\f\h\h\f\h\h\f\h\h\h\h";AT 1,3;"\f \f \f \f \f \f \f \f \f"
8095 INK 4: FOR w=1 TO 4: LET i=3*((INT (RND*9))+1): LET u=14+(INT (RND*7)): FOR j=2 TO u: PRINT AT j,i;"\f": NEXT j: NEXT w: INK 7
8097 FOR i=1 TO 9: READ x,y: PRINT INK 6;AT x,y;"\l": NEXT i
8100 DATA 3,23,3,26,6,24,9,6,12,25,15,15,18,0,18,2,99,0
8105 DATA 6,0,6,2,6,8,6,20,9,11,12,20,18,10,18,12,99,0
8110 DATA 9,26,12,2,12,6,12,12,18,19,99,0
8115 DATA 3,0,3,5,15,10,15,21,99,0
8120 DATA 18,19,3,8,3,28,6,6,6,22,9,13,9,22,12,24,12,10,15,19,18,11,18,24,99,0
8121 DATA 2,0,2,24,5,12,8,16,8,28,11,30,14,16,17,4,17,14
8200 DATA 9,2,12,6,12,7,12,24,12,26,13,24,13,26,19,8,19,12,99,0
8201 DATA 6,16,9,8,15,0,15,2,15,16,15,18,18,24,18,26,99,0
8202 DATA 3,18,3,20,3,26,6,26,18,8,18,12,99,0
8203 DATA 6,6,6,11,9,18,9,23,12,1,12,13,20,8,20,13,99,0
8204 DATA 3,18,6,20,6,27,9,13,9,25,12,29,15,4,15,17,15,29,18,4,99,0,5,7,5,31,8,8,8,23,11,1,11,30,14,0,20,7,20,31
8300 DATA 9,13,18,3,18,13,18,23,99,0
8301 DATA 3,3,3,13,3,23,3,26,12,13,99,0
8302 DATA 6,8,6,18,15,8,15,18,99,0
8303 DATA 99,0
8304 DATA 3,8,3,13,3,23,6,13,9,18,12,18,15,23,18,28,99,0
8305 DATA 2,4,2,17,5,10,8,16,11,13,14,13,17,4,17,13,20,31
8400 DATA 3,26,6,24,6,25,9,1,9,11,15,16,15,26,18,0,18,2,99,0,3,16,9,5,12,21,13,0,13,4,15,20,18,20,18,23,99,0
8401 DATA 3,20,6,0,6,11,12,0,12,4,99,0,6,6,12,16,12,26,14,0,14,5,99,0
8402 DATA 3,16,6,1,6,27,9,16,9,27,12,31,15,21,18,5,18,27,99,0,2,20,5,0,5,29,8,12,11,6,11,24,14,16,17,1,17,28
8500 DATA 3,24,3,26,6,20,9,3,9,5,12,19,15,2,18,18,18,26,99,0,6,25,9,15,9,18,12,0,12,3,15,16,18,0,99,0
8501 DATA 3,2,3,5,3,14,3,16,12,11,15,24,15,25,18,10,99,0,6,0,6,6,6,11,12,27,15,9,99,0
8503 DATA 3,10,3,20,3,29,6,22,9,16,12,16,15,4,15,20,18,3,18,20,99,0,2,2,2,24,5,15,8,10,11,1,11,20,11,28,14,2,17,29
8509 LET c=3*(2+(INT (RND*5)))-1: LET d=INT (RND*32): LET x=19: LET y=0: LET x1=x: LET y1=y: LET a=2: LET b=INT (RND*32)
8510 PRINT #0; PAPER 1; INK 4;AT 0,0;"\ : \ :\ : \ :"
8515 PRINT #0; INK 6; PAPER 1;AT 0,4;"SCORE 00000"; INK 5;AT 1,1;"HI SCORE 00000"; INK 4;AT 1,20;"LIVES"
8516 PRINT #0;AT 0,15-LEN STR$ s; INK 6; PAPER 1;s;AT 1,15-LEN STR$ h; INK 5; PAPER 1;h
8590 OVER 1 : INK 8: PAPER 8
8700 LET i=1: PRINT AT x,y;a$(i);AT x+1,y;b$(i);AT a,b;"\k";AT c,d;"\k"
8705 PRINT #0;AT 0,25;: FOR j=1 TO 1: PRINT #0; PAPER 1;" \a";: NEXT j
8710 PRINT #0;AT 1,25;: FOR j=1 TO 1: PRINT #0; INK 5; PAPER 1;" \b";: NEXT j
8715 IF g=1 THEN FOR j=63000 TO 63007: BEEP .1,(PEEK j)-40: FOR i=1 TO 20: NEXT i: NEXT j: LET z=z+1
8716 IF g=1 THEN LET f=f+1: IF f<5 THEN GO TO 8000
8717 IF f=5 THEN GO TO 8800
8720 FOR i=63000 TO 63063
8721 LET n=(PEEK i)-40
8725 BEEP .11,n
8730 IF INKEY$=m$(2) THEN GO TO 2000
8780 NEXT i: GO TO 8720
8800 IF PEEK 63000<>17 THEN RESTORE 9540: FOR i=63000 TO 63063: READ n: POKE i,n: NEXT i
9000 OVER 0: CLS
9004 INK 5: RESTORE 9520
9010 PRINT AT 1,3;"HE";AT 2,2;"HAUNTED";AT 3,2;"BELLTOWER"
9013 PLOT 11,169: DRAW 27,0: PLOT 21,168: DRAW 0,-7
9015 PLOT 86,145: DRAW 7,-7: DRAW 3,0: DRAW 0,3: DRAW -85,0
9090 PLOT 0,0: DRAW 255,0: DRAW 0,175: DRAW -255,0: DRAW 0,-175
9092 PLOT 64,0: DRAW 17,112: DRAW 2,-24: DRAW 24,22
9100 PLOT 102,0
9110 FOR m=1 TO 5: READ i,j: DRAW i,j: NEXT m
9130 PLOT 165,44: DRAW 90,-44
9132 FOR m=1 TO 6: READ i,j: PLOT 64+i,j: DRAW -2,12: DRAW 4,4: DRAW 4,-8: DRAW 2,-12: DRAW -7,3: NEXT m
9140 FOR m=1 TO 2: READ i,j: PLOT i+64,j: DRAW 1,12: DRAW 3,8: DRAW 3,-4: DRAW -1,-12: DRAW -5,-4: NEXT m
9144 PRINT INK 2;AT 11,21;"\k"
9145 PLOT 164,60: DRAW -35,44,4.5
9147 LET f=0: LET g=0: INK 7
9148 PRINT #0;AT 1,1; INK 2;"0=SCREEN DEMO 1=START GAME"
9150 FOR i=63063 TO 63000 STEP -1: LET n=(PEEK i)-40
9157 BEEP .13,n
9158 IF INKEY$<>"" THEN GO TO 9160
9159 NEXT i: GO TO 9150
9160 IF INKEY$="0" THEN LET g=1: GO TO 6005
9170 IF INKEY$="1" THEN GO TO 6005
9175 GO TO 9150
9200 CLS : PRINT INK 3;AT 0,0;"\k THE HAUNTED BELLTOWER \o"
9201 PRINT INK 5;AT 4,0;"The mischievous ghosts have taken down the bells and left them lying all around."
9205 PRINT INK 6;"Help the rabbit to collect them.Each bell he picks up will fly to it's proper place."
9206 PRINT INK 4;"If he collects them all, he can pass through the door on the topplatform to the next room."
9207 PRINT INK 5;'"There are 5 different rooms."
9208 GO SUB 9400
9210 PRINT INK 5;AT 4,2;"Don't let the bats bite him or he will turn into a bat too !"
9211 PRINT INK 4;AT 11,10;"\k \k \k"
9220 GO SUB 9400: RETURN
9402 PRINT #0;AT 0,1;"\c";AT 1,1; INK 5;"\d Press any key to continue"
9440 PAUSE 1: PAUSE 0
9455 IF INKEY$<>"" THEN GO TO 9455
9460 CLS : RETURN
9520 DATA 6,138,5,-28,38,-21,2,24,18,-112
9521 DATA 51,84,66,76,81,68
9522 DATA 110,20,125,12,140,4
9525 DATA 16,63,29,73
9540 DATA 17,29,41,53,53,41,29,17,19,31,43,55,55,43,31,19,22,34,46,58,58,46,34,22,24,36,48,60
9541 DATA 60,48,36,24,24,60,48,36,22,58,46,34,19,55,43,31,17,53
9542 DATA 41,29,53,17,29,41,55,19,31,43,58,22,34,46,60,24,36,48
9804 INK 3
9805 RESTORE 9860: READ nk
9807 LET m$="": CLS
9808 PRINT AT 4,3;"CHOOSE USER DEFINED KEYS:"''''
9810 FOR i=1 TO nk
9811 READ d$: PRINT " ";d$'': NEXT i: INK 6
9815 RESTORE 9860: READ nk
9816 PRINT AT 0,0;: PRINT '''''''
9818 FOR i=1 TO nk: READ d$
9819 LET m$=m$+CHR$ 0
9820 PRINT '" ";d$;
9822 FOR j=1 TO 12-LEN d$: PRINT "_";: NEXT j
9825 PRINT FLASH 1;"?";CHR$ 8;
9827 PAUSE 1: PAUSE 0
9830 LET k$=INKEY$
9832 FOR j=1 TO LEN m$
9833 IF m$(j)=k$ THEN GO TO 9827
9834 NEXT j
9840 LET m$(i)=k$: PRINT k$: BEEP .05,30: NEXT i: INK 7
9858 IF INKEY$<>"" THEN GO TO 9858
9859 RETURN
9860 DATA 3,"LEFT","RIGHT","CLIMB ROPE"
9990 STOP
9995 STOP
9997 SAVE "Belltower" LINE 1
9998 STOP
9999 VERIFY "Belltower"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.


