--- 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