DODGEM is a maze-avoidance game in which the player steers a custom sprite character through a randomly-generated field of colored obstacles, using the cursor keys (5/6/7/8), while surviving as many ticks as possible without crossing their own path or hitting a wall. The player sprite is defined as a user-defined graphic (UDG) via POKE into USR “p”, with binary data supplied through a DATA/READ loop at line 2000 that converts BIN strings using VAL and STR$. Collision detection is handled entirely through ATTR, checking the color attribute of the destination cell at line 90; a cell with attribute value 48 (green paper, black ink — the border color combination for the player’s trail dot) signals a clear path, while any other value triggers the death sequence. The game tracks a high score in variable hs, initialized to 365, and preserves it across rounds within the same session. Difficulty is controlled by adjusting both the delay loop length q and the BEEP pitch step d, making higher difficulty numbers produce faster, more audible gameplay.
Program Structure
The program is organized into four logical sections:
- Initialization and intro (lines 5, 2000–2050): Sets the high score, defines the UDG, displays instructions, and waits for a keypress.
- Game setup (lines 10–40): Reads difficulty, resets variables, draws the border and random obstacles, and places the player.
- Main game loop (lines 50–90): Reads input, moves the player, performs collision detection via ATTR, and updates the display.
- Death and lives handling (lines 100–150, 1000–1010): Plays a crash sound sequence, decrements lives, and either restarts the round or ends the game.
UDG Definition
Line 2000 defines the player sprite by poking eight bytes into the address range beginning at USR "p". The data is stored as decimal representations of binary numbers (e.g., 11000, 111100), and each is converted with VAL ("BIN "+STR$ a) — a technique that avoids storing binary literals directly in DATA statements while still being human-readable as bit patterns. The UDG is then referenced in PRINT statements as "\p".
Collision Detection via ATTR
Rather than maintaining an explicit map array, the game uses the ATTR function at line 90 to read the color attribute of the cell the player is about to move into. If the attribute equals 48, the cell is considered free and play continues. The value 48 corresponds to a specific paper/ink combination used for the trail dot printed at the previous position ("." on a default background), making the attribute itself the game-state record.
Difficulty Scaling
Difficulty input d (1–6, where 1 is hardest) is used in two ways after line 20: q=d+5 sets the length of a delay FOR loop at line 70 (lower d = shorter delay = faster game), and d=d/100 repurposes the variable as the BEEP duration argument in line 90. The input validation at line 10 rejects non-integers and out-of-range values with a warning beep and a retry loop.
Main Loop Mechanics
The main loop at lines 50–90 follows this sequence each tick:
- Read
INKEY$and update direction variablesa(column delta) andb(row delta) using Boolean arithmetic:(i$="6")-(i$="7"). - Print the time counter at line 60.
- Pause via a counted
FORloop (line 70). - Emit a BEEP, increment time, update position, read ATTR, print the sprite and leave a trail dot, then branch on the attribute check.
Direction is sticky: the variables a and b are only updated when a valid key in the range “5”–”8″ is detected, so the player continues in the last-pressed direction when no key is held.
Obstacle and Arena Generation
The border is drawn with a FOR loop printing INK 3 (yellow) block characters at line 20. Forty-five random colored patches are then scattered across the interior at line 30 using PAPER RND*3+1, creating a varied field of obstacles. These are purely attribute-based; any non-48 attribute in the player’s path triggers a collision.
Death Sequence and Lives
On collision, lines 100–110 play an escalating BEEP sequence (increasing pitch and duration), decrement l, and print the remaining lives. If lives remain (IF L at line 110, note the uppercase L which references the same variable as lowercase l), control jumps to line 1000, which flashes a taunt message, redraws a portion of the maze with transparent (PAPER 8) patches, then returns to line 40 to restart the round. When all lives are exhausted, the game checks for a new high score and waits for a keypress before looping back to line 10.
Notable Techniques and Anomalies
- The
VAL ("BIN "+STR$ a)idiom in the UDG loader elegantly converts decimal-encoded binary numbers without requiring theBINkeyword directly in DATA statements. - The DATA statement at line 2000 includes a bare
a(appearing twice) instead of a numeric literal, which will evaluate to whatever the current value of variableais at runtime — this is likely a bug, as those rows should probably be0or some specific bit pattern. Sinceais not initialized before the subroutine is called from line 5, its value is 0 by default, so the UDG rows default to 0 (blank), which may be intentional for padding rows but is fragile. - The case mismatch
IF L THENat line 110 vs. the lowercaselused elsewhere is valid on this platform (variable names are case-insensitive in the interpreter), but is inconsistent in the listing. - The keypress wait idiom
IF INKEY$<>"" THEN GO TO ...followed byIF INKEY$="" THEN GO TO ...(lines 130–140, 2030–2040) is a standard flush-then-wait pattern ensuring the player must release and re-press a key to proceed. RESTOREat the start of line 2000 ensures the DATA pointer is reset to the beginning regardless of how the subroutine is entered, making the UDG initialization safe to call only once at startup.
Source Code
5 LET hs=365: GO SUB 2000
10 INK 0: PAPER 6: BORDER 1: CLS : INPUT "DIFFICULTY (1 TO 6-EASIEST)";d: IF d<1 OR d>6 OR d>INT d THEN BEEP 1,0: GO TO 10
20 LET t=0: LET l=5: LET q=d+5: LET d=d/100: FOR f=0 TO 31: PRINT AT 0,f; INK 3;"█";AT 21,f;"█": IF f<22 THEN PRINT INK 3;AT f,0;"█";AT f,31;"█"
30 NEXT f: FOR f=1 TO 45: PRINT PAPER RND*3+1;AT RND*19+1,RND*29+1;" ": NEXT f: PRINT PAPER 3; INK 7;AT 0,16;"HIGH ";hs
40 BEEP 1,0: LET a=1: LET b=0: LET x=15: LET y=10: FOR f=9 TO 13: PRINT AT f,14;" ": NEXT f
50 LET i$=INKEY$: IF i$>"4" AND i$<"9" THEN LET a=(i$="6")-(i$="7"): LET b=(i$="8")-(i$="5"): BEEP .005,x
60 PRINT AT 0,0; INK 7; PAPER 3;"TIME ";t
70 FOR f=1 TO q: NEXT f
90 BEEP d,x: LET t=t+1: LET y=y+a: LET x=x+b: LET c=ATTR (y,x): PRINT AT y,x;"\p";AT y-a,x-b;".": IF c=48 THEN GO TO 50
100 LET du=.005: FOR f=1 TO 20: BEEP du,f: LET du=du+.003: NEXT f
110 LET l=l-1: PRINT PAPER 3;AT y,x;" ";AT 21,0; PAPER 3; INK 7;"LIVES ";l: IF L THEN GO TO 1000
120 PRINT #0; FLASH 1; INK 2; PAPER 6;"YOU ARE OUT OF LIVES": IF t>hs THEN PRINT AT 21,0; FLASH 1; PAPER 3; INK 7;"HIGH SCORE": LET hs=t
130 IF INKEY$<>"" THEN GO TO 130
140 IF INKEY$="" THEN GO TO 140
150 GO TO 10
1000 PRINT AT 21,16; FLASH 1; INK 2; PAPER 6;"HA,HA !": FOR y=1 TO 20: PRINT AT y,1; PAPER 8; INK 0;TAB 31;: NEXT y: FOR y=1 TO 15: PRINT AT RND*19+1,RND*29+1; PAPER RND*5;" ": NEXT y
1010 PRINT AT 21,16; PAPER 3;" ": GO TO 40
2000 RESTORE : FOR f=USR "p" TO USR "p"+7: READ a: POKE f,VAL ("BIN "+STR$ a): NEXT f: DATA 11000,a,111100,1011010,1011000,100100,a,0
2010 BORDER 0: PAPER 0: INK 6: CLS
2020 PRINT TAB 9;"DODGEMS"''" You (\p) must dodge your way through the maze,avoiding all obstacles & you mustn't cross your path."''" Move using the cursor keys."
2030 IF INKEY$<>"" THEN GO TO 2030
2040 IF INKEY$="" THEN GO TO 2040
2050 RETURN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

