---
title: "Chomper"
id: 53790
type: "computer_media"
slug: "chomper"
url: "http://localhost/computer_media/chomper/"
markdown_url: "http://localhost/computer_media/chomper.md"
published_at: "2024-05-13T18:56:52+00:00"
modified_at: "2026-03-31T07:21:35+00:00"
author: "David Anderson"
featured_image:
url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg"
excerpt: "A Pac-Man maze game squeezes dot-eating, two chasing ghosts, power pellets, skill levels, and high-score tracking into pure BASIC."
category:
- name: "Archived Media"
slug: "archived-media"
taxonomy: "category"
url: "http://localhost/category/archived-media/"
post_tag:
- name: "Downloadable"
slug: "downloadable"
taxonomy: "post_tag"
url: "http://localhost/tag/downloadable/"
- name: "TS 2068"
slug: "ts2068"
taxonomy: "post_tag"
url: "http://localhost/tag/ts2068/"
model:
- name: "Timex/Sinclair 2068"
slug: "ts-2068"
taxonomy: "model"
url: "http://localhost/model/ts-2068/"
genre:
- name: "Arcade"
slug: "arcade"
taxonomy: "genre"
url: "http://localhost/type/arcade/"
media_contents:
- id: 53877
title: "SINCUS Exchange Tape 101 – Entertainment"
type: "computer_media"
url: "http://localhost/computer_media/sincus-exchange-tape-101-entertainment/"
media_type: "Program"
images:
- url: "http://localhost/wp-content/uploads/2024/05/chomper-2.png"
- url: "http://localhost/wp-content/uploads/2024/05/chomper-1.png"
media_type_tags: "Arcade"
---
# Chomper
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:
1. **Lines 1–5:** Initialization — load UDGs from DATA, set up variables, call the title/skill-level subroutine at 7000.
2. **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.
3. **Lines 55–260:** Screen setup and maze construction — `B$()` rows are assigned as string literals encoding the maze using UDG escape characters.
4. **Lines 270–550:** Main game loop — print maze, poll keyboard, move player, handle pickups, move ghosts, check collisions, redisplay.
5. **Lines 1000–1030:** Random bonus item placement subroutine.
6. **Lines 1500–1830:** Player movement subroutines for keys 8 (right), 5 (left), 7 (up), 6 (down).
7. **Lines 2000–2050:** Power-pellet activation — sets `C=-1` (frightened mode), resets count timer.
8. **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).
9. **Lines 5000–5070:** Level-complete celebration animation, then loop back to maze reset.
10. **Lines 7000–7090:** Title screen and skill-level selection; skill input is converted to a probability value `a` used in ghost movement.
11. **Lines 8000–8010:** Player eats a frightened ghost — awards points, resets ghost position.
12. **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`, `H` that 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`. Because `AND` binds tighter than `OR` in Sinclair BASIC, this reads as `d<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 `a` used 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,31` which 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) |
## 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 RND47 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 RNDx 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 RND47 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 RNDy 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 RND47 AND d<>92 AND (d<144 OR d>157) THEN LET gy1=gy1+c: GO TO 500
472 IF RNDx 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 RND47 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 RNDy 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 X5 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