Back in April 2021 I introduced a simple text-based adventure game, The Lighthouse of Doom, which I'd written in Z80 assembly language for CP/M systems.
- My game is on github here:
- It was discussed on hacker-news at the time:
As it was recently the 40th Anniversary of the ZX Spectrum 48k, the first computer I had, and the reason I got into programming in the first place, it crossed my mind that it might be possible to port my game from CP/M to the ZX Spectrum.
To recap my game is a simple text-based adventure game, which you can complete in fifteen minutes, or less, with a bunch of Paw Patrol easter-eggs.
- You enter simple commands such as "up", "down", "take rug", etc etc.
- You receive text-based replies "You can't see a telephone to use here!".
My code is largely table-based, having structures that cover objects, locations, and similar state-things. Most of the code involves working with those objects, with only a few small platform-specific routines being necessary:
- Clearing the screen.
- Pausing for "a short while".
- Reading a line of input from the user.
- Sending a $-terminated string to the console.
- etc.
My feeling was that I could replace the use of those CP/M functions with something custom, and I'd have done the 99% of the work. Of course the devil is always in the details.
Let's start. To begin with I'm lucky in that I'm using the pasmo assembler which is capable of outputting .TAP files, which can be loaded into ZX Spectrum emulators.
I'm not going to walk through all the code here, because that is available within the project repository, but here's a very brief getting-started guide which demonstrates writing some code on a Linux host, and generating a TAP file which can be loaded into your favourite emulator. As I needed similar routines I started working out how to read keyboard input, clear the screen, and output messages which is what the following sample will demonstrate..
First of all you'll need to install the dependencies, specifically the assembler and an emulator to run the thing:
# apt install pasmo spectemu-x11
Now we'll create a simple assembly-language file, to test things out - save the following as hello.z80:
; Code starts here org 32768 ; clear the screen call cls ; output some text ld de, instructions ; DE points to the text string ld bc, instructions_end-instructions ; BC contains the length call 8252 ; wait for a key ld hl,0x5c08 ; LASTK ld a,255 ld (hl),a wkey: cp (hl) ; wait for the value to change jr z, wkey ; get the key and save it ld a,(HL) push af ; clear the screen call cls ; show a second message ld de, you_pressed ld bc, you_pressed_end-you_pressed call 8252 ;; Output the ASCII character in A ld a,2 call 0x1601 pop af call 0x0010 ; loop forever. simple demo is simple endless: jr endless cls: ld a,2 call 0x1601 ; ROM_OPEN_CHANNEL call 0x0DAF ; ROM_CLS ret instructions: defb 'Please press a key to continue!' instructions_end: you_pressed: defb 'You pressed:' you_pressed_end: end 32768
Now you can assemble that into a TAP file like so:
$ pasmo --tapbas hello.z80 hello.tap
The final step is to load it in the emulator:
$ xspect -quick-load -load-immed -tap hello.tap
The reason I specifically chose that emulator was because it allows easily loading of a TAP file, without waiting for the tape to play, and without the use of any menus. (If you can tell me how to make FUSE auto-start like that, I'd love to hear!)
I wrote a small number of "CP/M emulation functions" allowing me to clear the screen, pause, prompt for input, and output text, which will work via the primitives available within the standard ZX Spectrum ROM. Then I reworked the game a little to cope with the different screen resolution (though only minimally, some of the text still breaks lines in unfortunate spots):
The end result is reasonably playable, even if it isn't quite as nice as the CP/M version (largely because of the unfortunate word-wrapping, and smaller console-area). So now my repository contains a .TAP file which can be loaded into your emulator of choice, available from the releases list.
Here's a brief teaser of what you can expect:
Outstanding bugs? Well the line-input is a bit horrid, and unfortunately this was written for CP/M accessed over a terminal - so I'd assumed a "standard" 80x25 resolution, which means that line/word-wrapping is broken in places.
That said it didn't take me too long to make the port, and it was kinda fun.
Tags: cpm, retro, spectrum, z80 4 comments
If you can afford an extra build step, you can convert the TAP file to a snapshot via the bin2sna.py utility from SkoolKit. Then you can load it instantly into Fuse.
I use this approach in my River Raid disassembly: https://github.com/morozov/river-raid (see the resulting river-raid.z80).