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