TRAINS

Developer(s): Stan Livingston
Date: 1986
Type: Program
Platform(s): TS 2068
Tags: Demo

TRAINS displays an animated train moving across the screen in either left or right direction, using 18 user-defined graphics characters (UDGs A through R) to compose the locomotive and carriages. The UDG bitmap data is loaded via a GOSUB routine that POKEs 144 bytes into the UDG area starting at USR “A”. The train animation is achieved by rotating a 32-character string one character at a time each loop iteration, simulating smooth scrolling. Speed is controlled by a user-supplied PAUSE value, and pressing ENTER at any point resets the display and prompts for new settings. The DATA statements use the variable `y` (which is uninitialized and therefore 0 in Sinclair BASIC) as a shorthand for zero-value bytes in the UDG definitions.


Program Structure

The program is organized into three logical sections: initialization and UDG loading (lines 20–130), left-direction animation loop (lines 160–280), and right-direction animation loop (lines 290–420), plus the UDG-loading subroutine at lines 430–640.

  1. Lines 20–130: Draw a baseline, load UDGs via GOSUB, dimension and initialize the string variables, and prompt for speed (n) and direction (D$).
  2. Lines 160–280: Left-moving animation — rotates a$ (train body) and c$ (smoke/cab top) leftward one character per iteration.
  3. Lines 290–420: Right-moving animation — rotates a$ rightward and prepends from f$ (smoke for rightward travel).
  4. Lines 430–640: GOSUB subroutine that READs 144 bytes and POKEs them into the UDG area starting at USR "A", defining 18 custom characters (A through R).

UDG Definition Technique

The subroutine at line 430 uses a FOR loop iterating x from 0 to 143, READing one byte at a time and POKEing it to USR "A"+x. This defines 18 UDGs (18 × 8 = 144 bytes) covering characters A through R, which together form the locomotive cab, body, wheels, and carriages when assembled into the display string a$.

The “y” Variable as Zero Placeholder

Throughout the DATA statements (lines 470–630), the identifier y appears frequently. In Sinclair BASIC, an uninitialized numeric variable defaults to 0, so y is used as a compact stand-in for the value 0 within the DATA list. This avoids typing repeated zeros and is a space-saving idiom, though it relies on y never having been assigned a non-zero value elsewhere — which holds here since the only use of y as a live variable is in the FOR loop counter at line 430 (where it is the READ target, immediately consumed by POKE). Actually, looking more carefully, y in the FOR loop at line 430 is the READ destination: READ y. After the subroutine returns, y will hold the last value read (0), so using it in DATA as a zero placeholder is safe for all DATA lines that are read after line 430 executes — and all DATA is consumed within that subroutine.

String-Rotation Animation

Smooth scrolling is achieved by treating the 32-character strings as circular buffers. For leftward movement, the first character of a$ is saved, the string is rebuilt as a$(2 TO 32)+b$, and the result is printed. For rightward movement the last character is extracted and prepended. The smoke string (c$ or f$) is similarly rotated one row above the train body on row 0. This rotation-by-slicing technique is a classic Sinclair BASIC scrolling idiom.

Speed and Keypress Control

The user enters a PAUSE value n at line 110; small values produce fast animation and larger values slow it down. Within each animation loop, CODE INKEY$=13 (line 260 / line 400) checks for the ENTER key — if detected, the screen is cleared and the program restarts from line 20, allowing the user to choose a new speed and direction without a full restart.

Bugs and Anomalies

  • Loop counter reset (lines 250, 390): IF b=32 THEN LET b=1 is effectively dead code — after this line executes, NEXT b immediately increments b to 2 (or 33 before loop exit), so resetting to 1 does not cause the loop to repeat. The loop runs exactly 32 iterations regardless.
  • Right-rotation string growth (lines 330, 350): In the rightward loop, f$=g$+f$ and a$=b$+a$ prepend without trimming, so these strings grow by one character each iteration. After 32 iterations a$ will be 57 characters long (25 initial + 32 prepended). This will cause display overflow onto adjacent screen positions and is a likely bug; the left loop correctly trims by slicing (2 TO 32).
  • Line 120 — UDG-M appears twice, UDG-L is absent: The train string references [UDG-M] twice consecutively and skips [UDG-L], which may be intentional (two identical carriage-side segments) or a typo.
  • STOP at lines 280 and 420: After one pass of 32 iterations the loop ends and hits STOP rather than looping continuously. Combined with the dead reset code, the animation only runs one 32-step cycle before halting.

