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