Tilt Board

This file is part of and Timex Sinclair Public Domain Library Tape 1006. Download the collection to get this file.
Date: 198x
Type: Program
Platform(s): TS 1000
Tags: Game

This program implements a single-player dodgem-style game in which the player steers a character (“$”) across a screen filled with randomly placed inverse-video obstacles, trying to avoid walls and obstacles while accumulating a score. The screen is set up in FAST mode: columns of inverse spaces are printed to form barriers, then one cell per column is blanked to create gaps, and a solid border of block-graphic characters forms the perimeter. Movement is controlled via Q (up), Z (down), and L (right), and the program reads the display file directly using PEEK on the system variable at address 16396/16397 to detect collisions with inverse-video cells (value 128). The score starts at 20,000 and decrements by 50 each move and by 673 on each obstacle hit; hitting a wall divides the score by 3. A high-score tracker persists across rounds in variable U, and the SAVE command at line 530 uses an inverse character in the filename as an auto-run flag.


Program Analysis

Program Structure

The program is divided into clearly separated phases, each handled by a distinct block of lines:

  1. Initialisation and screen setup (lines 10–140): Sets the high-score accumulator U to zero, enters FAST mode, draws random column obstacles, and paints a solid border.
  2. Round initialisation subroutine (lines 440–510): Called via GOSUB 440 at line 150; sets starting position (A=10, B=1), initial score Z=20000, and last-direction A$="Z" (downward), then switches to SLOW mode.
  3. Main game loop (lines 160–340): Reads INKEY$, applies movement, erases the old sprite, performs collision detection, redraws the sprite, and loops back.
  4. End-of-round handling (lines 350–430): Displays score, updates high score, flashes the player sprite, clears the screen, and restarts from line 20.
  5. Obstacle-hit subroutine (lines 170–200): Replaces the hit cell with an inverse “$”, penalises the score by 673, updates the score display, and returns.
  6. Utility lines (520–540): CLEAR, SAVE with auto-run filename, and RUN — executed only when preparing the tape image.

Screen Setup

Lines 30–80 build the obstacle field. The outer FOR B loop steps through even columns 2 to 28. For each column, every row 0–19 is filled with an inverse space ("% ", i.e. an inverse-video space character). Then line 70 blanks one random cell in that column (RND*14+3, giving rows 3–17 approximately) to create a navigable gap. This produces a series of near-solid vertical barriers with a single passable hole each.

Lines 90–140 draw the border: line 100 prints the block-graphic character \@@ (a solid block, character 128) along rows 0 and 19, and lines 120–140 print it down columns 0 and 30, forming a rectangular enclosure.

Collision Detection via Display File PEEK

The most technically notable feature is the collision routine at line 290:

IF PEEK (PEEK 16396+256*PEEK 16397+33*A+B+1)=128 THEN GOSUB 170

System variables at addresses 16396 and 16397 hold the low and high bytes of the display file start address (D_FILE). Adding 33*A+B+1 computes the offset into the display file for row A, column B (each row is 33 bytes wide: 32 character cells plus a NEWLINE). A PEEK value of 128 corresponds to an inverse space — the obstacle character. This technique reads the screen contents directly rather than maintaining a separate map array, saving RAM and eliminating synchronisation issues.

Movement and Inertia

Line 220 implements a simple inertia system: if no key is pressed (INKEY$=""), the last valid keypress stored in A$ is reused. This means the player continues moving in the last chosen direction without holding a key. A$ is initialised to "Z" (move down) in the setup subroutine, so the player immediately starts moving downward.

Movement deltas are computed on lines 260–270:

  • A=A+(Z$="Z")-(Z$="Q") — Z moves down (increases row), Q moves up.
  • B=B+(Z$="L") — L moves right; there is no leftward control.

The absence of a left-movement key is intentional design: the player must progress rightward across the screen, navigating gaps in each vertical barrier.

Scoring

EventScore change
Each move (every iteration of main loop)−50
Hitting an obstacle cell−673 (plus the −50 for the move)
Hitting a wall (out of bounds)Score divided by 3 (integer)

