--- title: "Falling Blocks" id: 57507 type: "computer_media" slug: "unknown-game-2" url: "http://localhost/computer_media/unknown-game-2/" markdown_url: "http://localhost/computer_media/unknown-game-2.md" published_at: "2024-10-05T21:13:40+00:00" modified_at: "2026-03-30T21:17:38+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/262_Unk.png" excerpt: "A fast-paced catching game built entirely with POKE and PEEK into the display file — no sprites, just raw memory manipulation and a ticking countdown." 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 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Game" slug: "game" taxonomy: "genre" url: "http://localhost/type/game/" media_contents: - id: 56737 title: "Timex Sinclair Public Domain Library Tape 1006" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1006/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/262_Unk.png" media_type_tags: "Game, Software" --- This is a ZX81/TS1000 falling-blocks arcade game where the player controls a horizontally moving cursor to catch descending items. The screen is set up with inverse-video rows forming a playing field, and the PEEK of system variables 16396/16397 is used to obtain the base address of the display file so that direct POKE operations can manipulate individual character cells. The ball/block (character code 151) falls column by column toward the bottom, and a collision with character 148 triggers a score increment. A countdown timer (variable B starting at 501) drives the game loop, ending with a score display and automatic restart via RUN. *** ## Program Analysis ### Program Structure The program is organised into four logical phases: 1. **Initialisation (lines 1–7):** Resets the score `N`, sets the countdown `B=501`, draws the playing field with inverse-video characters, and sets the paddle position `P=8`. 2. **Main game loop (lines 8–100):** Decrements the timer, reads the keyboard, moves the falling block down through display memory, and loops back via `GOTO 10`. 3. **Catch handler (lines 110–140):** Awards a point, and every 112 catches redraws the screen (`GOTO 3`); otherwise returns to the game loop. 4. **End screen (lines 200–220):** Prints the score, pauses, clears, and restarts with `RUN`. ### Display File Addressing The display file base address is obtained at line 8 with: `LET V=PEEK 16396+256*PEEK 16397` System variables at addresses 16396–16397 hold the low and high bytes of the display file start (D_FILE). Adding offsets to `V` lets the program treat screen memory as a flat array, bypassing BASIC’s own `PRINT AT` mechanism for speed. ### Movement and Collision The paddle position `P` is updated each loop iteration at line 30: `LET P=P-1+(INKEY$="")*2-(P=15)+(P=0)` This single expression moves the paddle left when a key is held (subtracts 1 and adds back 2 only when no key is pressed), and clamps `P` within the range 0–15 using the boolean terms `-(P=15)` and `+(P=0)`. On ZX81 BASIC, boolean expressions evaluate to 1 (true) or 0 (false), making this an efficient idiom for boundary clamping without IF statements. The falling block is written at `120+V+P` (line 40 — row 7, column `P`) and then the `FOR` loop at lines 50–90 steps backward through display memory in strides of 17 (the ZX81 screen width of 32 columns plus one newline byte = 33 bytes per row, but the stride here is 17, suggesting a narrower virtual playfield). At each cell the loop checks for character 148 (the paddle/catcher character) and, on a miss, writes then immediately clears character 151 (the block). ### Key Variables | Variable | Purpose | | --- | --- | | `N` | Player score (caught blocks) | | `B` | Countdown timer, starts at 501 | | `P` | Horizontal paddle position (0–15) | | `V` | Display file base address from D_FILE | | `Z` | Loop counter / current display memory address | ### Notable Techniques - **Boolean arithmetic for clamping:** The paddle movement expression exploits ZX81 boolean-as-integer to handle wrapping without branching. - **Direct display POKE:** Writing character codes directly into display memory is significantly faster than `PRINT AT` and is the standard ZX81 trick for animation. - **Screen redraw trigger:**`IF INT(N/112)<>N/112` tests divisibility by 112 to trigger a full screen refresh, preventing inverse-video rows from being corrupted over time. - **Auto-restart:**`RUN` at line 215 reinitialises all variables and restarts without requiring user input. ### Bugs and Anomalies - The `FOR` loop stride of `-17` is unusual. The ZX81 display file uses 33 bytes per row (32 character positions plus a NEWLINE token at the end). A stride of 17 means the loop visits every other row in reverse, but also potentially lands on non-character bytes, which could produce unpredictable graphical artefacts. - Line 220 (`CLEAR`) is unreachable: execution flows `RUN` at line 215, which restarts the program before `CLEAR` can execute. - The falling block at line 40 is placed at byte offset `120+V+P` relative to the display file, which corresponds to roughly row 3 column `P` (120 ÷ 33 ≈ 3.6). The loop then scans back toward `V`, meaning the “falling” direction is upward in memory terms — this is consistent with the ZX81 display layout where lower addresses appear higher on screen, so visually the block appears to fall downward. ## Source Code ``` 1 LET N=0 2 LET B=501 3 FOR Z=0 TO 6 4 PRINT AT Z,0;"%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=%=" 5 NEXT Z 6 PRINT "% % % % % % % % % % % % % % % % ","\~~\~~\~~\~~\~~\~~\~~\~~\~~\~~\~~\~~\~~\~~\~~\~~" 7 LET P=8 8 LET V=PEEK 16396+256*PEEK 16397 10 LET B=B-1 20 IF B=0 THEN GOTO 200 30 LET P=P-1+(INKEY$="")*2-(P=15)+(P=0) 40 POKE 120+V+P,151 50 FOR Z=120+V+P TO V STEP -17 70 IF PEEK Z=148 THEN GOTO 110 80 POKE Z,151 81 POKE Z,128 90 NEXT Z 100 GOTO 10 110 POKE Z,120 120 LET N=N+1 130 IF INT (N/112)<>N/112 THEN GOTO 10 140 GOTO 3 200 PRINT "YOUR SCORE ";N 205 PAUSE 50000 210 CLS 215 RUN 220 CLEAR ```