--- title: "Munchie Man" id: 54549 type: "computer_media" slug: "pacman" url: "http://localhost/computer_media/pacman/" markdown_url: "http://localhost/computer_media/pacman.md" published_at: "2024-06-02T16:50:41+00:00" modified_at: "2026-03-30T21:44:14+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A joystick-driven Pac-Man clone uses nine custom sprite characters, an ASCII-art maze, and a randomly wandering ghost to challenge your dot-eating reflexes." 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/" - name: "Game" slug: "game" taxonomy: "genre" url: "http://localhost/type/game/" media_contents: - id: 51207 title: "CATS Library Tape 6" type: "computer_media" url: "http://localhost/computer_media/cats-library-tape-6/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/06/munchie-man.png" media_type_tags: "Arcade, Game" --- Munchie Man is a Pac-Man-style maze game that uses joystick input via the STICK function to move a player character through a maze, eating dots and special pellets. Nine custom UDG characters (labeled \a through \i) are defined at startup using POKEd DATA values, providing directional mouth animations for the player sprite and a ghost enemy sprite. The maze is drawn entirely through two long PRINT statements containing ASCII characters, where underscores represent walls, periods represent dots, and “o” represents power pellets. A single ghost enemy moves randomly through the maze, triggering a life-loss sequence with descending BEEP tones when it catches the player. The game tracks score, a session high score, remaining lives, and a dot count to detect level completion. *** ## Program Analysis ### Program Structure The program is organized into a main game loop and several subroutines, with line numbers used as semantic separators: - `10–250`: Main game loop — initialization calls, player and ghost movement, collision detection - `2000–2050`: Game Over screen with high-score tracking and restart prompt - `5000–5140`: Death sequence — animated player collapse with descending BEEPs, life decrement - `6000–6010`: Power pellet subroutine — bonus score (200–299 points) and sound effect - `7000–7100`: Maze drawing subroutine — sets colors, CLSes, and PRINTs two long layout strings - `8000–8990`: Game variable initialization (player position, score, lives, dot count) - `9000–9990`: UDG definition loop with DATA, plus REMarks labeling each UDG ### UDG Sprite Definitions Nine UDGs (`\a` through `\i`) are defined at lines `9030`–`9110` by POKEing 8 bytes each into memory starting at `USR "a"`. The loop at line `9000` iterates from `USR "a"` to `USR "i"+7`, consuming DATA values one per POKE. The REMark at line `9980`–`9990` documents the mapping: | UDG | Description | Usage | | --- | --- | --- | | `\a` | Full circle (closed mouth) | Player idle / death start | | `\b` | Mouth open left | Moving left | | `\c` | Mouth open right | Moving right / initial sprite | | `\d` | Mouth open down | Moving down | | `\e` | Mouth open up | Moving up | | `\f` | Partial collapse frame 1 | Death animation | | `\g` | Partial collapse frame 2 | Death animation | | `\h` | Partial collapse frame 3 | Death animation | | `\i` | Ghost sprite | Enemy character | ### Maze Representation The maze is stored as two long string literals in `PRINT` statements at lines `7010`–`7020`. Walls are encoded as `_` (underscore), collectible dots as `.` (period), power pellets as `o`, and open corridors as spaces. Collision detection throughout the main loop uses `SCREEN$` to read these characters back from the display, making the screen itself serve as the game state map — a common technique in Sinclair BASIC games that avoids maintaining a separate map array. ### Joystick Input via STICK Input is handled using the `STICK (1,1)` function (TS2068 extended keyword). The directional values used are: `1`=up, `2`=down, `4`=left, `8`=right. The sprite selection at line `70` uses a string-arithmetic idiom: each direction string is ANDed with the Boolean result of the corresponding STICK comparison, so exactly one non-empty string is concatenated into `m$`. Movement deltas at lines `100`–`110` use the same Boolean-as-integer trick (`(condition)` evaluating to 1 or 0) to compute row and column offsets in a single expression each. ### Ghost AI The ghost (stored at position `z`, `x`) uses a simple random walk. At line `170`, movement deltas are computed from direction variable `m1` (values 5–8 mapping to up/down/left/right). When the ghost hits a wall (`SCREEN$ (z,x)="_"`), a new random direction is chosen via `INT(RND*4)+5` and the ghost reverts to its previous position. Lines `190`–`195` restore any dot or pellet that the ghost had moved over, preventing the ghost from permanently erasing collectibles. ### Collision Detection All game entity interactions are resolved by reading `SCREEN$` at the player’s or ghost’s new position before rendering: - `SCREEN$="_ "` → wall, revert position - `SCREEN$="."` → dot eaten, +10 score, increment dot counter - `SCREEN$="o"` → power pellet, go to bonus score subroutine at line `6000` - `SCREEN$=""` (empty string / space) → player–ghost collision, go to death sequence at line `5000` Note that the ghost-catch condition at line `140` and line `200` checks for `SCREEN$=""`, meaning an empty cell. This relies on the ghost sprite being printed at that cell first (line `210`), but in some frame orderings the ghost sprite character `\i` would be there instead — this is a minor logic fragility in the collision system. ### Screen Wrap-Around Lines `85`–`86` implement horizontal screen wrap: if `h` (column) falls below 1 it wraps to 30, and above 30 it wraps to 0. There is no equivalent vertical wrap, so the player can be stopped by walls at the top and bottom of the maze. This asymmetry is typical for simple maze layouts where corridors don’t cross the vertical edge. ### Death Animation The death sequence at lines `5000`–`5140` steps through UDGs `\a`, `\b`, `\f`, `\g`, `\h`, then a space, each paired with a `BEEP` call at a descending pitch (15, 11, 7, 3, 0, −5 semitones). After the animation, the dot count is reset, player position is reset to `v=12, h=16`, and lives are decremented. Level completion is checked at line `240` by comparing `count` against `tot=286`, which matches the total number of dot characters embedded in the two maze PRINT strings. ### Notable Techniques and Anomalies - Using the display as a game map (via `SCREEN$`) avoids a separate DATA array, saving significant memory. - The string-AND idiom at line `70` for sprite selection is idiomatic Sinclair BASIC, exploiting the fact that `"str" AND 0` returns `""` while `"str" AND 1` returns `"str"`. - The ghost restores dots/pellets it passes over (lines `190`–`195`), but only at the previous position `z1, x1` — this correctly handles one step of memory but could misfire if the ghost backtracks across a pellet it already consumed. - Line `140` uses `SCREEN$ (v,h)=""` to detect the player walking into the ghost; this works because the ghost was just printed at `z,x` using UDG `\i`, and the player moves into empty space — the condition is fragile but functional given the fixed draw order. - The session high score (`hi`) is initialized to 0 at line `20` and persists across replays since `GO TO 30` (not `GO TO 20`) is used at line `2050`. ## Source Code ``` 10 REM Munchie Man 20 LET hi=0: GO SUB 9000 30 GO SUB 8000 40 LET v=12: LET h=16: GO SUB 7000: INVERSE 1: PRINT AT 20,15;"Lives left ";lives 45 PRINT AT v,h; INVERSE 1;m$ 50 IF STICK (1,1)=0 THEN GO TO 80 70 LET m$=("\b" AND STICK (1,1)=1)+("\c" AND STICK (1,1)=8)+("\d" AND STICK (1,1)=2)+("\e" AND STICK (1,1)=4) 80 PRINT AT v,h;" " 85 IF h<1 THEN LET h=30 86 IF h>30 THEN LET h=0 90 LET v1=v: LET h1=h 100 LET v=v+( STICK (1,1)=2)-( STICK (1,1)=1) 110 LET h=h+( STICK (1,1)=8)-( STICK (1,1)=4) 120 IF SCREEN$ (v,h)="_" THEN LET v=v1: LET h=h1: PRINT AT v,h;m$: GO TO 150 130 IF SCREEN$ (v,h)="." THEN LET sc=sc+10: BEEP .008,10: LET count=count+1 135 IF SCREEN$ (v,h)="o" THEN GO SUB 6000 140 IF SCREEN$ (v,h)="" THEN GO TO 5000 145 PRINT AT v,h;"\a";AT 20,0;"Score ";sc 150 PRINT AT z,x;" " 160 LET z1=z: LET x1=x 170 LET z=z+(m1=6)-(m1=7): LET x=x+(m1=8)-(m1=5) 180 IF SCREEN$ (z,x)="_" THEN LET m1=INT (RND*4)+5: LET z=z1: LET x=x1: GO TO 160 185 PRINT AT v,h;m$ 190 IF SCREEN$ (z,x)="." THEN PRINT AT z1,x1;"." 195 IF SCREEN$ (z,x)="o" THEN PRINT AT z1,x1;"o" 200 IF SCREEN$ (z,x)="" THEN GO TO 5000 210 PRINT AT z,x; PAPER 4;"\i" 230 IF lives<1 THEN GO TO 2000 240 IF count=tot THEN LET count=0: GO TO 40 250 GO TO 50 990 STOP 2000 CLS 2010 INVERSE 0: PRINT ''TAB 5;"GAME OVER" 2020 PRINT ''" You scored ";sc 2030 IF sc>hi THEN LET hi=sc 2040 PRINT ''" Highest score today ";hi 2050 INPUT "Press enter to play again "; LINE a$: GO TO 30 5000 INVERSE 1 5005 PRINT AT z,x;"." 5010 PRINT AT v,h;"\a" 5020 BEEP .3,15 5030 PRINT AT v,h;"\b" 5040 BEEP .3,11 5050 PRINT AT v,h;"\f" 5060 BEEP .3,7 5070 PRINT AT v,h;"\g" 5080 BEEP .3,3 5090 PRINT AT v,h;"\h" 5100 BEEP .3,0 5110 PRINT AT v,h;" " 5120 BEEP .5,-5 5130 LET count=0: LET v=12: LET h=16: LET lives=lives-1 5140 INVERSE 0: GO TO 40 6000 BEEP .008,15: BEEP .005,-15: LET sc=sc+INT (RND*100)+200 6010 RETURN 7000 PAPER 0: CLS : INK 0: PAPER 6: BORDER 0 7010 PRINT "_________________________________............... ..............__.___._________._.________.___.__o___......................___.__.___.____.___________.___.___.__.....____......_......___.....__.___._________._.________.___.__.___.__................__.___.__.....__.______________.__.....______....______________...._____" 7020 PRINT "......__................__......_____.__.______________.__.______..............................__o___._________._.________.___o__..._.__........_.......__._...____._.__.______._._____.__._.____.....__................__.....__._________.________._________.__.............................._________________________________" 7030 INK 0 7040 PAPER 6 7100 RETURN 8000 LET m$="\c" 8020 LET c1=8: LET m1=8 8030 LET sc=0 8040 LET z=7: LET x=15 8050 LET lives=3 8060 LET count=0 8070 LET tot=286 8990 RETURN 8998 REM 8999 REM 9000 FOR a=USR "a" TO USR "i"+7 9010 READ user: POKE a,user 9020 NEXT a: RETURN 9030 DATA 60,126,255,255,255,255,126,60 9040 DATA 0,66,195,231,255,255,126,60 9050 DATA 60,126,248,240,240,248,126,60 9060 DATA 60,126,255,255,231,195,66,0 9070 DATA 60,126,31,15,15,31,126,60 9080 DATA 0,0,126,255,255,255,126,60 9090 DATA 0,0,0,0,16,24,60,60 9100 DATA 129,66,36,0,0,36,66,129 9110 DATA 28,62,42,107,127,127,109,73 9980 REM a b c d e f g h i 9990 REM \a \b \c \d \e \f \g \h \i ```