I haven’t added the #games tag to this post but I have updated the .tap download in CaliRun to include these Sinclair BASIC optimisations.
There are many techniques for optimising Sinclair BASIC programs. Some tackle general complexity like Big O notation, some are relevant to all BASICs, and some are specific to Sinclair BASIC. I’m quite fond of the Make it work, make it right, make it fast approach. Optimising out complexity from problem areas if possible before getting closer to the metal with each pass, sometimes even dropping down to machine code.
I recently came across a series of posts with Sinclair BASIC specific optimisations that includes some top down analysis, but also some very technical and Sinclair BASIC specific optimisations. The author has also released a tool called ZX-Basicus that will analyse your Sinclair BASIC program and apply some of these optimisations (described on the download page). This won’t include some top down techniques such as where complexity can be optimisied out, but is complementary to those and can be included in your build pipeline (I have!) so that once you’ve done everything you can it will take it from there.
I did also apply an optimisation I found in one of the posts which applied to how I was cycling through the 32 BEEPs in my version of Magical Sound Shower. It’s explained in the posts much better than I can but by replacing LET n=n+1:IF n>32 THEN LET n=1 with LET n=n+1 OR n=32 is a saving of 540 microseconds. So a very nice improvement and something to keep in my back pocket.
This is an amazing resource and tool and I’m very grateful. The proof is in the pudding however and as the last game I wrote CaliRun is very sensitive to performance bottlenecks I used it as a guinea pig and the following optimised code was generated. I won’t (and probably can’t) identify all the improvements that it made but the game (now updated as the download) is genuinely quicker. The full Sinclair BASIC listing as generated by ZX-Basicus is as follows.
10 CLEAR 61439 : GOSUB 298
14 GOSUB 280
16 GOSUB 216
18 LET s = 0 : LET h = 0
20 IF s > h THEN LET h = s
22 GOSUB 192
24 GOSUB 136
28 FOR u = 1 TO 16 : FOR q = 1 TO 4 : FOR p = 2 TO 7 STEP 5 : INK p : PAPER p : LET c = ( INKEY$ = "p" ) - ( INKEY$ = "o" ) : IF c <> 0 THEN BEEP 0.01 , 24 : LET a = a + c
38 FOR x = 1 TO v
40 IF ATTR ( 20 , a ) > 64 THEN GOTO 214
42 RANDOMIZE USR 61440 : NEXT x : IF ATTR ( 22 , a ) = 64 THEN POKE 23232 + a , 66
50 LET s = s + v : PRINT # 1 ; AT 1 , 27 ; BRIGHT 1 ; PAPER 1 ; INK 5 ; s
54 IF m ( n ) > 0 THEN BEEP 0.01 , m ( n )
56 LET n = n + 1 OR n = 32 : LET y = b + ( b * 8 ) : LET z = 22528 + d : POKE z - 1 , y : POKE z + 16 , y : PRINT AT 0 , d + 1 ; INK 0 ; PAPER 0 ; BRIGHT 1 ; "\udg(BBBBBBBBBBBBBB)" ; : LET y = 64 + p + ( p * 8 ) : POKE z , y : POKE z + 15 , y
74 FOR x = t ( u , 4 ) TO 10 STEP t ( u , 4 ) : POKE z + x , y : NEXT x : IF t ( u , 3 ) > 0 AND p = 2 AND q = 4 THEN POKE z + 32 + t ( u , 2 ) , t ( u , 3 ) + 64
80 RANDOMIZE USR 61440
84 LET d = d + t ( u , 1 ) : NEXT p : NEXT q : NEXT u : GOSUB 92
90 GOTO 26
94 LET u = 1 : GOSUB 118
100 LET v = v + 1
104 LET b = b + 2 : IF b > 7 THEN LET b = 1
108 LET p = 7
110 BORDER b : IF b = 7 THEN PRINT AT 3 , d ; INK 0 ; PAPER 7 ; BRIGHT 1 ; "Congratulations!" : PAUSE 0 : GOTO 20
114 GOSUB 134
116 RETURN
118 PRINT AT 2 , d - 3 ; INK 7 ; PAPER 7 ; "\udg(BBBBBBBBBBBBBBBBBBBBBB)" : PRINT AT 3 , d - 3 ; INK 7 ; PAPER 7 ; "\udg(BBBBBBBBBBBBBBBBBBBBBB)" : PRINT AT 4 , d - 3 ; INK 7 ; PAPER 7 ; "\udg(BBBBBBBBBBBBBBBBBBBBBB)" : PRINT AT 5 , d - 3 ; INK 7 ; PAPER 7 ; "\udg(BBBBBBBBBBBBBBBBBBBBBB)" : PRINT AT 6 , d - 2 ; INK 5 ; PAPER 5 ; "\udg(B)" ; AT 6 , d + 17 ; "\udg(B)" : PRINT AT 7 , d - 2 ; INK 5 ; PAPER 5 ; "\udg(B)" ; AT 7 , d + 17 ; "\udg(B)" : PRINT AT 8 , d - 2 ; INK 5 ; PAPER 5 ; "\udg(B)" ; AT 8 , d + 17 ; "\udg(B)" : RETURN
134 PRINT AT 0 , 0 ; INK b ; PAPER b ; "\udg(BBBBBBBB)" ; BRIGHT 1 ; "\udg(B)" ; INK 0 ; PAPER 0 ; "\udg(BBBBBBBBBBBBBB)" ; INK b ; PAPER b ; "\udg(B)" ; BRIGHT 0 ; "\udg(BBBBBBBB)" : RETURN
138 LET b = 4 : LET d = 8 : LET a = 10 : LET c = 0 : LET v = 3 : LET n = 1 : LET s = 0 : INK b : PAPER b : BORDER b : CLS
168 FOR x = 0 TO 21 : PRINT AT x , 0 ; "\udg(BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB)" : NEXT x : PRINT # 1 ; AT 0 , 0 ; BRIGHT 1 ; INK 0 ; PAPER 0 ; "\udg(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)" : PRINT # 1 ; AT 1 , 0 ; BRIGHT 1 ; INK 0 ; PAPER 1 ; "\udg(EEEEEEEEEEE) " ; INK 3 ; "CaliRun" ; INK 0 ; " \udg(EEEEEEEEEEEE)" : GOSUB 134
180 FOR x = 0 TO 23
182 RANDOMIZE USR 61440 : NEXT x : PRINT # 1 ; AT 1 , 1 ; BRIGHT 1 ; PAPER 1 ; INK 5 ; "HIGH:" ; h ; # 1 ; AT 1 , 21 ; BRIGHT 1 ; PAPER 1 ; INK 5 ; "SCORE:" ; s : GOSUB 118
190 RETURN
192 BORDER 1 : PAPER 1 : INK 5 : CLS : PRINT AT 2 , 0 ; PAPER 0 ; INK 3 ; " " ; INK 2 ; "\udg(A)" ; INK 3 ; " CaliRun " ; INK 5 ; "\udg(B) " : PRINT AT 4 , 5 ; "Race across California" ; AT 5 , 5 ; "in your Ferrari F110" ; AT 6 , 5 ; "through 5 stages that" ; AT 7 , 5 ; "get faster and earn" ; AT 8 , 5 ; "you more points if you" ; AT 9 , 5 ; "manage not to crash!" ; AT 11 , 5 ; "O is Left, P is right." : PRINT # 1 ; AT 0 , 9 ; INK 3 ; "HIGH SCORE " ; h ; # 1 ; AT 1 , 9 ; INK 3 ; "LAST SCORE " ; s : INK 6
202 FOR x = 100 TO 10 STEP -10 : PLOT ( 255 - x ) / 2 , 8 : DRAW x , 0 , -3.1415927 : NEXT x : INK 0 : PLOT 0 , 7 : DRAW 255 , 0 : PRINT AT 13 , 5 ; INK 3 ; "PRESS ANY KEY TO START" : PAUSE 0 : CLS : RETURN
214 PRINT AT 21 , a - 1 ; INK 6 ; PAPER 2 ; FLASH 1 ; BRIGHT 1 ; "\gph(:'':)" : PRINT # 1 ; AT 0 , a - 2 ; INK 3 ; PAPER 0 ; BRIGHT 1 ; "\udg(D)" ; # 1 ; AT 0 , a - 1 ; INK 6 ; PAPER 2 ; FLASH 1 ; BRIGHT 1 ; "\gph(:..:)" ; # 1 ; AT 0 , a + 1 ; INK 6 ; PAPER 0 ; BRIGHT 1 ; "\udg(C)" : RANDOMIZE USR 61452 : PAUSE 0 : GOTO 20
218 DIM t ( 16 , 4 ) : LET u = 1 : DIM m ( 32 ) : LET n = 1 : RESTORE 240
234 FOR x = 1 TO 16 : READ t ( x , 1 ) : READ t ( x , 2 ) : READ t ( x , 3 ) : READ t ( x , 4 ) : NEXT x : LET u = 1
236 FOR x = 1 TO 32 : READ m ( x ) : NEXT x : LET n = 1 : RETURN
242 DATA 0 , 0 , 0 , 11
244 DATA 1 , 7 , 1 , 5 : DATA 0 , 0 , 0 , 5 : DATA -1 , 8 , 3 , 5 : DATA 0 , 0 , 0 , 3 : DATA -1 , 9 , 5 , 6 : DATA 0 , 0 , 0 , 6 : DATA 1 , 8 , 6 , 6 : DATA 0 , 0 , 0 , 3 : DATA 1 , 3 , 1 , 9 : DATA 0 , 7 , 3 , 5 : DATA -1 , 3 , 5 , 9 : DATA 0 , 0 , 0 , 3 : DATA -1 , 8 , 6 , 5 : DATA 0 , 7 , 1 , 5 : DATA 1 , 0 , 0 , 5 : DATA 12 , 0 , 16 , 9 , 0 , 12 , 0 , 11 , 0 , 14 , 0 , 14 , 7 , 0 , 11 , 0 , 12 , 0 , 16 , 9 , 0 , 12 , 0 , 11 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0
280 RESTORE 286 : LET i = USR "a" : LET z = i + 40 - 1 : FOR x = i TO z : READ y : POKE x , y : NEXT x : RETURN
286 DATA 0 , 38 , 36 , 255 , 153 , 255 , 195 , 0
288 DATA 0 , 60 , 66 , 66 , 126 , 231 , 255 , 66 : DATA 0 , 0 , 6 , 7 , 4 , 38 , 93 , 154 : DATA 0 , 0 , 48 , 48 , 96 , 172 , 170 , 89 : DATA 255 , 0 , 0 , 0 , 0 , 0 , 0 , 255
298 RESTORE 308
300 FOR x = 1 TO 43 : READ y : POKE x + 61439 , y : NEXT x : RETURN
310 DATA 33 , 191 , 90 , 17 , 223 , 90 , 1 , 192 , 2 , 237 , 184 , 201
314 DATA 1 , 16 , 39 , 121 , 230 , 7 , 87 , 120 , 230 , 1 , 7 , 7 , 7 , 7 , 178 , 211 : DATA 254 , 0 , 0 , 0 , 0 , 11 , 120 , 177 , 32 , 233 , 62 , 2 , 211 , 254 , 201