In the best of home computer traditions; here at Paleotronic we’re celebrating Christmas with a quick delve into Sinclair BASIC and a festive type in listing.
It’s time to dust off the trusty 48k ZX Spectrum and get to coding a XMAS screen saver to run on the family Television over the holiday season.
Just as in 1982 the starting point of any questionably good Christmas demo is the BASIC language. For users of the Sinclair line of computers that means dealing with a slightly idiosyncratic version of the BASIC language developed specifically for the ZX line of home computers.
What would become known as Sinclair BASIC started life at Nine Tiles, a small UK company located in Cambridge, UK. The initial version was written by company owner John Grant for Sincliars ZX80 computer. Based on the 1978 ANSI minimal BASIC standard, the ZX80s implementation was incomplete, lacking many features and handled only integer arithmetic.
Upon joining Nine Tiles in 1980, Steve Vickers was appointed developer of Sinclair BASIC going on to the language for the ZX81 and the later ZX Spectrum, adding trigonometric functions, floating-point arithmetic along with numerous other improvements including colour, sound and advanced graphic facilities that were not required on the earlier ZX machines.
Arguably the most unique feature of Sinclair BASIC and one present on all Sinclair made machines is its keyword entry system. Keywords are comprised of single-stroke key entries, rather than requiring the user to fully typed out instructions. For example if “P” is pressed while the computer is in command mode, the keyword “PRINT” appears.
This command entry scheme is somewhat necessitated by the diminutive and less than ideal keyboards common to all early Sinclair computers, however it does helpfully facilitate a pre-execution syntax validation process. Unlike most BASIC interpreters of the period, Sinclairs will not allow commands to be entered into the system without undergoing syntax checking, hopefully resulting in fewer typing errors; This won’t stop all bugs finding their way into your code.
A 40 year old 48k computer can only do so much after all.
Sadly ZX Spectrum BASIC doesn’t fare so well in the speed stakes. Due in part to a rushed development cycle and a desire to cram as many features as possible into the smallest possible space, the language is painfully slow. So while all the enhanced features added to the Sinclair BASIC over its various iterations greatly improved the language, getting these features to produce multiple fast moving graphics, such as for example; the type required for good game play or a speedy graphical demo is next to impossible. All serious software development would move very quickly into machine coding.
Regardless of the shortcomings, the language can be quite powerful; there are not many features lacking that can’t be overcome by some creative programming. For example the an obvious keyword lacking from ZX Spectrum BASIC is ‘ELSE’, used in IF statements. With creative sub-routines and or the usage of multi statement command lines the use of a dedicated ‘ELSE’ keyword is easy enough to circumvent.
For users upgrading to the Spectrum and those eagerly expecting one in their 1982 Christmas Stockings, Sinclair BASICs minor incompatibilities and shortfalls were unlikely to be of any real concern. In high probability / more likely the inclusion of colour and graphics was of much higher importance; after games of course. To that end a festive Yuletide themed screen saver (or demo if you prefer) awaits the keen typist.
Our first task in exploring the brave new world of graphics and colour is to define some of those graphics. UDGs or User Definable Graphics were a new feature on Spectrums version of Sinclair BASIC. Located in characters positions 144 to 164, they initially appear as letters, and are entered as graphic characters on keys A to U until new values are assigned by the user. Each character is stored in a 8*1 bit blocks. ZX XMAS sets up all UDG characters in lines 400 to 440, the data used to describe each UDG is held in DATA statements listed from 450 to 565. For clarity each DATA statement holds the values for 1 whole character, although this separation is not required.
Our base winter scene is drawn and later redrawn from Lines 120 to 320. There are a couple of sub-routines here and called throughout the programs execution in order to redraw parts of the screen. For example lines 265 to 320 are used to set up colour attributes for our house, then used again latter to gradually turn the house lights off as the snow level rises around it.
Most importantly we need to consider our programs speed. The Spectrums BASIC is relatively slow, as tracking movement of many individual graphic elements can lead very quickly to a noticeable slowdown This is not a particularly desirable outcome for applications requiring a fluid movement of elements. Some simple tricks keep the programs execution speed as high as possible. Unfortunately these tricks sacrifice some degree in program legibility in the process.
Further to the above, printing characters to specific locations in specified colours is a time consuming job. To do this normally requires ‘PRINT AT’, ‘INK’ and ‘PAPER’ statements. There are some tricks to overcome this. The Spectrum divides its screen layout into 2 major components, the Display File and the Attribute File. The Attribute File located at address 22528 to 23296 holds colour information for 8*8 pixel character blocks, POKEing these addresses instead of printing directly can gain us some time.
Lines 120 to 155 print the snowflake patterns that simulate the falling snow. The INK and PAPER colours are set to black, which renders the characters effectively invisible. By POKEing colour values to the Attribute File during execution of the programs main loop visibility of the snow characters can be turned on or off when required.
The main loop of ZX XMAS is located almost at the very start of the listing at lines 30 to 115. The loop keeps track of our individual snowflakes and the builds up of snow on the ground. Snow falls at 2 differing rates, loops y and x govern this rate. Four flakes fall at twice the rate, achieving to 2 main goals. Visually it provides a staggered snow drop appearance, and a smoother fall rate with more flakes on the screen at one time.
The actual position of each flake is held in array f(x). The starting location of each flake is set by a combination of the s(x) array which holds the column offset and the row value for f(x) READ for DATA lines 575 to 610. Once snow hits the ground, the value of s(x), plus a random offset of 0 to 3 determines the column in which a snowflake starts at the top of the screen again. Average snow build up height is tracked and once the level reaches above the house, the screen is reset and we start again.
Key Program Functions:
025-115: Main Loop; track snow fall.
125-155: Set up Falling Snow Characters
165-195: Draw Ground and Base Snow
205-255: Draw Christmas House
275-320: Light up House
325-395: Define Main Variables
405-440: Load UDGs
Tips to improve execution speed:
- Keep the main program loop as close to the start of a BASIC listing as is practically possible: Speed of execution can be effected by something as innocuous as REM statements placed at the start of a program. In ZX XMAS vital information on variables has been located at the end of the program to gain execution speed.
- Define highly used variables first, and avoid the using arrays: Breaking the array rule, ZX XMAS tracks the positions of snow flakes in f(x) and s(x), however access times to these is shortened by defining these variables before all others.
- Limit variable names to single characters: For a noticeable cost to readability all variables in the application can be kept to single letters.
- Reduce line counts by stacking commands on single lines separated by a colon.
10 REM 48K Spectrum XMAS Snow David Stephenson 2018
15 RANDOMIZE : PAPER 0: INK 0: BORDER 0: CLS
20 GO SUB 405: GO SUB 325: GO SUB 125
25 REM MAIN loop
30 LET o=INT (RND*4)
35 LET i=71-o
40 FOR y=1 TO 2: FOR x=1 TO y*4
45 LET p=f(x)
50 POKE p,i
55 POKE p-32,0
60 LET f(x)=p+32
65 IF NOT PEEK f(x) THEN GO TO 105:
70 LET p=PEEK f(x): LET n=f(x)-32-a+96: LET r=INT (n/32): LET c=n-r*32
75 IF INT (v/32)-1>r THEN POKE f(x)-32,0: LET f(x)=s(x)+o+a: GO TO 105:
80 IF x=4 THEN LET v=v/32*31+r+.25: REM Even up snow build up.
85 IF p=71 OR INT (v/32)<r THEN POKE f(x),127: POKE f(x)-32,0: PRINT AT r,c;CHR$ (151+INT (o/2))
90 IF p=69 THEN PRINT AT r+1,c;CHR$ (153+INT (o/2)): POKE f(x)-32,0: POKE f(x),71
95 IF p=7 THEN POKE f(x)-32,0: POKE f(x),69
100 LET f(x)=s(x)+o+a
105 NEXT x: NEXT y
110 IF INT (v/32)=b-4-l THEN LET l=l+1: GO SUB 275
115 GO TO 30
120 REM setup falling snow
125 PAPER 0: BORDER 0: INK 0: BRIGHT 0
130 PRINT AT 3,0;
135 REM If ASCII listing Backslash Charters = graphic mode letters
140 REM IF ZX letter Charters = graphic mode “Caps SHIFT + 9” letters
145 FOR x=0 TO 79
150 PRINT “\d\d\c*\a\b”;
155 NEXT x
160 REM setup snow base
165 FOR x=0 TO 14 STEP 2
170 INK 7
175 PRINT AT b-3,x;”\j\k”;AT b-3,30-x;”\j\k”
180 BRIGHT 1: PAPER 7: PRINT AT b-2,x;”\::\::”;AT b-2,30-x;”\::\::”: REM G shifted 8
185 PAPER 0: PRINT AT b-1,x;”\e\f”;AT b-1,30-x;”\e\f”
190 BRIGHT 0: PRINT AT b,x;”\g\g”;AT b,30-x;”\g\g”
195 NEXT x
200 REM print out house
205 BRIGHT 1
210 PRINT AT b-2,h;”\s\s\s\s\t\t\t\s\s\s\t\t\t”
215 PRINT AT b-3,h;”\p\r\r\p\q\r\r\p\r\p\q\r\q”
220 PRINT AT b-4,h;”\p\r\r\p\q\r\r\p\r\p\q\r\q”
225 PRINT AT b-5,h;”\p\p\p\p\q\q\q\l\n\p\p\o\m”
230 PRINT AT b-6,h;”\l\n\p\r\r\q\o\m”;AT b-6,h+8;”\l\n\o\m”
235 PRINT AT b-7,h+1;”\l\n\p\q\o\m”;AT b-7,h+9;”\l\m”
240 PRINT AT b-8,h+2;”\l\n\o\m”
245 PRINT AT b-9,h+3;”\l\m”
250 BRIGHT 0
255 GO SUB 275
265 REM set house windows colours. w = poke colour, & also check snow height
270 REM called from main loop and from initial setup
275 FOR x=0 TO 1
280 IF l=3 OR w=22 THEN POKE a+h+1+x+32*(b-7),w+x: POKE a+h+1+x+32*(b-6),w+1-x
285 IF l=2 OR w=22 THEN POKE a+h+5+x+32*(b-7),w+x: POKE a+h+5+x+32*(b-6),w+1-x
290 IF l=1 OR w=22 THEN POKE a+h+8+32*(b-7+x),w+x: POKE a+h+11+32*(b-7+x),w+1-x
295 IF l=4 OR w=22 THEN POKE a+h+3+x+32*(b-9),w+x
300 NEXT x
305 LET w=10: REM set w to 10 after setup
310 REM reset
315 IF INT (v/32)<b-7 THEN GO SUB 325: GO SUB 125
325 LET b=19
330 LET v=(b-2.5)*32
335 LET a=22528+96
340 LET w=22
345 LET l=0
350 LET h=INT (RND*10)+4
355 DIM s(8)
360 DIM f(8)
365 REM get starting position of snow flakes
370 RESTORE 575
375 FOR x=1 TO 8
380 READ s(x)
385 READ f(x): LET f(x)=f(x)*32+s(x)+a
390 NEXT x
400 REM get user defined graphics
405 RESTORE 455
410 FOR x=97 TO 116
415 FOR y=0 TO 7
420 READ dat
425 POKE USR CHR$ (x)+y,dat
430 NEXT y
435 NEXT x
445 REM UDG DATA
450 REM snow flakes
455 DATA 66,219,36,90,90,36,219,66
460 DATA 0,102,90,44,52,90,102,0
465 DATA 0,36,102,24,24,102,36,0
470 DATA 16,16,16,56,124,56,16,16
475 REM snow base
480 DATA 255,255,255,253,120,1,10,170
485 DATA 255,255,255,115,112,0,10,170
490 DATA 85,170,69,170,68,170,16,170
495 REM fallen snow
500 DATA 0,0,0,0,0,102,254,255
505 DATA 0,0,0,0,0,112,252,255
510 DATA 0,24,122,255,255,255,255,255
515 DATA 0,0,195,227,255,255,255,255
520 REM house
525 DATA 1,3,7,15,31,63,127,255
530 DATA 128,192,224,240,248,252,254,255
535 DATA 254,253,250,244,232,211,167,74
540 DATA 127,191,95,47,23,203,229,82
545 DATA 128,63,127,170,0,255,255,82
550 DATA 1,252,254,85,0,255,255,74
555 DATA 0,84,42,84,42,84,42,84
560 DATA 128,63,127,170,68,255,255,255
565 DATA 1,252,254,85,34,255,255,255
570 REM Position Snow DATA: 1st = column, 2nd = starting row
575 DATA 0,0
580 DATA 8,6
585 DATA 16,4
590 DATA 24,2
595 DATA 4,5
600 DATA 12,3
605 DATA 20,5
610 DATA 28,1
615 REM MAIN VARIABLES
620 REM b = bottom of screen
625 REM v = average height of snow
630 REM a = start of colour attribute address
635 REM h = horizontal position of house
640 REM w = colour range of house windows
645 REM i = colour of falling snow
650 REM l = window l / off, sets w
655 REM c = column
660 REM r = row
665 REM f(x) = flake positions
670 REM s(x) = base position of flakes
675 REM p = current peek read val
680 REM n = position of active flake