Simone's Blog (no JS mode)

Developing Game of Life on a TRS-80 in 2024

game of life TRS-80 running

Four months ago, I got into this retrocomputing spree and thought it'd be cool to snag an old computer from the 80s that I could mess around with using BASIC. After going through different options on eBay, I ended up picking a TRS-80 Model 100 as this one didn't occupy much space and could be easily placed on a bookshelf. According to various urban legends it's said to be the last computer Bill Gates ever wrote code on!

The computer that I initially purchased looked great but, there was something wrong with the motherboard, upon booting the computer up, after roughly 30 seconds, some random gibberish started appearing on the screen. I tried cleaning everything with isopropyl alcohol, just as The 8-Bit Guy taught us back in school, and replaced the memory backup battery, which didn't show signs of leakage. I diligently followed each and every possible guide online, but still nothing worked... Either a capacitor corroded one or more traces or something beyond my understanding was happening.

TRS-80 Gibberish screen

This Facebook group, Model T Computers also came in handy when I was trying to wrap my head around the problem as well. In the end, I bought another one that was recently recapped by a person from Romania, and it worked like a charm, the only downside was that the case was as yellow as the walls of a heavy smoker's house. Simply swapping the two motherboards and keeping the case from the first one did the trick!

I then got a perfectly working TRS-80 machine and it was now time to explore the 1983 Microsoft BASIC and fill those 29382 bytes of free memory. My new target was creating Game of Life in BASIC.

Too many line numbers

As soon as I started prototyping a few programs, I realized that keeping track of all the line numbers and GOTOs is an incredibly painful way of programming. As a 90s kid, I genuinely admire those who grew up in the 80s trying to code and plan how many possible unforeseen BASIC statements they could insert between line numbers.

This made me realize how impatient of a programmer I am sometimes. I often seek the result as fast as I can, even if I haven't fully flashed out the solution in my head. However, the process of putting in line numbers, planning GOTOs, and loading the program onto the TRS-80 emulator forced me to carefully think about my approach several times over before writing any code.

This whole line numbers thing started feeling a bit like a waste of time. So I decided to steer away from the orthodox 80s coding experience and I created a small "transpiler" in Ruby called baspiler.rb 💎 (BAsic tranSPILER). This script allowed me to write a simplified BASIC dialect where I didn't have to care about line numbers, the transpiler would simply add them all at runtime. It would also automatically find GOTOs and swap the line number references with labels instead (as these were not provided out of the box by the language).

Here is an example:

function print_loader
    print @318, lc$(cuco);
    cuco = (cuco + 1) mod 4
return

Would be transpiled to:

470 REM FUNCTION PRINT_LOADER
480 PRINT @318, LC$(CUCO);
490 CUCO = (CUCO + 1) MOD 4
500 RETURN

Then the gosub print_loader would be transpiled by the script to GOSUB 470.

Here's another example:

label_init print "hello world"
input "what's my name again"; n$
if n$ = "simone" then goto label_end
goto label_init
label_end print "right!"
end

translates to:

0 PRINT "HELLO WORLD"
10 INPUT "WHAT'S MY NAME AGAIN"; N$
20 IF N$ = "SIMONE" THEN GOTO 40
30 GOTO 0
40 PRINT "RIGHT!"
50 END

The script worked perfectly, and it allowed me to rapid prototype the algorithm without too much hassle, making it as close as possible to a syntax I'd find in modern programming languages. This simplified BASIC sort of reminded me of Lua!

You can compare my final implementation of Game of Life in the simplified BASIC version with the transpiled one and see see for yourself just how slightly more readable it actually is.

The Algorithm

After studying what was available in the BASIC dialect, I came to the conclusion that a Game of Life algorithm with nested loops and complex data structures was just too slow for this machine. So I opted for an algorithm that only used a single FOR loop to scan one array where the grid was going to be saved. In my head, this approach should have boosted the performance a bit, but we'll see the results at the end :)

I originally sketched an algorithm in Java making it as simple as possible in order to then port it over to BASIC.

