Zombies! - A Game Written in Z80 Assembly

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.

Watch the video!

Grab the code!

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!

Where it came from

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.

Once all programming books were like this…

Once all programming books were like this…

Zombies!

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.

Decypher that!

Decypher that!

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.

Decypher that!

Decypher that!

Z80 Assembly

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.

Z80 Assembly Language Subroutines

Z80 Assembly Language Subroutines

Programming the Z80

Programming the Z80

Register Twiddling

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

Conditions and CPU flags

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

Learn Assembly!

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.

Did you like this post?

if you did, it'd be really nice if you shared it