A Sinclair BASIC game for the ZX Spectrum inspired by Iron
Lung that uses
zx0 to compress and load the game screen and other creepy
images. Pasmo is used to assemble this data, and
zmakebas is used for cross-platform Sinclair BASIC
development. ZX-Basicus is part of the build pipeline to
optimise the BASIC and create the idiomatic murkydepths.zxb output. murkydepths.tap is for use
with a ZX Spectrum emulator (tested with Fuse). The .scr
image files were created with ZX Paintbrush, and
the n11 font is courtesy of @jimblimey.
Download .tap file
A quick demo
Play it now
A few notes on what I did
Talk is cheap, show me the code.
This is a Sinclair BASIC game that uses built-in trigonometry commands such as SIN and COS to
move a PLOTted dot around the screen to represent a submarine, detecting collisions with
POINT. I had the idea of loading a more complex game screen, created in ZX
Paintbrush, using LOAD ""SCREEN$, which worked
well. The problem was that any use of CLS to show instructions, for example, would wipe that
screen completely.
This led me to the idea of creating a buffer using machine code that I could quickly load in and out
using LDIR and CLEARing enough room for the screen data (6144 bytes for just the bitmap data).
This was fairly straightforward, but I had previously seen a compression tool called
zx0 and I wondered if I could use this to include more
screens to tell a bit of a story.
The inspiration for this game was the submarine horror game Iron
Lung, so I decided to create some creepy
screens that the player could discover by following coordinates. These are compressed into the top
of memory, with zx0’s standard
decompressor used to extract
them into screen memory. All the game logic is in Sinclair BASIC, but the build pipeline uses
Pasmo to assemble the compressed data and includes the
decompressor and some shortcut routines at known addresses that are called from Sinclair BASIC. I
also included a font called n11 that suited the creepy vibe, courtesy of
@jimblimey. The .asm for this is as
follows, with some lines removed for clarity.
; load map.scr.zx80 into screen memory
org 39594
ld hl, 39743
ld de, $4000
jp dzx0_standard
...
; -----------------------------------------------------------------------------
; ZX0 decoder by Einar Saukas & Urusergi
; "Standard" version (68 bytes only)
; -----------------------------------------------------------------------------
; Parameters:
; HL: source address (compressed data)
; DE: destination address (decompressing)
; -----------------------------------------------------------------------------
include "..\tools\zx0\z80\dzx0_standard.asm"
; map (1741 bytes)
org 39743
incbin "..\res\map.scr.zx0"
...
; font
org 64000
include "..\res\font.asm"
The address locations have all been carefully calculated based on the size of the compressed files,
which are extracted directly into screen memory. For example, the compressed map is 1741 bytes but
expands to fill 6912 bytes of screen memory, so the saving is pretty decent. It would be better to
generate this with a custom build script instead of recalculating everything after each change, and
I still might do that because it would be useful for other projects too. It could also generate a
header for the zmakebas .zxb file, which I have added
manually here. A snippet is shown below.
@begin:
CLEAR 39593
LOAD ""CODE
REM load font at 64000
POKE 23607,249
REM - addresses of main.asm routines that load screen data
LET map = 39594
LET monster = 39612
REM etc...
@showmonster:
LET statusimg = monster
LET statusink = 2
LET s$ = "You have awoken Cthulhu."
GO TO @showstatus
@showstatus:
BORDER 0
RANDOMIZE USR statusimg
PRINT #1; AT 1,0; PAPER 0; INK statusink; s$
BEEP 0.5,-12
PRINT #1; AT 1,31; INK statusink; FLASH 1; "\::"
PAUSE 0
POKE 23560, 0
BEEP 0.01,0
RETURN
It may seem wasteful to create variables to hold these constant values, but because I am also using ZX-Basicus in the build pipeline, any variables that are assigned once and then used like constants are replaced in the final output. A snippet of that is shown below.
10 CLEAR 39593 : LOAD "" CODE : POKE 23607 , 249
...
158 LET j = 39612
160 LET k = 2 : LET s$ = "You have awoken Cthulhu." : GOTO 182
182 BORDER 0 : RANDOMIZE USR j : PRINT # 1 ; AT 1 , 0 ; PAPER 0 ; INK k ; s$ : BEEP 0.5 , -12 : PRINT # 1 ; AT 1 , 31 ; INK k ; FLASH 1 ; "\gph(::)" : PAUSE 0 : POKE 23560 , 0 : BEEP 0.01 , 0 : RETURN