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 deal with general complexity, like Big O notation, some are relevant to all BASICs, and some are specific to Sinclair BASIC. I am quite fond of the Make it work, make it right, make it fast approach: optimise complexity out of the problem first if possible, then get closer to the metal with each pass, sometimes even dropping down into machine code.
I recently came across a series of posts on Sinclair BASIC-specific optimisations. They include some top-down analysis, but also plenty of very technical Sinclair BASIC tricks. The author has also released a tool called ZX-Basicus that analyses your Sinclair BASIC program and applies some of these optimisations, as described on the download page. It will not cover the more top-down techniques where complexity can be optimised out, but it complements that sort of work very nicely and can be included in your build pipeline (as I have done) so that, once you have done everything you can, it can take things a bit further.
I also applied an optimisation I found in one of the
posts
that applied to how I was cycling through the 32 BEEPs in my version of Magical Sound
Shower. It is explained in those posts much better than
I can explain it here, but replacing LET n=n+1:IF n>32 THEN LET n=1 with LET n=n+1 OR n=32
saves 540 microseconds. That is a very nice improvement, and definitely one to keep in my back
pocket.
This is an amazing resource and tool, and I am very grateful for it. The proof is in the pudding, though, and because the last game I wrote, CaliRun, is very sensitive to performance bottlenecks, I used it as a guinea pig. The following optimised code was generated. I will not, and probably cannot, identify every improvement it made, but the game, now updated in the download, is genuinely quicker. The full Sinclair BASIC listing as generated by ZX-Basicus is shown below.
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