I’ve been on a retro computing binge lately and was recently watching one on programming on what it was like to program in the 80’s. Having lived and programmed during that time, I know full well what that was like. But the narrator was showing off an Apple ][+, a computer I didn’t own. Although I programmed on an Apple ][ in high school, my memory of it is vague at best. But what was really interesting was what he was programming a snake game. Being something I’d never tried myself, I thought now would be as good time as any.
# Giving snake a chance.
Now, mind you, I have played snake like games in the past. In fact, I’m pretty sure I played one on the Apple ][. Given that, I knew the concepts. I’d even programmed a few path like games back in the day, but those were rudimentary games. Mine lacked any path management and often ended after capturing some item. If memory servers me right, I used some of those in Water Bug or Water Bug II. But, alas, not a snake game.
Watching the video, it was quite intriguing to see him walk through the various logic of the game. There was much time spent just showing how to place and move the character. Although I zoned out a good portion of the video and then headed for a run, my mind was already churning. I was pretty sure I could write something similar for the ZX81.
Now, to my surprise, I decided to write Snake in BASIC. It was what the video was using. Outside of a few tests, most of my recent work was in z80 machine code. That doesn’t mean I couldn’t write a version using assembly. The challenge of creating one in BASIC was the motivating factor in my decision.
# Diving into code.
With a plan in mind, I started to code out the snake that evening. I spent a fair amount of time thinking about the program’s structure. I wanted much of the game to play at the top of the program. To that end, I moved much of the setup and initialization code into subroutine towards the end.
As I worked out the code in my head, that subroutine was further divided into initial state and subsequent resets. I was thinking about their being stages of game play and wanted to reuse much of the code in each stage. I then mapped out some movement, and that was where I stopped for the evening.
Although it was getting late and my mind was still racing. I went to bed thinking about different ways to work out the path management. The biggest challenge with snake is managing the path and removing the tail. As the snake grew, I needed a dynamic way of managing the path size.
My initial thought was a multidimensional numeric array. The first value of the array would hold the X axis and the other the Y axis. I threw the
DIM A(2,255) into the program and then headed to bed, still not having worked out the math for it. Because of that, my mind kept stewing on how to make that work through the night.
# Next day coding session.
Waking up the next morning, my mind was pretty much made up to not use a numeric array. There were a couple of concerns. First, it would be a huge amount of memory as the ZX81 BASIC uses only floating point numbers. Although I didn’t see myself running out of memory given the small size of the program, it still felt excessive.
The second, and more troublesome problem, was how would I rotate the array. I figured I could use a pair of pointers, but that would require a certain amount of math. My worry then would be speed. I felt the approach wasn’t realistic in BASIC.
My other idea was to just code some routine in assembly, that left me feeling like I failed. I wanted a BASIC game. Using machine code, I probably should just code the whole game in it. Plus, I’d still have to manage the floating point number structure and such. I rejected that idea pretty quickly.
# Landing on a solution.
The one that stuck, however, was using a string array. This had a couple of advantages. The first is I could code the X or Y axis values in a single byte each. This reduced the amount of memory required by a factor of 5. Again, not really a problem, but definitely allows for any future changes.
The second advantage is I could use the native string slicing features in ZX81 BASIC to rotate the arrays. This is actually pretty powerful on the ZX81, even if done in a very nonstandard way from how other BASIC dialects did it. You simply use the
TO function to slice up the string how ever you want it.
My younger self had abused the heck out of string slices. I ran across some examples for rotating text as a kid and used it all sorts of games. One of my favorite uses was in Water Bug to create rolling screens.
In Snake, I created a multidimensional string array, similar to the numeric array I started with. Thus,
Z$(1) held the X values and
Z$(2) held the Y values. Note that the ZX81’s arrays started at 1 instead of 0. Now, my first versions did slice the array, but as I started to test I found I could use a trick to remove the slice altogether.
The ZX81 won’t let you add more characters than the array is defined as. This is due to string arrays being fixed in size. I found I could just append the first array and it would automatically ignore the last character—no slice required. Using only two lines I could rotate and update the array as follows:
30 LET Z$(1)=CHR$ X+Z$(1)
40 LET Z$(2)=CHR$ Y+Z$(2)
My original plan was that to push the new location into the back of the array. But by reversing that, it shortened the code and also resolved a bug that was cropping up in the math. Given The found that the SIZE variable, which holds how big the snake is, could be used to clear the tail each move. All I had to do was convert the string characters to numeric values in this line:
100 PRINT AT CODE Z$(2,SIZE),
CODE Z$(1,SIZE);" ";AT Y,X;
Notice that I haven’t actually printed the snake yet. The game needed to peek at that position to determine if you ate food or hit something. Depending on that outcome, it either prints your snake or an inverted version if you died.
# Making it a game.
Now, the snake I remember had momentum. Thus, I coded the game to make your snake move constantly in a direction. Hitting keys would change your direction. This diverted from what I remember in the video, although he may have gotten to that later. In any case, I reworked the original movement to ensure the snake never stopped.
With movement working, I then started added various game elements. That included a boarder to avoid, food to eat, growth to manage, and some sort of goal to complete. I started first with initializing the game area with some text stating the goal. I think print in all the food that will help you meet that goal.
I was careful to make sure the food landed in unique locations, skipping if where the snake started or other food had been placed. It does make the later stages a bit more tedious as you have to wait for all that food to get placed. I contemplated using the ZX81’s
FAST mode for that, but decided to shorten the array instead. I like the look and, although slow, a 100 seemed manageable in my testing.
I also made some visual changes. I went from a static snake graphic to one that changes each run. This prompted me to modify the food a bit to look different from the snake. Also, if you died, I invert to the normal character using some simple math. The code was tight and fun to write.
# All done, sort of.
As always, finishing a program is a mixed feeling for me. Ultimately,, the game works well and I loved watching the snake grow and move around. The play mechanics are pretty solid once you get used to the controls. I decided to use the more modern AWSD layout, but the user can adjust line 60 whatever whatever layout they prefer.
That said, I still feel the game could be more interesting. I contemplated adding food as it ran, but worried that would cause the game to stutter. Either that, or I could add in
FAST in while printing food to speed that up. But if I do that, I would need to add some sort of pause before you start. As stated earlier, I discarded that idea for now.
The other aspect that I would like to add are obstacles in the levels. Having something to dodge while getting your food would make the game more strategic than it is now. Modifying the code would be easy enough using some repeating patterns.
The last idea is adding in other snakes. I don’t think the BASIC code would handle the AI though. The current speed is decent. But, without any delay routines, adding in any more logic would slow things down. I might reserve that idea for a future machine language version.
And there you have it, my version of Snake on the ZX81. Unlike many others, it wasn’t one of my first programs. 40 years later, I can finally add it to my list. In any case, I enjoyed the coding challenge and hope you enjoyed playing it.