QBert

This file is part of and CATS Library Tape 1. Download the collection to get this file.
Products: Q*Bert
Developer(s): Werner Kaelin
Date: 1984
Type: Cartridge
Platform(s): TS 2068
Tags: Arcade, Game

This program is a BASIC implementation of the arcade game Q*bert, where the player navigates a character across a diamond-shaped board while avoiding enemies named Sammy and the Snake. Movement is controlled via Q/W/A/S keys or a joystick (read via the STICK function), with diagonal movement dispatched through a computed GOSUB using PEEK 23560. The program makes extensive use of machine code routines loaded at addresses 65200–65330 for animation and sound effects, called via USR. A ten-entry high-score table with name entry is maintained in a string array, and UDGs (user-defined graphics) are used for all character sprites.


Program Analysis

Program Structure

The program is organized into several well-defined functional blocks:

  1. Lines 999, 5800–5840: Entry point; sets up border/colors and displays a title/credits screen.
  2. Lines 6710–6890: Initialization — plays intro music, dimensions the high-score array, defines sprite strings using UDG escape sequences, and falls through to the attract loop.
  3. Lines 5500–5790: Attract/menu loop — cycles through character showcase, controls help, scoring info, and the leaderboard, returning to 1000 on keypress.
  4. Lines 1000–1100: Main game loop — initializes variables, draws HUD, plays level start tune, then repeatedly reads input, dispatches movement, moves enemies, and redraws.
  5. Lines 1500–2099: Movement subroutines for four diagonal directions (down-left, up-left, down-right, up-right), each separated by a dummy RETURN at the base of the group.
  6. Lines 2800–2980: Enemy AI for Sammy (the bouncer) and the Snake, using random direction selection each turn.
  7. Lines 3000–3040: Death sequence — plays sounds, animates, decrements lives.
  8. Lines 3500–3540: Egg collection — increments score, checks for level completion.
  9. Lines 4000–4070: Enemy-collision death sequence with explosion effect.
  10. Lines 5000–5240: Game over and high-score entry.
  11. Lines 9900–9970: Save/load block for the BASIC program and associated machine code.

Input Dispatching via Computed GOSUB

The key input mechanism is a notable idiom. Line 1075 reads the joystick state via STICK (1,1) and stores an ASCII key code into system variable address 23560:

POKE 23560, (113*(j=5) + 119*(j=9) + 97*(j=6) + 115*(j=10))

The values 113, 119, 97, 115 correspond to the ASCII codes for Q, W, A, S respectively. Line 1050 then dispatches to a movement subroutine with:

GO SUB PEEK 23560 * 5 + 1500

The mapping is:

KeyASCIIPEEK*5+1500Target lineDirection
Q11320652065Up-left
W11920952095Up-right
A9719851985Down-left
S11520752075Down-right
(none)015001500No-op RETURN

Dummy RETURN statements are placed at lines 1984, 2064, 2074, and 2094 to catch any off-by-one addresses that might land between valid entry points. Line 1500 itself is the no-input no-op.

Machine Code Usage

Several calls to USR at high memory addresses appear throughout the program. These invoke pre-loaded machine code routines saved separately as a CODE block (see line 9910: SAVE "Q-BERT" CODE 58280,7225). The routines and their apparent purposes are:

AddressUsage contextApparent purpose
65200After movement delta appliedDelay / animation frame
65230Death/collision animation loopScreen flash or shake effect
65250Death animation loopFurther animation frame
65275Death animation loopFurther animation frame
65312Level init and introBoard drawing / initialization
65330On death/collisionFlash/explosion effect trigger

The return value of each USR call is assigned to the dummy variable l (e.g., LET l=USR 65312) purely to satisfy BASIC syntax, as the results are not used.

Sprite Representation

All character sprites are two rows of two UDG characters each, stored in short strings. The variables a$ through i$ are initialized at lines 6800–6820 using UDG escape sequences (e.g., \a\b for the first row of the hero). The hero uses UDGs \a–\d for display and erase (blank-tile) versions, enemies use \e–\l and \n–\q, and a single-character UDG \m is used for Sammy’s erase frame. The program draws and erases sprites by printing with OVER 1, which XORs pixels — printing the same image twice restores the background.

