I had this idea the other day. One of the problems I’ve had with the ZX81 is printing numbers, which is pretty slow. Due to this, many of my games avoided printing them. delaying showing score until after play has ended. My idea was to try to find a way to speed that up. Not only did I find a way, I decided to implement it in an older game of mine: E.T.
# Starting with the fix.
Now, the reason numbers take so long to display has more to do with how the ZX81 stores them. Using floating point numbers, the ZX81 has to deal with more than just digits. The radix is calculated based on the exponent—thus the floating point. Since the ZX81 has to deal with both very small and large numbers, it needs to determine what to print. That process requires a bit of work, and thus the impact to speed.
Actual calculations with ZX81 numbers aren’t too bad. At least, not for addition or subtraction, the common use case for a scoring system. So, to fix the problem, I cheated a bit. I treated the numbers as an index. Instead of printing the numbers directly, I printing a substring of a variable that corresponded to the digit it represented.
Below is some example code show a way to add 3 to a score. The score has three digits. The counters resets to zero when you exceed the routine. It isn’t perfect, but you get the general idea of how the routine works.
10 DIM D(3)
20 LET N$="0123456789"
30 PRINT AT 2,0;N$(D(3)+1);N$(D(2)+1);N$(D(1)+1)
40 GOSUB 100
50 GOTO 30
100 REM ADD 3
110 LET D(1)=D(1)+3
120 IF D(1)<10 THEN RETURN
130 LET D(2)=D(2)+1
140 LET D(1)=D(1)-10
150 IF D(2)<10 THEN RETURN
160 LET D(3)=D(3)+1
170 LET D(2)=D(2)-10
180 IF D(3)<10 THEN RETURN
190 DIM D(3)
Although this looks long, to my surprise it runs very fast. I tested against printing numbers and the difference was night and day. I played around with different ideas, changing the routine to see how it would preform with different math functions. Subtraction was probably the most difficult, but all of them ran decently. My minor optimization attempt was proving fruitful.
# Testing it out.
With my new fangled routine, I started to search for a way to try it out. One game that I landed on was my old E.T. game. That game displayed a constantly decreasing energy counter. Because of this, the game play was incredibly slow. It wasn’t the only reason, but the energy counter was a major contributor. So much so, I had converted the game to machine code using MCoder II back in 2012.
To see how bad the counter really impacted the game, I commented out the display routine. To my surprise, the game was much more playable. Yes, it was still slow. But it wasn’t unbearable. I started to think that I could optimize the code enough to make it playable in BASIC.
To that end, my next step was to migrate my test routine into the E.T. code. The original version printed a number with a space if less than 100. The new routine just displays all the digits, no logic tests required. I think it actually looks better with the fixed digits as depicted below.
E.T. 23, Waiting to Go Home, ZX81 Screenshot, 2023 by Steven Reid
Since my test routine used addition, I had to adjust it to decrement the energy. The actual routine took some debugging to get the subtraction to work properly. The final routine even has some recursion in it. With the new routine working, I could properly test how it worked in a real game. To my surprise, playing didn’t feel any slower than it did when I had just commented out the routine. My idea proved viable.
# Moving beyond idea.
With my new routine in hand, I cleaned up the routines to deal with all the use cases for updating the energy. This included adding larger numbers such as 10, 20 and 100. I cheated a bit to avoid writing different routines. It looks more like what I would do in assembly than BASIC, but it works.
Now, E.T. suffers from some other slow routines. I had, for some reason, combined dynamic print routines with static ones. Within a loop, such as creating the screen, it was burning a lot of processing to print the pits that didn’t change. Breaking apart the print into static and dynamic parts drastically sped up those routines.
Making some minor changes to auto run the game and restart it, I soon had a decent running BASIC version of E.T. It still wasn’t perfect, but was very playble. In fact, that is what I did. I played the game to make sure it worked. Now, it isn’t anywhere near as fast as the compiled version, but it worked well enough.
# Further optimizations.
Having proven out my routine, I got distracted and see if I could make E.T. a better BASIC game. This led to fixing the problems it had. A sore spot was the power pills. I had used a silly giant routine print routine with lots of random routines. Commenting that out showed how bad that routine really was. The game’s speed improved quite a bit.
Knowing that the program had issues, I found myself digging through the code and reworking it. I even started to use a little timing routine to see how different code would perform. I had seen a lot of work around memory management, but not as much on optimizing ZX81 BASIC. My goal was just to speed things up—at least at first.
E.T. 23, Discovering a Flower, ZX81 Screenshot, 2023 by Steven Reid
As I dug deeper, I started to fix the shortcomings of the game. Specifically, I fixed the power pills to be more random and only display one at a time. After a bit of time, the pill would go away if not consumed. To be honest, although the routine is better, I didn’t find much value having it in the game. I decided to leave it in, but removing it would actually improve things a bit.
The other fix was those pits. I moved all that into the initializing code and then added in a little routine to put different items in the pits. This allowed me to set a random location for each item placed into the pits. With each pit containing a unique item, you have to go visit all of them to find the phone pieces. This improvement made the game a lot more fun.
E.T. 23, Finding a Phone Piece, ZX81 Screenshot, 2023 by Steven Reid
Another change I made was how you find the landing pad. I got rid of the “special path” from the original game. But, I expanded the routine to let you find it if run into any part of the landing pad. It isn’t too hard to come up with your own strategy for finding the pad.
# Moving beyond optimizations.
Funny enough, with all the tweaks I made I decided to do a bit more optimizations. One of the things that always bugged me was how the ship disappears after dropping E.T. off. It isn’t as slick as the scene for him returning home. To fix that, I decided to optimize those routines. This ended up being quite a chore.
To generalize the routine, I needed to deal with E.T. swapping positions depending on if he was being dropped off or picked up. Most of the loop code worked well, but I kept having issues with the drop off or pick up. The fixed ended up being a bit more complex than I intended, but I did get it to finally work.
E.T. 23, Elliott Taking E.T. Home, ZX81 Screenshot, 2023 by Steven Reid
Having that done, though, I needed to add one more thing. E.T. would just pop over to the house after being dropped off. That never really made much sense. To fix it, I decided to have Elliott find him and bring him to his house. This made the intro scene not only longer, but it now made a lot more sense.
# Putting it all together.
With the fixes in place, along with my shiny new number routine, made E.T. a better game. It isn’t by any means perfect. I could still should still add an antagonist to add some challenge. But the play is now functional and requires a bit of strategy to work out. I even dropped the energy quite a bit to make it more challenging.
E.T. 23, Finished, ZX81 Screenshot, 2023 by Steven Reid
More importantly, my tweaks showed me that a BASIC game can be improved even on the tortuously slow ZX81. I don’t know if anyone else ever tried what I did. I’ve read lots of examples of how to save memory and the impact to speed for each. But not the opposite attempts to speed up the code.
For me, moving to assembly was freeing. MCODER was my stepping stone, but had limitations. Learning z80 assembly allowed me to do so much more. It wasn’t a panacea, but it did open up possibilities for how the little machine could be more than it was. But I still have a big place in my heart for how I originally used my ZX81. This trip down into the rabbit hole that is ZX81 BASIC proved worthwhile