Chomper is a Pac-Man-style maze game in which the player navigates a character through a dot-filled maze, eating pellets and special power items while being pursued by two ghosts. The maze layout is stored entirely as a 21×21 string array (B$), with each cell holding a character that encodes both the displayed graphic and the collision type. Twenty-one User Defined Graphics are loaded from DATA statements at startup to render the maze walls, player, and ghost sprites. Ghost movement is driven by a probability-gated chase algorithm that compares ghost row/column coordinates to the player’s position, with boundary checks performed by reading character codes from B$ directly. A “frightened” mode is triggered when the player eats a power pellet (UDG \o), setting a counter that causes ghosts to flash in INK 4 and allows the player to eat them for bonus points; eating a ghost resets its position to the center of the maze.
Program Analysis
Program Structure
The program is organized into a clear set of functional blocks:
- Lines 1–5: Initialization — load UDGs from DATA, set up variables, call the title/skill-level subroutine at 7000.
- Lines 10–50: DATA statements for 21 UDG definitions (chars 144–164), covering maze wall segments, player sprite, ghost sprite, dot, power pellet, and bonus item.
- Lines 55–260: Screen setup and maze construction —
B$()rows are assigned as string literals encoding the maze using UDG escape characters. - Lines 270–550: Main game loop — print maze, poll keyboard, move player, handle pickups, move ghosts, check collisions, redisplay.
- Lines 1000–1030: Random bonus item placement subroutine.
- Lines 1500–1830: Player movement subroutines for keys 8 (right), 5 (left), 7 (up), 6 (down).
- Lines 2000–2050: Power-pellet activation — sets
C=-1(frightened mode), resets count timer. - Lines 3000–4030 / 6010–6020: Unused or vestigial ghost-pathfinding helper routines (variables D, F, G, H are never initialized in the active code path).
- Lines 5000–5070: Level-complete celebration animation, then loop back to maze reset.
- Lines 7000–7090: Title screen and skill-level selection; skill input is converted to a probability value
aused in ghost movement. - Lines 8000–8010: Player eats a frightened ghost — awards points, resets ghost position.
- Lines 9000–9530: Death handling — checks frightened state before killing player, decrements lives, game-over, high-score update, restart.
Maze Representation
The maze is stored in a DIM B$(21,21) two-dimensional string array. Each element is a single character: a UDG (codes 144–164) for wall segments, "." for a dot, " " for empty space, "/" and "\" for the tunnel wrap-around portals, and special UDGs for power pellets (\o, char 164) and the bonus cherry (\u, char 160). This dual-purpose encoding lets the same array serve as both the display buffer and the collision map, with ghost movement checks reading CODE B$(row,col) directly to determine passability.
Ghost Movement Algorithm
Two ghosts are tracked by coordinates (GX,GY) and (GX1,GY1). At each game loop iteration, the code attempts to move each ghost one cell toward the player’s position (y,x). Each directional move is gated by a random check using RND < a, where a is the skill probability (0.1 for hardest, 0.6 for easiest). A lower value of a means fewer random direction changes and more direct pursuit. Passability is tested by checking that the candidate cell’s character code is not a wall UDG (codes 144–157) and not a tunnel marker (codes 47 for / or 92 for \).
The frightened mode flag is the sign of variable C: C=1 is normal chase; C=-1 is frightened. A counter (COUNT) increments each loop; when it reaches 30 (line 510), C is reset to 1, ending frightened mode.
Collision and Pickup Detection
Line 380 checks if the player’s new cell contains a dot; line 390 checks for a power pellet; line 400 checks for the bonus cherry. Rather than using separate position flags, all state is read directly from B$(Y,X) after the movement subroutine returns and before the cell is cleared to " " at line 405. This means the array is both the display source and the game-state authority.
Ghost–player collision at lines 540–545 compares coordinates. If COUNT <= 30 at death-entry (line 9000), the code branches to line 8000 to treat the collision as the player eating a frightened ghost instead of dying.
UDG Initialization
Lines 1–2 load all 21 UDGs (chars 144–164, i.e., \a through \u) by reading 8 bytes each from the DATA statements at lines 10–55 and POKEing them into USR CHR$ a + b. The RESTORE 0 at line 1 resets the DATA pointer to the very first DATA statement regardless of line order — a reliable way to ensure correct data reading on restart.
Tunnel Wrap-Around
The maze contains left-right tunnel portals. In the rightward movement subroutine (line 1525), if the cell to the right contains "\" (char 92, stored as a literal backslash in the maze row), the player’s x-coordinate is set to 2, wrapping them to the left side. The leftward subroutine (line 1620) mirrors this: detecting "/" wraps the player to column 20. This is a simple hard-coded portal rather than a general mechanism.
Skill Level Selection
At line 7060, the skill level is read via INKEY$ in a tight polling loop. The raw key code minus 49 gives a 0–4 range; then line 7080 adds 1 and divides by 10, yielding values 0.1 through 0.5. This float is stored in variable a, re-using the same variable name as the player sprite character string a$ — but a (numeric) and a$ (string) are distinct in Sinclair BASIC, so there is no conflict. A value of 5 entered by the user yields a = 0.6, making ghosts move more randomly (easier); value 1 yields a = 0.2 for more direct pursuit (harder).
Level Completion and Scoring
The target score threshold is stored in T, initialized to 180 (line 3). Each dot scores 1 point; each power pellet scores 10 and adds 10 to T (line 400); eating a frightened ghost awards 10 points and adds 10 to T. When S = T (line 415), the level-complete routine at 5000 runs a scrolling animation and adds 180 to T before returning to redraw the maze, effectively starting a new level with the same maze refilled.
Bugs and Anomalies
- Subroutines at lines 3000–3040, 4010–4020, 6010–6020 are never called from the active code path. They appear to be remnants of a more sophisticated pathfinding system using variables
D,F,G,Hthat were replaced by the current inline directional chase logic. - Line 430 has a logical precedence ambiguity:
d<144 OR d>157 AND d<>47 AND d<>92. BecauseANDbinds tighter thanORin Sinclair BASIC, this reads asd<144 OR (d>157 AND d<>47 AND d<>92), which is probably the intended meaning (allow passage if code is below UDG range, or above UDG range and not a wall character). However, line 470 correctly uses explicit parentheses:(d<144 OR d>157), showing inconsistency between the two ghost movement blocks. - The variable
aused for skill level probability is never re-initialized on game restart (line 9530 goes to 60, skipping line 7000), so skill level persists across restarts within the same session — this is likely intentional. - Line 5000 prints
AT 10,31which places a character at column 31, at the very right edge of a 32-column display, followed by a power-pellet UDG. This is benign but the character may be partially off-screen on some display configurations.
Variable Summary
| Variable | Purpose |
|---|---|
B$() | 21×21 maze array (display + collision map) |
y, x | Player row, column |
a$ | Current player sprite UDG (directional) |
gx, gy | Ghost 1 row, column |
gx1, gy1 | Ghost 2 row, column |
D$, E$ | Character saved under ghost 1 and 2 (restore on move) |
C | Ghost mode: 1 = normal, -1 = frightened |
COUNT | Frightened mode timer |
s | Current score |
hs | High score |
T | Score target for level completion |
lives | Remaining lives |
a | Ghost randomness probability (skill level) |
Content
Source Code
1 RESTORE 0: FOR a=144 TO 164
2 FOR b=0 TO 7: READ c: POKE USR CHR$ a+b,c: NEXT b: NEXT a
3 LET s=0: LET hs=0: LET T=180: DIM B$(21,21): LET COUNT=30
4 LET lives=3
5 GO SUB 7000
10 DATA 0,255,0,0,0,0,0,0,0,0,0,0,0,0,255,0,0,255,0,0,0,0,255,0,0,248,4,2,2,2,2,2
20 DATA 0,31,32,64,64,64,64,64,0,63,64,128,128,64,63,0,0,252,2,1,1,2,252,0
30 DATA 2,2,2,2,2,4,248,0,64,64,64,64,64,32,31,0,2,2,2,2,2,2,2,2
40 DATA 0,24,36,66,66,66,66,66,66,66,66,66,66,36,24,0,64,64,64,64,64,64,64,64,66,66,66,66,66,66,66,66,0,0,16,56,124,56,16,0
50 DATA 24,60,30,15,30,60,24,0,0,0,34,119,127,62,28,8,0,24,60,120,240,120,60,24,16,56,124,254,238,68,0,0,56,126,90,219,255,255,255,147,2,6,10,20,36,68,238,68
55 PAPER 1: BORDER 1: CLS : INK 7: PAPER 0
60 LET B$(1)="\e\a\a\a\a\a\a\a\a\a\n\a\a\a\a\a\a\a\a\a\d"
70 LET B$(2)="\m.........\n.........\j"
80 LET B$(3)="\m.\e\a\d.\e\a\d.\n.\e\a\d.\e\a\d.\j"
90 LET B$(4)="\m\o\m \j.\m \j.\n.\m \j.\m \j\o\j"
100 LET B$(5)="\m.\i\b\h.\i\b\h.\l.\i\b\h.\i\b\h.\j"
110 LET B$(6)="\m...................\j"
120 LET B$(7)="\m.\f\c\g.\k.\f\c\a\c\g.\k.\f\c\g.\j"
130 LET B$(8)="\m.....\n...\n...\n.....\j"
140 LET B$(9)="\i\b\b\b\b.\m\c\g.\l.\f\c\j.\b\b\b\b\h"
150 LET B$(10)=" \j.\n.......\n.\m "
160 LET B$(11)="\b\b\b\b\h.\l.\e---\d.\l.\i\b\b\b\b"
170 LET B$(12)="/.......\m \j.......\"
180 LET B$(13)="\a\a\a\a\d.\k.\i---\h.\k.\e\a\a\a\a"
190 LET B$(14)=" \j.\n.... ..\n.\m "
200 LET B$(15)="\b\b\b\b\h.\l.\f\c\a\c\g.\l.\i\b\b\b\b"
210 LET B$(16)="\m.........\n.........\j"
220 LET B$(17)="\m\o\f\c\d.\f\c\g.\l.\f\c\g.\e\c\g\o\j"
230 LET B$(18)="\m...\n...........\n...\j"
240 LET B$(19)="\a\a\d.\l.\f\c\c\c\c\c\c\c\g.\l.\e\a\a"
250 LET B$(20)=" \j...............\m "
260 LET B$(21)=" \a\a\a\a\a\a\a\a\a\a\a\a\a\a\a "
270 FOR N=1 TO 21: PRINT AT N,1;B$(N): BEEP .05,N+15: NEXT N
280 LET y=14: LET x=12: LET a$="\r"
290 LET gx=12: LET gy=12: LET gx1=12: LET gy1=11: LET C=1: LET D$=" ": LET E$=" "
295 PRINT AT 0,19;"HI-SCORE=";hs
300 PRINT AT gx,gy; INK 3;"\t"
310 PRINT AT gx1,gy1; INK 5;"\t"
320 PRINT AT y,x; INK 6;a$
330 IF INT (RND*100)=0 THEN GO SUB 1000
340 IF INKEY$="8" THEN GO SUB 1500
350 IF INKEY$="5" THEN GO SUB 1600
360 IF INKEY$="7" THEN GO SUB 1700
370 IF INKEY$="6" THEN GO SUB 1800
380 IF B$(Y,X)="." THEN LET s=s+1: BEEP .05,25
390 IF B$(Y,X)="\o" THEN LET s=s+10: BEEP .1,25: BEEP .1,39: GO SUB 2000
400 IF B$(Y,X)="\u" THEN LET s=s+10: LET T=T+10: BEEP .1,28: BEEP .1,36
401 PRINT AT 0,0;"LIVES=";lives;" SCORE=";S
405 LET B$(Y,X)=" "
410 PRINT AT y,x; INK 6;a$
415 IF S=T THEN GO TO 5000
420 PRINT AT GX,GY;D$;AT GX1,GY1;E$
425 IF RND<a THEN GO TO 440
430 IF gy<x THEN LET d=CODE b$(gx,gy+c): IF d<>47 AND d<>92 AND d<144 OR d>157 AND d<>47 AND d<>92 THEN LET gy=gy+c: GO TO 470
435 IF RND<a THEN GO TO 450
440 IF gy>x THEN LET d=CODE b$(gx,gy-c): IF d<>47 AND d<>92 AND d<144 OR d>157 AND d<>47 AND d<>92 THEN LET gy=gy-c: GO TO 470
445 IF RND<a THEN GO TO 460
450 IF gx<y THEN LET d=CODE b$(gx+c,gy): IF d<>47 AND d<>92 AND d<144 OR d>157 AND d<>47 AND d<>92 THEN LET gx=gx+c: GO TO 470
455 IF RND<a THEN GO TO 470
460 IF gx>y THEN LET d=CODE b$(gx-c,gy): IF d<>47 AND d<>92 AND d<144 OR d>157 AND d<>47 AND d<>92 THEN LET gx=gx-c
465 IF RND<a THEN GO TO 475
470 IF gy1<x THEN LET d=CODE b$(gx1,gy1+c): IF d<>47 AND d<>92 AND (d<144 OR d>157) THEN LET gy1=gy1+c: GO TO 500
472 IF RND<a THEN GO TO 480
475 IF gy1>x THEN LET d=CODE b$(gx1,gy1-c): IF d<>47 AND d<>92 AND d<144 OR d>157 AND d<>47 AND d<>92 THEN LET gy1=gy1-c: GO TO 500
477 IF RND<a THEN GO TO 490
480 IF gx1<y THEN LET d=CODE b$(gx1+c,gy1): IF d<>47 AND d<>92 AND d<144 OR d>157 AND d<>47 AND d<>92 THEN LET gx1=gx1+c: GO TO 500
485 IF RND<a THEN GO TO 500
490 IF gx1>y THEN LET d=CODE b$(gx1-c,gy1): IF d<>47 AND d<>92 AND d<144 OR d>157 AND d<>47 AND d<>92 THEN LET gx1=gx1-c
500 LET COUNT=COUNT+1
510 IF COUNT>=30 THEN LET C=1
520 IF C=-1 THEN PRINT AT GX,GY; INK 4; FLASH 1;"\t"
525 IF C=1 THEN PRINT AT GX,GY; INK 3;"\t"
530 IF C=-1 THEN PRINT AT GX1,GY1; INK 4; FLASH 1;"\t"
535 IF C=1 THEN PRINT AT GX1,GY1; INK 5;"\t"
540 IF GX=y AND GY=x THEN GO TO 9000
545 IF GX1=y AND gy1=X THEN GO TO 9000
550 FLASH 0: LET D$=B$(GX,GY): LET E$=B$(GX1,GY1): GO TO 330
1000 IF B$(14,11)="\u" THEN RETURN
1010 PRINT AT 14,11; INK 2;"\u": BEEP .5,15: IF B$(14,11)="." THEN LET T=T-1
1020 LET B$(14,11)="\u"
1030 RETURN
1500 PRINT AT Y,X;" "
1505 IF B$(y,x+1)="." THEN LET x=x+1: GO TO 1530
1510 IF B$(y,x+1)=" " THEN LET X=X+1: GO TO 1530
1520 IF b$(y,x+1)="\u" THEN LET x=x+1
1525 IF B$(y,x+1)="\" THEN LET x=2
1530 LET a$="\r"
1540 RETURN
1600 PRINT AT Y,X;" "
1605 IF B$(y,x-1)="." THEN LET x=X-1: GO TO 1630
1610 IF B$(y,x-1)=" " THEN LET x=X-1: GO TO 1630
1615 IF B$(y,x-1)="\o" THEN LET x=X-1: GO TO 1630
1620 IF B$(y,x-1)="/" THEN LET x=20: GO TO 1630
1625 IF B$(y,x-1)="\u" THEN LET x=X-1
1630 LET a$="\p"
1640 RETURN
1700 PRINT AT Y,X;" "
1705 IF B$(y-1,x)="." THEN LET y=y-1: GO TO 1725
1710 IF B$(y-1,x)="\o" THEN LET y=y-1: GO TO 1725
1720 IF B$(y-1,x)=" " THEN LET y=y-1
1725 LET a$="\q"
1730 RETURN
1800 PRINT AT y,x;" "
1805 IF B$(y+1,x)="." THEN LET y=y+1: GO TO 1825
1810 IF B$(y+1,x)="\o" THEN LET y=y+1: GO TO 1825
1815 IF B$(y+1,x)=" " THEN LET y=y+1
1825 LET a$="\s"
1830 RETURN
2000 LET C=-1
2010 LET B$(Y,X)=" "
2020 PRINT AT Y,X;A$
2030 LET T=T+10
2040 LET COUNT=0
2050 RETURN
3000 IF Y>D THEN LET D=D+G
3010 IF X>F THEN LET F=F+H
3020 IF X<F THEN LET F=F-H
3030 IF Y<D THEN LET D=D-G
3040 RETURN
4010 LET D=GX: LET F=GY: RETURN
4020 LET D=GX1: LET F=GY1
4030 RETURN
5000 CLS : PRINT AT 10,31;"\o"
5010 FOR N=1 TO 27
5020 PRINT AT 10,N; INK 5;" \t "; INK 7;"\r"
5030 BEEP .05,N: NEXT N
5040 FOR N=27 TO 1 STEP -1: PRINT AT 10,N; FLASH 1; INK 4;"\t"; FLASH 0; INK 7;" \p "
5050 BEEP .05,N: NEXT N
5060 LET T=T+180
5070 GO TO 60
6010 LET GX=D: LET GY=F: RETURN
6020 LET GX1=D: LET GY1=F: RETURN
7000 PRINT AT 0,12;"CHOMPER";TAB 11;" \''\''\''\''\''\''\'' "
7010 PRINT AT 9,0;"\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a"
7020 PRINT AT 11,3;"SELECT SKILL LEVEL (1-5) "
7030 PRINT TAB 7;"(5 is the easiest)"
7040 PRINT AT 14,0;"\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a"
7060 LET a=CODE INKEY$-49
7070 IF a>5 OR a<0 THEN GO TO 7060
7080 LET a=a+1: LET a=a/10
7090 RETURN
8000 IF gy=x AND gx=y THEN LET s=s+10: LET t=t+10: LET gy=12: LET gx=12: BEEP .05,20: BEEP .05,10: BEEP .1,10: GO TO 550
8010 IF gy1=x AND gx1=y THEN LET s=s+10: LET t=t+10: LET gy1=11: LET gx1=12: BEEP .1,10: GO TO 550
9000 IF count<=30 THEN GO TO 8000
9005 LET LIVES=LIVES-1
9010 PRINT AT Y,X; FLASH 1;A$: FOR N=50 TO 0 STEP -1: BEEP .05,N: NEXT N
9020 IF lives<=0 THEN GO TO 9500
9030 GO TO 270
9500 IF HS<S THEN LET HS=S
9510 PRINT AT 10,0;"HIT ANY KEY TO RESTART"
9520 IF INKEY$="" THEN GO TO 9520
9530 PAPER 1: LET S=0: CLS : PAPER 0: LET T=180: LET lives=3: GO TO 60
9998 SAVE "Chomper" LINE 1: BEEP .4,15
9999 VERIFY ""
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