The score starts at 20,000. Hitting a wall also terminates the round (line 320 branches to 350 if out of bounds). Line 360 updates the persistent high score U if the current score exceeds it.

End-of-Round Animation

Lines 370–410 loop 6 times, flashing the player’s position between normal and inverse “$” while alternating the “BEST SO FAR” display between normal and inverse text ("%B%E%S%T" is “BEST” in inverse video). This provides simple visual feedback without any additional machinery.

Key BASIC Idioms

  • Boolean arithmetic for movement (Z$="Z" evaluates to 1 or 0) avoids IF/THEN branching for direction calculation.
  • FAST/SLOW switching: the screen is built in FAST mode for speed, then SLOW is restored for gameplay so the display remains visible.
  • The coordinate variables Y/X store the previous position so the old sprite can be erased before the new position is drawn (lines 240–250, 280).

Bugs and Anomalies

  • Line 310 applies the wall penalty (Z=INT(Z/3)) and line 320 then checks the same boundary condition again to branch to the end-of-round section. This duplication means the penalty is correctly applied before the branch, but the condition is evaluated twice unnecessarily.
  • There is no upward boundary column guard for B in the wall-penalty check at line 310 (B<1 is checked but B>29 is not), while line 320 does check B>29. A player reaching column 30 or beyond gets the end-of-round without the score-division penalty from line 310 — this may be intentional (reaching the right edge is a success state).
  • The display-file offset formula uses +1 to skip the NEWLINE byte at the start of each row, which is correct for the ZX81 display file layout.

Content

Appears On

Assembled by Tim Ward from many sources. Contains programs 10252 – 10293.

Related Products

Related Articles

Related Content

Image Gallery

Tilt Board

Source Code

  10 LET U=0
  20 FAST 
  30 FOR B=2 TO 28 STEP 2
  40 FOR A=0 TO 19
  50 PRINT AT A,B;"% "
  60 NEXT A
  70 PRINT AT RND*14+3,B;" "
  80 NEXT B
  90 FOR A=0 TO 30
 100 PRINT AT 0,A;"\@@";AT 19,A;"\@@"
 110 NEXT A
 120 FOR B=1 TO 18
 130 PRINT AT B,0;"\@@";AT B,30;"\@@"
 140 NEXT B
 150 GOSUB 440
 160 GOTO 210
 170 PRINT AT A,B;"%$"
 180 LET Z=Z-673
 190 PRINT AT 20,0;"SCORE: ";Z;"    "
 200 RETURN 
 210 LET Z$=INKEY$
 220 IF Z$="" THEN LET Z$=A$
 230 LET Z=Z-50
 240 LET Y=A
 250 LET X=B
 260 LET A=A+(Z$="Z")-(Z$="Q")
 270 LET B=B+(Z$="L")
 280 PRINT AT Y,X;" "
 290 IF PEEK (PEEK 16396+256*PEEK 16397+33*A+B+1)=128 THEN GOSUB 170
 300 PRINT AT A,B;"$"
 310 IF A>18 OR A<2 OR B<1 THEN LET Z=INT (Z/3)
 320 IF A>18 OR A<2 OR B<1 OR B>29 THEN GOTO 350
 330 LET A$=Z$
 340 GOTO 160
 350 PRINT AT 20,0;"END OF ROUND SCORE: ";Z
 360 IF Z>U THEN LET U=Z
 370 FOR G=1 TO 6
 380 PRINT AT 21,3;"BEST SO FAR: ";U
 390 PRINT AT A,B;"%$";AT A,B;"$";AT A,B;"%$"
 400 PRINT AT 21,3;"%B%E%S%T"
 410 NEXT G
 420 CLS 
 430 GOTO 20
 440 LET A=10
 450 LET B=1
 460 LET Z=20000
 470 LET Y=A
 480 LET X=B
 490 LET A$="Z"
 500 SLOW 
 510 RETURN 
 520 CLEAR 
 530 SAVE "1028%8"
 540 RUN 

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

People

No people associated with this content.

Scroll to Top