Enemy AI

Both enemies (Sammy at bx,by and the Snake at sx,sy) use the same movement strategy each game tick in subroutine 2800–2980. A random direction is chosen via GO TO RND*4+2801 (or 2901), selecting one of four diagonal moves. Boundary and screen-content checks prevent movement off the board. The Snake has an additional behavior (line 2970): with a probability that increases with round number (.9-ro/200), it drops an “o” marker on a vacated cell, deducting 50 from the field counter fi — effectively placing obstacles for the player.

Collision Detection

Collision detection is performed entirely via SCREEN$ character reads rather than coordinate comparison. Movement routines check SCREEN$ (qy+1, qx)=" " to detect falling off the board (empty space below), and check adjacent cells for valid landing squares. Enemy-to-player collision is checked by coordinate equality (e.g., bx=qx AND by=qy) after each enemy move.

High-Score Table

The leaderboard uses DIM w$(12,24) — a 12-row by 24-character string array. Only 10 entries are displayed; the extra rows serve as overflow guards. Scores are left-padded to 6 digits with "000000"(TO 6-LEN STR$ sc)+STR$ sc for lexicographic comparison. The insertion sort at lines 5050–5070 walks down the table comparing string values to find the insertion point.

HUD and Lives Display

Lives remaining are displayed as pairs of UDG characters — "\a\b\a\b\a\b"(TO li*2) — which neatly scales the display to show exactly li sprite pairs by using a string slice. Score is right-justified by printing at column 7-LEN STR$ sc.

Level Progression

The field counter fi tracks egg collection progress. Collecting an egg (an “o” character detected by SCREEN$) adds 50 to both sc and fi. When fi reaches 1350 (27 eggs × 50 points), the level-complete sequence fires at line 3510, awarding a 500-point bonus and incrementing the round counter ro (capped at 120), which influences enemy speed and Snake drop probability.

Notable Anomalies

  • Line 5010 uses variable a in DATA statements (e.g., a,0, a*2,7). This is legal in Spectrum BASIC — READ evaluates expressions, so a is read as the current numeric variable. The value of a is set to .135+q/500 at line 5100 before RESTORE 5010 is called, making the tune tempo dependent on leaderboard rank.
  • The GO TO RND*4+2801 idiom generates a non-integer target; BASIC truncates it, so valid targets are 2801–2804 and 2901–2904 — exactly the four direction sub-blocks.
  • Line 1037 uses INPUT "" as a one-frame pause to allow the display to refresh before the game loop starts, rather than for actual user input.

Content

Appears On

Capital Area Timex Sinclair User Group’s Library Tape.

Related Products

Implementation of the arcade game. Cartridge.

Related Articles

Related Content

Image Gallery

QBert

