The development environment for doing ZX81 games is the same as for the Spectrum, just that you need a ZX81 emulator to see the results on. You can still use whatever text-editor/project solution you are used to, and the same assembler – it’s just Z80 again after all. For the emulator I’d recommend using either EightyOne – which is very accurate – or No$ZX81 – which is much faster to boot, but not quite as exact. I personally use No$ZX81 for day-to-day development & testing, given it’s quick turnaround, and EightyOne for final testing.
There are two things however that we need to sort out before we start converting the game. Luckily both of these have already been solved by some intelligent people way before I came onto the scene, by the use of two ‘template’ files. I can take no credit for either of the files, but they both do their jobs very well, so I’ve very grateful to the original authors.
The first bridge to cross is the fact that the ZX81 doesn’t use ASCII, but Sinclair’s own made-up character set instead. Because of this, if you try to use ASCII strings in your game – such as to print ‘Hello World’, then it won’t show what you expect, as if you compile it as a string it’ll be an ASCII string, not a ZX81 string. So the following code, which would be fine on the Speccy, won’t work on the ZX81:
defb 'Hello World!', 0
There are two solutions to this. The first is to write a convertor to take an ASCII string and convert it to a ZX81 string, by using a macro. The second is to use a series of defines for each letter to allow you to specify the string as the ZX81 expects it, such as:
defb _H, _E, _L, _L, _O, __, _W, _O, _R, _L, _D, 0
Although this second method seems more long-winded than the first, it’s actually the one I use, because there are so few characters in ZX81 set, and so many in ASCII, that it’s easy to make a mistake, and it also allows for custom defines to be added for all the various graphics characters. Perhaps if I wrote text-heavy games like adventures I might think differently, but for most of my games I tend to keep text at a minimum.
So here is a file – template_charcode.z80 – which does just that:
; A useful reference ; ; ____0___1___2___3___4___5___6___7___8___9___A___B___C__D_E_F_ ; 00 SPC GRA GRA GRA GRA GRA GRA GRA GRA GRA GRA " GBP $ : ? 0F ; 10 ( ) > < = + - * / ; , . 0 1 2 3 1F ; 20 4 5 6 7 8 9 A B C D E F G H I J 2F ; 30 K L M N O P Q R S T U V W X Y Z 3F __ EQU $00 _QT EQU $0B _CO EQU $0E _QU EQU $0F _CL EQU $0e _OB EQU $10 _CB EQU $11 _GT EQU $12 _LT EQU $13 _ADD EQU $15 _SUB EQU $16 _ST EQU $17 _FS EQU $18 _SCL EQU $19 _CM EQU $1a _DT EQU $1b _0 EQU $1c _1 EQU $1d _2 EQU $1e _3 EQU $1f _4 EQU $20 _5 EQU $21 _6 EQU $22 _7 EQU $23 _8 EQU $24 _9 EQU $25 _A EQU $26 _B EQU $27 _C EQU $28 _D EQU $29 _E EQU $2a _F EQU $2b _G EQU $2c _H EQU $2d _I EQU $2e _J EQU $2f _K EQU $30 _L EQU $31 _M EQU $32 _N EQU $33 _O EQU $34 _P EQU $35 _Q EQU $36 _R EQU $37 _S EQU $38 _T EQU $39 _U EQU $3a _V EQU $3b _W EQU $3c _X EQU $3d _Y EQU $3e _Z EQU $3f
The other thing of note in getting setup is that the emulator image we need to produce for the ZX81 is different to that of the Spectrum. With the Spectrum we get the assembler to produce a BASIC program which loads our game code, and then run it. On the ZX81 however there’s just a single file (usually seen with a .P extension), which overwrites the entire RAM of the machine – the BASIC listing, any machine code, the display file, system setup, everything. The easiest way of getting machine code into this single file is to actually embed it within the BASIC itself. Luckily there’s a really good template file for this too – template_main.z80:
INCLUDE ".\template_charcode.z80" ; if you want the program to autorun, leave these as-is. ; if you don't, comment out the 1st line and uncomment the second. AUTORUN EQU line1 ;AUTORUN EQU dfile ; SYSVARS which aren't saved ERR_NR EQU $4000 FLAGS EQU $4001 ERR_SP EQU $4002 RAMTOP EQU $4004 MODE EQU $4006 PPC EQU $4007 ORG $4009 ; SYSVARS which are. This is the start of the .P VERSN: DEFB 0 E_PPC: DEFW 0 D_FILE: DEFW dfile DF_CC: DEFW dfile+1 VARS: DEFW vars DEST: DEFW 0 E_LINE: DEFW vars+1 CH_ADD: DEFW last-1 X_PTR: DEFW 0 STKBOT: DEFW last STKEND: DEFW last BERG: DEFB 0 MEM: DEFW MEMBOT DEFB 0 DF_SZ: DEFB 2 S_TOP: DEFW 1 LAST_K: DEFB $FF, $FF, $FF MARGIN: DEFB 55 NXTLIN: DEFW AUTORUN ; #define this in your main asm file; use 'line0', etc. 'dfile' if no autorun. OLDPPC: DEFW 0 FLAGX: DEFB 0 STRLEN: DEFW 0 T_ADDR: DEFW $0C8D SEED: DEFW 0 FRAMES: DEFW $FFFF COORDS: DEFB 0, 0 PR_CC: DEFB $B CS_POSN: DEFB 33, 24 CDFLAG: DEFB %01000000 PRTBUF: DEFS 33 MEMBOT: DEFS 30 ; calculator's scratch DEFS 2 ; some useful ROM routines RESET EQU $0000 SLOWFAST EQU $0207 SETFAST EQU $02E7 NEXTLINE EQU $0676 DECODEKEY EQU $07bd PRINTAT EQU $08f5 MAKEROOM EQU $099e CLS EQU $0a2a STACK2BC EQU $0bf5 STACK2A EQU $0c02 CLASS6 EQU $0d92 FINDINT EQU $0ea7 FAST EQU $0f23 SLOW EQU $0f2b DEBOUNCE EQU $0f4b SETMIN EQU $14BC ;= First BASIC line, asm code ================================== line0: DEFB $00, $01 ; line number DEFW line1-$-2 ; line length DEFB $ea ; REM DEFB $7e ; m/c for ld a,(hl), BASIC token for 'next 5 bytes are fp number' jp start ; this instruction and all following will be hidden. All you see is the REM... DEFB 0, 0 ; ... what a neat trick - thanks Math123! DEFB $76 ; end of line ; Program code. start: INCLUDE "main.z80" ; return to basic - resumes program execution at next line jp NEXTLINE ;= Second BASIC line, calls ASM ================================== line1: DEFB 0, 2 ; line number DEFW dfile-$-2 ; line length DEFB $f9, $d4, $c5 ; RAND USR VAL DEFB _QT,_1,_6,_5,_2,_1,_QT ; "16521" DEFB $76 ; N/L dfile: ;- Display file -------------------------------------------- DEFB $76 ; Initial N/L. REPT 24 ; 32 spaces to make an expanded line. REPT 32 DEFB __ ENDM DEFB $76 ; N/L to next line. ENDM ;- BASIC-Variables ---------------------------------------- vars: DEFB $80 ;- End of program area ---------------------------- last:
With this, you place your code in a main.z80 file and compile template_main.z80 to produce the .P image file. The code is compiled and placed into line #0 of the BASIC listing, and when the .P file is loaded it automatically starts running the BASIC from line #1 – which immediately runs our code from line #0. You’ll see that file also has some defines for useful ROM routines, should you need them.
You’ll notice at the end of that main template a section labelled ‘Display file‘. This is the ZX81’s screen memory, and the template initialises it as 24 rows of 32 columns of spaces – so an blank screen. This might seem strange at first – the Spectrum automatically has this – but it’s actually a crafty memory-saving trick. The original ZX81 had 1K – 1024 bytes – of memory, and a screen is 24 * 32 characters, which consumes 768 bytes – and so doesn’t leave much room for anything else at all! (Just try writing a BASIC program to fill every character on the screen on a 1K machine and it’ll run out of memory before it gets to the bottom). So the trick used is to give each row a ‘newline’ character – $76 – at the end, and if you don’t want all 32 characters in a row you simply only have as many as you need before that newline. So a blank screen can actually be stored only 24 bytes – 24 newline characters. As we are working towards a game it’s best to focus on a 16K minimum, and so be able to use a full-size screen, which the template code allocates and has setup ready for use.
Another trick of having this display file in the code is that we can put whatever we want it in – think of it like an instant loading screen! Okay, so you won’t actually be able to see it until the game has loaded, but if the first thing your game does is wait then it’ll be immediately visible for all to see. (I did this in a few game, Boulder Logic for example)
And that’s all the setup we have to do. Whatever we now put in our main.z80 file is down to us and what we want our game to do.
Now where did I put that BASIC manual…?
P.S. This is a WordPress blog, and it appears to have Z80 gremlins which keep removing the underscore ‘_’ character from random points in my listings. Shout if you see them!