The hard part of a Game of Life problem is just calculating the sum of living neighbouring cells. So in my solution, by just knowing the width of the grid and keeping track of the rows scanned, I could figure out the position of each neighbouring cell on the top, bottom and sides. One more detail that would make the algorithm a wee more complicated was that I wanted a wrapped grid. Since the screen was already too small and I didn't want the cells to just die of loneliness at the edges.

Going very quickly through the solution, I initialize a row counter to zero and then I loop over each cell of the grid stored in an array of length: width by height. Inside the single FOR loop I then create two indexes, one which tracks the top row and one for the bottom row relative to the current row being scanned. These indexes will be calculated using a MOD operator, so that: if I'm scanning the first row of the grid (row zero), my top row will be the row at the very bottom: ((row - 1) + height) % height. The same thing goes for the cells at the edge of the columns if I'm scanning item number width - 1, the neighbour on the right-hand side will be the very first item in the row, and so on and so forth...

After that, the formula for calculating the index of each neighbouring cell is quite straight forward and it goes like this: (A % W) + (W * R). Let's tart with the easy variables:

Finally, A can be one of the following:

-W -1 , -W  ,  -W +1
   -1 ,  i  ,     +1
+W -1 , +W  ,  +W +1

i is the index of the current cell being scanned, or simply the FOR loop counter. After creating eight variables for the indexes of each neighbouring cell, I simply sum these and calculate the living cells in order to decide if the current cell should live or die.

The result

(Emulated sped up version running at ~12Mhz)

Finally, my Game of Life was completed with a nice infinite GOTO loop and running with no issues. Sadly though, to my surprise, it took a whole minute and thirty seconds to render and compute a full generation on a grid of 39 by 7. *sigh* turns out that using those nested FOR loops and multidimensional arrays only made the program 5 seconds slower after all...

I'm curious if any of you would be able to come up with a more efficient algorithm written in TRS-80 BASIC!

After ensuring there were no bugs in the emulated version and validating each generation using this web app, I moved everything over to the actual TRS-80 machine. And oh gosh was it challenging to manually type all those lines of code. I introduced a few bugs during the process and had to LIST the entire program a couple of times before figuring out the issues.

Update 12/05/2024

Shortly after sharing this article on the Model T Computers group, someone pointed out that the performance issue was not in the use of multidimensional arrays or nested loops, but in the lack of native multiplication and division on the 8085! I should've known better... After eliminating all those modulo operations from my original implementation, I came up with a new algorithm that runs slightly faster, reducing the execution time to only 1 minute on a grid of 39 by 7.

Cassette tapes

game of life TRS-80 cassette

To conclude this TRS-80 exploration, I chose to save my newly written program onto a cassette tape to preserve a permanent memory of my accomplishment. I'll document all the steps I followed here since the ones I found online were a bit chaotic, and they might come back useful to me in the future.

First, insert the cassette onto the player and rewind it to the side you'd like to write on (A/B). Reset the tape counter to zero.

To save a cassette:

  1. disconnect the input cable from the cassette player
  2. press RECORD and PLAY buttons on the cassette player
  3. type CSAVE "GOL" on the TRS-80

To load the program from a cassette:

  1. move the tape counter to the position where the program was stored
  2. put the volume of the cassette player between 4 and 6 (this step is super finicky you might need to tweak the volume up and down to make it work)
  3. reconnect the input cable
  4. type CLOAD "GOL" on the TRS-80
  5. press PLAY

If everything goes well, the program should be correctly saved and loaded onto the cassette, accompanied by an annoyingly loud sound. I highly discourage doing all of this in a public space.

loading from cassette (video of me loading the program from the cassette)

This is the longest blog post I've written so far, and I really enjoyed sharing this experience. Creating something for an old computer evokes a very cozy feeling, on a practical term is basically useless unless you're doing it for learning purposes.

I would recommend to anyone learning to work with old tech and old programming languages to understand and appreciate the comfort of our current development environments.

It's still amusing to me that back in university, during one of the first lessons, we were taught never to use GOTOs because some guy considered them harmful. We all remained puzzled as none of us (including people with coding backgrounds like myself) had ever seen a GOTO statement in our lives!

The source code of all these experiments are available on GitHub in this directory.

Happy 60th Birthday BASIC!