Source Code

  999 GO TO 5800
 1000 POKE 23658,0: LET ro=1: LET bon=0: LET sc=0: LET li=3
 1010 CLS : LET l=USR 65312: LET fi=0: LET qx=16: LET qy=1: LET bx=4: LET by=19: LET sx=28: LET sy=19: LET qx1=qx: LET qy1=qy: LET bx1=bx: LET by1=by: LET sx1=sx: LET sy1=sy
 1015 POKE 23560,0: PAPER 1: PRINT AT 1,7-LEN STR$ sc; INK 6;sc;AT 4,30-LEN STR$ ro; INK 6;ro;AT 1,32-li*2; INK 6;"\a\b\a\b\a\b"( TO li*2);AT 2,32-li*2; INK 6;"\c\d\c\d\c\d"( TO li*2): PAPER 0: IF bon THEN GO TO 1035
 1020 RESTORE 1030: FOR q=1 TO 16: READ a: BEEP .095+li/100,a: NEXT q: LET bon=0
 1030 DATA 20,16,12,10,20,16,12,10,20,16,19,23,19,16,12,10
 1035 RANDOMIZE : PRINT OVER 1; INK 5;AT 18,4;e$;AT 19,4;f$; INK 6;AT 18,28;h$;AT 19,28;i$
 1037 INPUT ""
 1040 PRINT OVER 1;AT qy-1,qx;a$;AT qy,qx;b$; INK 5;AT by-1,bx;e$;AT by,bx;f$; INK 6;AT sy-1,sx;h$;AT sy,sx;i$
 1050 GO SUB PEEK 23560*5+1500: POKE 23560,0
 1060 PRINT OVER 1;AT qy-1,qx;c$;AT qy,qx;d$
 1070 PRINT OVER 1;AT qy-1,qx;a$;AT qy,qx;b$; INK 5;AT by-1,bx;e$;AT by,bx;f$; INK 6;AT sy-1,sx;h$;AT sy,sx;i$
 1075 LET j= STICK (1,1): POKE 23560,(113*(j=5)+119*(j=9)+97*(j=6)+115*(j=10))
 1080 GO SUB 2800
 1090 PRINT OVER 1;AT qy-1,qx;c$;AT qy,qx;d$
 1100 GO TO 1040
 1500 RETURN 
 1984 RETURN 
 1985 PRINT OVER 1;AT qy-1,qx;a$;AT qy,qx;b$: LET qx=qx-2: LET qy=qy+3: LET l=USR 65200: IF SCREEN$ (qy+1,qx)=" " OR qy>=21 THEN GO TO 3000
 1986 IF SCREEN$ (qy-3,qx+2)="" THEN PRINT OVER 1;AT qy-4,qx+2;a$;AT qy-3,qx+2;b$: GO TO 1986
 1987 IF qy=by AND qx=bx OR qy=sy AND qx=sx THEN LET qx1=qx: LET qy1=qy: LET qx=qx+2: LET qy=qy-3: GO TO 4000
 1988 LET qx1=qx: LET qy1=qy: IF SCREEN$ (qy,qx)="o" THEN GO SUB 3500
 1989 RETURN 
 2064 RETURN 
 2065 PRINT OVER 1;AT qy-1,qx;a$;AT qy,qx;b$: LET qx=qx-2: LET qy=qy-3: LET l=USR 65200: IF SCREEN$ (qy+1,qx)=" " OR qy<=0 THEN GO TO 3000
 2066 IF SCREEN$ (qy+3,qx+2)="" THEN PRINT OVER 1;AT qy+2,qx+2;a$;AT qy+3,qx+2;b$: GO TO 2066
 2067 IF qy=by AND qx=bx OR qy=sy AND qx=sx THEN LET qx1=qx: LET qy1=qy: LET qx=qx+2: LET qy=qy+3: GO TO 4000
 2068 LET qx1=qx: LET qy1=qy: IF SCREEN$ (qy,qx)="o" THEN GO SUB 3500
 2069 RETURN 
 2074 RETURN 
 2075 PRINT OVER 1;AT qy-1,qx;a$;AT qy,qx;b$: LET qx=qx+2: LET qy=qy+3: LET l=USR 65200: IF SCREEN$ (qy+1,qx)=" " OR qy>=21 THEN GO TO 3000
 2076 IF SCREEN$ (qy-3,qx-2)="" THEN PRINT OVER 1;AT qy-4,qx-2;a$;AT qy-3,qx-2;b$: GO TO 2076
 2077 IF qy=by AND qx=bx OR qy=sy AND qx=sx THEN LET qx1=qx: LET qy1=qy: LET qx=qx-2: LET qy=qy-3: GO TO 4000
 2078 LET qx1=qx: LET qy1=qy: IF SCREEN$ (qy,qx)="o" THEN GO SUB 3500
 2079 RETURN 
 2094 RETURN 
 2095 PRINT OVER 1;AT qy-1,qx;a$;AT qy,qx;b$: LET qx=qx+2: LET qy=qy-3: LET l=USR 65200: IF SCREEN$ (qy+1,qx)=" " OR qy<=0 THEN GO TO 3000
 2096 IF SCREEN$ (qy+3,qx-2)="" THEN PRINT OVER 1;AT qy+2,qx-2;a$;AT qy+3,qx-2;b$: GO TO 2096
 2097 IF qy=by AND qx=bx OR qy=sy AND qx=sx THEN LET qx1=qx: LET qy1=qy: LET qx=qx-2: LET qy=qy+3: GO TO 4000
 2098 LET qx1=qx: LET qy1=qy: IF SCREEN$ (qy,qx)="o" THEN GO SUB 3500
 2099 RETURN 
 2798 RETURN 
 2800 LET bx1=bx: LET by1=by: GO TO RND*4+2801
 2801 LET bx=bx+2: LET by=by+3: GO TO 2810
 2802 LET bx=bx+2: LET by=by-3: GO TO 2810
 2803 LET bx=bx-2: LET by=by+3: GO TO 2810
 2804 LET bx=bx-2: LET by=by-3
 2810 IF by<0 OR by>21 THEN LET by=by1: LET bx=bx1: GO TO 2860
 2820 IF SCREEN$ (by+1,bx)=" " THEN LET bx=bx1: LET by=by1: GO TO 2860
 2830 IF bx=qx AND by=qy THEN PRINT OVER 1; INK 7;AT by1-1,bx1;e$;AT by1,bx1;f$; INK 5;AT by-1,bx;e$;AT by,bx;f$: GO TO 4000
 2840 PRINT OVER 1; INK 7;AT by1-1,bx1;e$;AT by1,bx1;f$
 2850 PRINT OVER 1; INK 5;AT by-1,bx;e$;AT by,bx;f$
 2860 BEEP .01,-by: BEEP .01,-bx
 2900 LET sx1=sx: LET sy1=sy: GO TO RND*4+2901
 2901 LET sx=sx-2: LET sy=sy+3: GO TO 2910
 2902 LET sx=sx+2: LET sy=sy-3: GO TO 2910
 2903 LET sx=sx+2: LET sy=sy+3: GO TO 2910
 2904 LET sx=sx-2: LET sy=sy-3
 2910 IF sy<0 OR sy>21 THEN LET sy=sy1: LET sx=sx1: GO TO 2980
 2920 IF SCREEN$ (sy+1,sx)=" " THEN LET sx=sx1: LET sy=sy1: GO TO 2980
 2930 IF sx=qx AND sy=qy THEN PRINT OVER 1; INK 7;AT sy1-1,sx1;h$;AT sy1,sx1;i$; INK 6;AT sy-1,sx;h$;AT sy,sx;i$: GO TO 4000
 2940 PRINT OVER 1; INK 7;AT sy1-1,sx1;h$;AT sy1,sx1;i$
 2950 PRINT OVER 1; INK 6;AT sy-1,sx;h$;AT sy,sx;i$
 2960 IF RND<.9-ro/200 THEN BEEP .01,-sy: BEEP .01,-sx: RETURN 
 2970 IF SCREEN$ (sy1,sx1)<>"o" AND sx1<>sx AND sy1<>sy AND sx1<>bx AND sy1<>by THEN PRINT AT sy1,sx1;"o": BEEP .01,0: BEEP .01,10: LET fi=fi-50
 2980 BEEP .01,-sy: BEEP .01,-sx: RETURN 
 3000 LET l=USR 65330: BEEP .3,10: BEEP .3,0: BEEP .3,-10: FOR q=1 TO 8: LET l=USR 65230: NEXT q: FOR q=1 TO 5: LET l=USR 65250: LET l=USR 65275: NEXT q
 3005 PRINT OVER 1; INK 6;AT 1,32-li*2;"\e\f\e\f\e\f"( TO li*2);AT 2,32-li*2;"\g\h\g\h\g\h"( TO li*2)
 3010 PRINT OVER 1;AT qy1-1,qx1;a$;AT qy1,qx1;b$: IF SCREEN$ (qy1,qx1)="" THEN GO TO 3010
 3020 FOR q=20 TO -20 STEP -1: BEEP .02,q: BEEP .01,q: NEXT q: FOR q=-20 TO 0: BEEP .01,q: NEXT q
 3030 LET li=li-1: IF li<=0 THEN GO TO 5000
 3040 LET bon=0: GO TO 1010
 3500 LET sc=sc+50: BEEP .01,20: BEEP .01,10: PRINT AT qy,qx;" ": PRINT AT 1,7-LEN STR$ sc; PAPER 1; INK 6;sc: LET fi=fi+50: IF fi<1350 THEN RETURN 
 3510 PRINT OVER 1;AT qy1-1,qx1;a$;AT qy1,qx1;b$: IF SCREEN$ (qy1,qx1)="" THEN GO TO 3510
 3515 PRINT OVER 1;AT qy-1,qx;a$;AT qy,qx;b$
 3520 FOR q=1 TO 500 STEP 10: LET sc=sc+10: BEEP .01,0: PRINT AT 1,7-LEN STR$ sc; PAPER 1; INK 6;sc: NEXT q
 3530 FOR q=-20 TO 50 STEP 5: BEEP .1,q: NEXT q
 3540 LET bon=1: LET ro=ro+(1 AND ro<120): GO TO 1010
 4000 LET l=USR 65330: BEEP .3,10: BEEP .3,0: BEEP .3,-10
 4005 PRINT OVER 1; INK 6;AT 1,32-li*2;"\e\f\e\f\e\f"( TO li*2);AT 2,32-li*2;"\g\h\g\h\g\h"( TO li*2)
 4010 PRINT OVER 1;AT qy1-1,qx1;a$;AT qy1,qx1;b$
 4020 IF qx1<26 THEN PRINT AT qy1-1,qx1+2; FLASH 1; BRIGHT 1; PAPER RND*3; INK RND*4+4;"*%*''": GO TO 4040
 4030 PRINT AT qy1-1,qx1+2; FLASH 1; BRIGHT 1; PAPER RND*3; INK RND*4+4;"*%";AT qy1,qx1+2;"!'"
 4040 FOR q=1 TO 8: LET l=USR 65230: NEXT q: FOR q=1 TO 5: LET l=USR 65250: LET l=USR 65275: NEXT q
 4050 FOR q=20 TO -20 STEP -1: BEEP .02,q: BEEP .01,q: NEXT q: FOR q=-20 TO 0: BEEP .01,q: NEXT q
 4060 LET li=li-1: IF li<=0 THEN GO TO 5000
 4070 LET bon=0: GO TO 1010
 5000 LET x$="": PRINT AT 1,30; PAPER 1; INK 6;"  ";AT 2,30;"  "
 5010 DATA a,0,a,2,a/2,3,a/2,2,a,0,a,0,a,2,a/2,3,a/2,2,a,0,a,3,a,5,a*2,7,a,3,a,5,a*2,7,.075,7,.025,8,a/2,7,a/2,5,a/2,3,a/2,2,a,0,.075,7,.025,8,a/2,7,a/2,5,a/2,3,a/2,2,a,0,a,0,a,-5,a*1.5,0,a,0,a,-5,a*1.5,0
 5020 LET x$=x$+"000000"( TO 6-LEN STR$ sc)+STR$ sc: LET q=1
 5050 IF q=11 OR sc=0 THEN GO TO 5240
 5060 IF x$<w$(q, TO 6) THEN LET q=q+1: GO TO 5050
 5070 FOR i=10 TO q STEP -1: LET w$(i+1)=w$(i): NEXT i
 5100 LET w$(q)=x$+"                ": LET a=.135+q/500: RESTORE 5010: FOR f=1 TO 36: READ b,c: BEEP b,c: NEXT f
 5110 CLS : PRINT AT 7,1; INK 4;"Your score is one of the";AT 9,1;"highest today. Enter your name!";AT 14,4; INK 6-INT ((q-1)/2);x$; INK 7;" _________________"
 5120 LET f=11
 5130 LET q$=INKEY$: IF q$="" THEN GO TO 5130
 5150 IF CODE q$=13 THEN GO TO 5240
 5160 IF CODE q$=12 AND f>11 THEN LET w$(q,f-4)=" ": LET f=f-1: PRINT AT 14,f;" ";: IF INKEY$<>"" THEN GO TO 5220
 5170 IF q$<" " OR q$>"z" THEN GO TO 5130
 5180 PRINT AT 14,f;q$: LET w$(q,f-3)=q$: BEEP .01,20: LET f=f+1: IF f>=28 THEN LET f=11
 5220 IF INKEY$<>"" THEN GO TO 5220
 5230 GO TO 5130
 5240 CLS : PRINT AT 11,9; FLASH 1; PAPER RND*3; INK RND*4+4;" GAME OVER ": BEEP 1,-25: BEEP 1,-30
 5500 GO SUB 5780: PRINT AT 6,7; BRIGHT 1;" The persons: "
 5510 PRINT AT 9,6;a$;AT 10,6;b$;"......the Hero"
 5520 PRINT INK 5;AT 12,6;e$;AT 13,6;f$; INK 7;".........Sammy"
 5530 PRINT INK 6;AT 15,6;h$;AT 16,6;i$; INK 7;".....the Snake"
 5540 PAUSE 100: PRINT OVER 1;AT 9,6;c$;AT 10,6;d$;AT 12,6; INK 5;g$: GO SUB 5790: PAUSE 100
 5545 IF INKEY$<>"" THEN GO TO 1000
 5550 GO SUB 5780: PRINT BRIGHT 1;AT 7,7;" To move press: "
 5560 PRINT AT 10,12;"Q   W";AT 11,13;"\ /";AT 12,14;"x";AT 13,13;"/ \";AT 14,12;"A   S"
 5570 PRINT AT 17,6; BRIGHT 1;" Or use joysticks ": GO SUB 5790: PAUSE 200
 5580 IF INKEY$<>"" THEN GO TO 1000
 5620 GO SUB 5780: PRINT AT 7,7; BRIGHT 1;" Scoring: "
 5630 PRINT AT 11,7; INVERSE 1;" 50"; INVERSE 0;" Points for each egg";AT 16,11;"for every new game";AT 14,7; INVERSE 1;"500"; INVERSE 0;" Points bonus": GO SUB 5790: PAUSE 200
 5640 IF INKEY$<>"" THEN GO TO 1000
 5660 CLS : PRINT TAB 3; BRIGHT 1; INK RND*4+4; PAPER RND*3;" The 10 leaders: ": PRINT 
 5680 FOR q=1 TO 10: PRINT 'TAB 3; INK 6-INT ((q-1)/2);w$(q, TO 6); INK 5;w$(q,7 TO ): NEXT q: GO SUB 5790: PAUSE 200
 5720 IF INKEY$="" THEN GO TO 5500
 5740 IF INKEY$<>"" THEN GO TO 1000
 5750 GO TO 1000
 5780 RANDOMIZE : CLS : PRINT AT 3,10; BRIGHT 1; INK RND*4+4; PAPER RND*3;" Q-BERT ": RETURN 
 5790 PRINT #1;" Press any key to start  Q-BERT ": RETURN 
 5800 BORDER 0: PAPER 0: INK 7: CLS 
 5810 LET w$="Q-BERT"
 5820 PRINT AT 3,8; FLASH 1; BRIGHT 1; PAPER RND*3; INK RND*4+4;" HOMECOMPUTER ";AT 5,10; PAPER RND*3; INK RND*4+4;" presents ";AT 18,3; INK 1; PAPER 6; FLASH 0;" adapted by Werner Kaelin "
 5830 FOR q=1 TO 6: PRINT AT 8+q,11+q; FLASH 1; BRIGHT 1; INK RND*6+2;w$(q): NEXT q: POKE 23658,8
 5840 GO SUB 5790: PAUSE 0
 6710 LET l=USR 65312
 6720 DATA 0,6,9,15,9,6,0
 6730 FOR i=40 TO -20 STEP -3: RESTORE 6720: FOR q=1 TO 4: READ a: BEEP .04,a+i: NEXT q: NEXT i: BEEP 1,i+1: BEEP 1,i: BEEP 2,i+5
 6750 DIM w$(12,24): FOR q=1 TO 12 STEP 3
 6760 LET w$(q)="000000 Q-BERT___________"
 6770 LET w$(q+1)="000000 SAMMY____________"
 6780 LET w$(q+2)="000000 THE SNAKE________"
 6790 NEXT q
 6800 LET a$="\a\b": LET b$="\c\d": LET c$="\e\f": LET d$="\g\h"
 6810 LET e$="\i\j": LET f$="\k\l": LET g$="\m"
 6820 LET h$="\n\o": LET i$="\p\q"
 6890 GO TO 5500
 9000 STOP 
 9900 CLEAR : SAVE "Q-BERT" LINE 9940
 9910 SAVE "Q-BERT"CODE 58280,7225
 9930 STOP 
 9940 CLEAR 58200: LOAD "Q-BERT"CODE 
 9970 GO TO 5800

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

Scroll to Top