Variable Summary

VariablePurpose
nPAUSE duration (speed control)
a$32-char train body string (UDG characters)
b$Temporary single character during rotation
c$32-char smoke/top row string for leftward travel
D$Direction input (“R”/”L”)
e$Temporary character for rotating c$
f$32-char smoke/top row string for rightward travel
g$Temporary character for rotating f$
xUDG POKE loop counter
yREAD target; doubles as 0 placeholder in DATA
bAnimation loop counter

Image Gallery

Source Code

  10 REM "TRAIN" by                  Stan Livingston SINCUS         Johnson City NY 1985
  20 PLOT 0,159:DRAW 255,0
  30 GO SUB 430
  40 DIM a$(32)
  50 DIM c$(32)
  60 REM  use USER DEFINED GRAPHICS
  70 LET c$="[UDG-P][UDG-R]                      "
  80 DIM f$(32)
  90 LET f$="[UDG-Q]                       "
 100 REM enter speed of train,try 1-15
 110 INPUT "PAUSE?";n
 120 LET a$="[UDG-A][UDG-B][UDG-C][UDG-D][UDG-E][UDG-F][UDG-G][UDG-F][UDG-G][UDG-F][UDG-G][UDG-H][UDG-I][UDG-H][UDG-I][UDG-H][UDG-I][UDG-J][UDG-K][UDG-J][UDG-K][UDG-M][UDG-M][UDG-N][UDG-O]"
 130 REM DIRECTION OF TRAIN
 140 INPUT "RIGHT OR LEFT ?";D$
 150 IF D$="R" OR D$="r" THEN GO TO 290
 160 PRINT AT 1,0;a$
 170 FOR b=1 TO 32
 180 LET e$=c$(1)
 190 LET c$=c$(2 TO 32)+e$
 200 LET b$=a$(1)
 210 LET a$=a$(2 TO 32)+b$+"  "
 220 PRINT AT 1,0;a$
 230 PRINT AT 0,0;c$
 240 PAUSE n
 250 IF b=32 THEN LET b=1
 260 IF CODE INKEY$=13 THEN CLS :GO TO 20
 270 NEXT b
 280 STOP 
 290 PRINT AT 1,0;a$
 300 PRINT AT 0,0;f$
 310 FOR b=1 TO 32
 320 LET g$=f$(32)
 330 LET f$=g$+f$
 340 LET b$=a$(32)
 350 LET a$=b$+a$
 360 PRINT AT 1,0;a$
 370 PRINT AT 0,0;f$
 380 PAUSE n
 390 IF b=32 THEN LET b=1
 400 IF CODE INKEY$=13 THEN CLS :GO TO 20
 410 NEXT b
 420 STOP 
 430 FOR x=0 TO 143
 440 READ y:POKE USR "[UDG-A]"+x,y
 450 NEXT x
 460 DATA 6,63,127,255,127,63,29,8
 470 DATA 0,255,y,y,y,y,221,136
 480 DATA 63,255,y,y,y,y,239,198
 490 DATA 31,63,127,y,y,255,28,8
 500 DATA 254,y,y,y,y,255,56,16
 510 DATA 255,127,y,y,y,255,56,16
 520 DATA 255,254,y,y,y,255,28,8
 530 DATA 127,73,y,79,y,255,112,32
 540 DATA 254,146,y,242,y,255,28,8
 550 DATA 0,y,y,127,y,255,56,16
 560 DATA 0,y,y,254,y,255,28,8
 570 DATA 0,y,y,y,y,255,56,16
 580 DATA 0,y,y,y,y,255,28,8
 590 DATA 3,255,6,38,39,255,56,16
 600 DATA 192,255,48,50,242,255,28,8
 610 DATA 0,y,y,y,y,y,3,4
 620 DATA 0,y,y,y,y,y,248,4
 630 DATA 0,y,y,y,y,y,255,0
 640 RETURN 
 650 SAVE "TRAINS" LINE 10

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