With everyone talking about making retro games (but actually making modern games with a retro visual look) I decided to make a real retro game. In machine code. On the Spectrum.
This page chronicles my first attempt, and it's appearance as the newest game on the Sinclair ZX Vega.
Back in May I spent an evening watching retro game documentaries - From Bedroom to Billions, Atari:Game Over, and Video Games-The Movie. All good in their own way, with individual quirks, issues, and omissions. (Disclaimer: I'm a computer historian!) The one thing I took away was how long ago it all seemed! What was worse, is that I've been in the industry for longer that many of the participants, and so I felt really old :( I started programming in 1983, and have developed on almost every console, home micro, and online service since then. But I haven't done it recently...
...which is when I thought about making Adventure (1979 from Atari) on the ZX Spectrum (1982 from Sinclair.)
It was so crazy, it just might work. (At least I knew that, technically, it would be feasible. And that even I could manage the graphics.)
My plan was to remake Adventure as if it were an unlicensed port of the original. Which, in essence, it is! I reasoned about what corners a developer working in 1982 might cut to get the product out - my dragons are only 1 character high, for example, instead of 2. I wondered how much extra detail would go into the map - not a lot, I thought! While the monochrome approach to screen design didn't take hold until 1984-ish, as a means to prevent attribute clash, I also considered that a lot of ZX Spectrum developers would have just come from the ZX81 and so would do little more than colour the ink of a tile.
My first step was to create a development environment. I needed a cross-assembler (pasmo worked, and had adequate documentation) and an emulator (the Zero emulator worked out the box, too.) I also grabbed a text editor called ConText because I was impatient and couldn't work out how to configure key shortcuts in Sublime! With this, my editor would assemble the current source file by pressing F8, and then load and run the resultant TZX binary in the emulator by pressing F9. (I picked keys with a gap between them, so it would be more difficult to accidentally hit the wrong key.)
It's not a perfect solution, but workable. I did need to mute my development PC because the loading procedure of Zero insists on doing it properly. That is, with the wiggly lines and all the screeching! I had a turn around of about 15 seconds per build, which is a far sight quicker than it was 'back in the day' when I first coded on the ZX Spectrum.
From here, I set about writing basic routines for drawing sprites, reading the keyboard, clearing the screen, and so on. I also started researching the various ROM routines to change the border, make beeps, and so on. Again, modern technology such as the Internet has meant I can improve the turn-around time significantly. Whereas I used to have a handful of manuals and a bucketful of bookmarks, I now have a couple of browser tabs open and find anything I want quicker than I did with my books. In one tab was the Z80 instruction set, and in other was the disassembled ROM. The find on page window was also permanently open. I ultimately didn't use the ROM breakdown much, as I wrote most of the code myself often cobbled together from existing demos. But the instruction set was a fantastic resource. I could find out in a fraction of a second if the registers I was using could be combined in the way I wanted, or whether I'd need to push bc/pop de to move them. This, along with the demo code I found from Jason, was the biggest time saver in the development.
There's several shortcuts, bugs, and development war stories. Here's just a few...
I found a tool called SPECTRA_ImageConverter which would allow me to turn a JPG or PNG into a data block that I could copy straight into the Spectrum's display memory to replicate the image. In fact, my first experiments with pasmo came from creating a few slideshows using this image conversion technique, and some basic key press code. It ensured I knew enough to write stuff to the screen, and read input from the user. With these two things I can, with some or more effort, get anything to happen in-game.
I tried to implement the game in an object-oriented way so I had a game object 'structure' consisting of a block like:
DATA_PLAYER defb 0, 0, 0, 0, 0, 0, 0,0, 0 defw collision_nullwhere the total size of each object would be identical (be it player, dragon, key, portcullis, and so on) and each object would use the data as it needed. This meant hogging the IX and IY registers to reference an object. But with this approach reading the necessary data was easy (with ld c,(ix+DATA_OFFSET_XPOS)) and could be shared. The first bytes indicate the screen and co-ordinates (and the renderer automatically ignores anything on a different screen), with the other parameters indicating the graphic image, animation frame, and some object-specific user data.
And yes, it has animation! I's generally too small to see, but it truly is there.
The code that opens the portcullis is still a horrid hack! All collisions come into 1 of 2 categories: against a wall, and against an object. Since the objects move about, we need to check the bounding box of each object to check for collisions. That's (relatively) expensive, and so is only done with the few moving objects we have in the game. Checking for the wall is done more often (because there are more walls), and needs to be cheaper. The design meant that all walls were aligned to an entire square block, so I only need to determine which square(s) the player was trying to walk to and check one byte for each square. This mean I had to change the byte holding the portcullis. Which meant writing over the static data block holding the map. It's an ugly solution, for sure. But by creating a line in the game start code which ensures the portcullis is written to the map data at the start of every game, it works well enough.
The parameters for 'score' and 'air' are my invention. I think they add an urgency and purpose to the game, since it's quite simple otherwise. Warren's original Adventure had different difficulty levels of play, with only one level my game needed this addition.
In my first pass the dragons never moved. It was a tech demo, rather than a full game. (And it wasn't scheduled to appear on the Vega at the time.) This is why the dragon guards the second key in the bricked alcove as he does - it required the player to first get the sword to kill him. Once the dragons moved, this is unimportant as you can quickly sneak past it to collect the key. When I introduced movement to the dragons (when I was asked to incorporate the game into the Vega) I left the wall in place. If you review the code, you'll also see how cheap the code is to move both dragons!
Then there's the 'pickup sweep' bug, which caused graphic corruption when the key was picked up. But only sometimes. Well, there were three things going on here.
And the bug disappeared!
So I changed the 'sweep' sound for a three-note motif using the music player code, and it worked flawlessly. What a hack! I suspect the stack, IX, and/or IY registers were being corrupt but I couldn't fix it with my usual tricks of push/pop so I kept the new audio motif, shipped it out, and continued with the day job.
Of all the code, I like sgxDrawSurface_drawTextCentre which worked perfectly first time. I hate the OS routines that trample over something and cause me to waste cycles doing the push/pop trick. I should spend time unifying the registers so I don't have to keep moving things from BC or DE, just to have access to the instructions I want. I also like the xor a trick to quickly set a to zero, and or a to set the flags, but change nothing else. There's probably a few other tricks in there, too, but I'd have to go searching for them.
From a game perspective, it'd be great to add the bat in there. (The tunnel I never really cared for, but the bat is essential as that changes the game each time you play it, adding the replayability which my current version lacks.)
I've known about the Vega since before it was called the Vega. Or, indeed, before it was designed. I used to work with Paul in 2008 when he first discussed the idea with me. I had already developed my EMF emulation technology at this time, so I mocked up some ideas at home to see if I could do it. I discovered two things:
1. I knew how to do it
2. I didn't know where to find enough free time to do it well!
But over the years Paul and I kept in touch, and the idea would periodically resurface. During the 30th anniversary celebrations I met Chris Smith and recommended Paul get in touch with him, instead. The rest is history, and the cliche goes.
As I was progressing on the game, I posted a few pictures on Facebook and Twitter and Paul got back in touch. "Can you do a version for the Vega?" he asked. I could, of course. When's the deadline? "This Saturday" Well, I reasoned that since his staff wouldn't be working at the weekend, could I have a 2 day extension to finish and polish it during the weekend and give him a build on Monday? He agreed, and a version was duly delivered.
A week later I got word of a bug (the 'pickup sweep' bug), so fixed it in my lunch hour and sent a build back. A week later it was built into the production model of the machine making it (probably) the quickest release ever! Certainly the newest game to be included on the Vega. And it is that version which is now in the memory of the machine you now hold.
It might not be a great achievement, but to be a brand new game on a brand new console, and at launch, is a feat I've never managed before.
I have thought about porting the library code to the ZX81, which would involve ignoring all the colour information, all the graphics, sound, and worry about getting the code size down to fit in 16K. Without the large screens for title, win, and lose, I think it's very possible. The collision and movement code would be the biggest game-oriented re-write, because you only have 32 units across the screen on the ZX81, instead of the 256 on the ZX Spectrum. But that's a problem for another day...
The full source to ZX Adventure is available!