I decided to improve my Z80 assembly skills by writing a game entirely from nothing. It’s based on a BASIC type-in game I found in an old book.
The source code and compiled binary can be obtained from my Github Repo here. Feel free to copy it and make modifications. If you fix or improve it, let me know!
A conversion of a BASIC type in game to the RC2014 Z80 computer
How to use
You will also need a joystick module that is mapped to Z80 input port 1.
You can upload the Intel HEX format binary (zombies.hex) to the machine through the serial terminal.
The game uses ANSI colours, so make sure your terminal is set to interpret those.
Execution starts at 0x8000
For more info
I like to collect old programming books for long-dead computer systems. I have a shelf full of them for the Spectrum, C64 and other machines. There’s also a small collection of generic books that present BASIC listings that work on most home micros from the 80s. While flicking through the Rainbow Book of BASIC Programs I came across a game that caught my attention.
The thing with these old books is computers back then weren’t very capable and a lot of the games were somewhat non-interactive. The games usually relied on text for graphics, and there was no full-screen motion or characters that moved across the screen.
The game I found, Zombies, is a little more sophisticated. I typed it out into my BBC Micro first to figure out what was going on - that didn’t help, as it is the game is almost unplayable and pretty confusing to figure out.
It looked like a good candidate for a more complex Z80 programming project. First though I had to reverse-engineer the BASIC code, which was a complete mess. BASIC has a reputation for being disorganised spaghetti, but it’s not until you sit down and try to understand the algorithms, that you realise just how unstructured it is.
Fortunately the code in this book comes with explanations about what each group of lines does, and that made the task somewhat manageable.
However, probably to fit the printing requirements of the book, the code is very compact. There are no variables with names longer than a single letter, zero comments and many lines have multiple instructions. I had to copy and write all over the code to figure it out.
Fortunately I wasn’t trying to accomplish this on my own. Once I’d figured out the general algorithm for the game I had plenty of resources to use when writing the Z80 assembly code.
I used the following books, they seem to be the standard reference books people used back in the 80s, and once you get your head around the terminology, the books are pretty useful.
Writing assembly code requires a certain degree of faffing. The CPU can only work on numbers stored in its registers, and specifically it can only do arithmetic on numbers stored inside its accumulator, or ‘A’ register. This leads to lots of shuffling of data around the CPU.
Here is an algorithm to convert an integer into its ASCII equivalent. All it’s doing is dividing a number by 10, and then adding hex 30 to the result. However to do it I have to shuffle registers onto the stack to preserve them, shuffle one register into another, and then undo it all at the end.
; convert 8 bit integer into ascii ; put number into c ; put address of string position in ix ; a changed itoa: push bc push de ld d,10 call C_Div_D add a,$30 ld (ix+1),a ld a,c add $30 ld (ix),a pop de pop bc ret
Another thing to consider when writing Z80 assembler is the state of the CPU, which is stored in its flags register. The books kept mentioning this, but I didn’t fully understand why until I tried to do a fairly simple
if - then - else type routine.
In assembly such a thing doesn’t exist. All you can do is compare one number with another. And really a comparison is just the result of subtracting the two numbers without actually doing the subtraction. At the end the CPU is left in a certain state, and by inspecting its flags you can work out what happened.
If the zero flag is set, both numbers are the same. If the zero flag isn’t set, they’re different. If the carry flag is set, the first number was smaller. If the carry wasn’t set, the first number was bigger.
If you think about this, it makes sense. Subtracting a number from itself results in zero as the answer, setting the zero flag. Subtracting a big number from a small number causes overflow - you need to carry in a 1 to balance out the maths.
Here’s some code that works out which way to move a zombie towards the player. It’s full of this kind of logic.
loop: ; if zombie dead, skip it push bc ld de,(ix) ; zombie xy pos ld a,(ix+2) ; zombie dead flag cp 0 jp z, endloop ; dead zombie, ignore ; now calculate new zombie pos ; cp flags ; Z - a==d, NZ - a != d ; C - a<d ; NC - a >d ld bc, (playerNew) ; player position ld a,b ; x pos player cp d ; x pos zombie jp z,xsame ; player/zombie on same x jp c,xbehind ; player behind zombie on x jp nc,xfront ; player in front of zombie on x
I highly recommend you learn assembly programming if you’re a programmer. It doesn’t matter which CPU you try to learn (picking an 8 bit one makes it easier), the process of learning it will teach you a lot. There’s a specific mindset you get into when manually instructing the CPU; if you can remove one instruction the code runs faster. Sometimes you can reorganise code to use less registers.
It also massively helps with your problem solving skills. There were several moments when I had bugs, and couldn’t figure them out. My code looked like it was supposed to work but didn’t. Every single time I fixed these bugs, I learnt something new.
However if you’re going to learn assembly, do what I did and try to write a decent sized program in it. Forget the “add two numbers and print the answer” or the dry “write a program to calculate the area of a circle” type programs. Dig out a basic game or something and try to write that.
You don’t even need a computer to run your code on. Fire up an emulator of your favourite 8 bit machine and use that. I use the SJAsmPlus Z80 cross compiler, so I could develop on a modern machine and then run the code on the actual hardware. I could have easily run it on a Z80 emulator too.