Okay, the uncompressed, huge image data started bugging me, so let's compress it. I fetched Einar Saukas' excellent zx0 (github) decompressor and the windows compressor, compressed the image, included the decompressor source, replaced the raw data with the compressed one.
Compressed Image
keyselect_scr:
INCBIN "keyselect.scr.zx0"
INCLUDE "dzx0_standard.asm"
..and replaced the image copy ldir with a call to the decompressor:
ld de, SCREEN
ld hl, keyselect_scr
call dzx0_standard
Result: 3868 bytes, or 5329 bytes saved.
I also desaturated the image so it will compress a bit better. Whether the resulting image looks better or worse is a matter of taste.
New Assets
We need those minitiles for the minimap, but before doing that, I wanted a few new tiles. We know that the falling rock will be its own tile, so it might as well have a separate image too. When the rock hits the player the player goes splat, let's have a tile for that. I also want a wandering monster type, so four tiles go to the purple people eater, and finally let's have water and water source. Like so:
Again, the tiles are laid out top down to make them easier to parse.
With all the tiles done, I shrunk them with point sampling down to 8x8 and did some fixing by hand. The mini-tiles don't need to look too great, as they are just for preview.
The artwork done, I again converted these to binary with using my "png2bin" tool from my speccy github tool, and designed the colors straight in the source file.
tiles:
BLOCK 32,0 ; empty tile
INCBIN "tiles.dat"
; 0x00 0x00 = black 0x04 0x20 = green
; 0x01 0x08 = blue 0x05 0x28 = cyan
; 0x02 0x10 = red 0x06 0x30 = yellow
; 0x03 0x18 = magenta 0x07 0x38 = white
; 0x40 = bright 0x80 = blink
tilecolors:
db 0x00, 0x00, 0x00, 0x00 ; 0 empty
db 0x05, 0x05, 0x04, 0x04 ; 1 player
db 0x10, 0x10, 0x10, 0x10 ; 2 ground
db 0x57, 0x57, 0x57, 0x57 ; 3 bricks
db 0x44, 0x44, 0x44, 0x44 ; 4 goo
db 0x45, 0x05, 0x05, 0x45 ; 5 gem
db 0x07, 0x07, 0x07, 0x07 ; 6 stone
db 0x04, 0x04, 0x04, 0x04 ; 7 exit (closed)
db 0x44, 0x44, 0x44, 0x44 ; 8 exit (open)
db 0x07, 0x07, 0x07, 0x07 ; 9 falling stone
db 0x07, 0x07, 0x02, 0x02 ; 10 splat stone
db 0x03, 0x03, 0x03, 0x03 ; 11 people eater up
db 0x03, 0x03, 0x03, 0x03 ; 12 people eater right
db 0x03, 0x03, 0x03, 0x03 ; 13 people eater down
db 0x03, 0x03, 0x03, 0x03 ; 14 people eater left
db 0x0d, 0x0d, 0x0d, 0x0d ; 15 water
db 0x07, 0x07, 0x0d, 0x0d ; 16 water source
minitiles:
BLOCK 8,0 ; empty tile
INCBIN "minitiles.dat"
minitilecolors:
db 0x00 ; 0 empty
db 0x05 ; 1 player
db 0x10 ; 2 ground
db 0x57 ; 3 bricks
db 0x44 ; 4 goo
db 0x45 ; 5 gem
db 0x07 ; 6 stone
db 0x04 ; 7 exit (closed)
db 0x44 ; 8 exit (open)
db 0x07 ; 9 falling stone
db 0x07 ; 10 splat stone
db 0x03 ; 11 people eater up
db 0x03 ; 12 people eater right
db 0x03 ; 13 people eater down
db 0x03 ; 14 people eater left
db 0x0d ; 15 water
db 0x07 ; 16 water source
Since the minitile colors are always the first ones from the tile colors, I could have saved a few bytes by just keep using those, but maybe it's cleaner this way.
Drawminitile got a few small changes to use the new minitile data; the changes are so trivial that I don't think it makes much sense to copy all of it here.
; One tile is 8 bytes
add hl, hl ; *2
add hl, hl ; *4
add hl, hl ; *8
ld de, minitiles
add hl, de
ld de, hl ; hl now is pointing at the start of tile x
Since one tile is 8 bytes, *8 is now enough.
; Instead of looping, we'll plot each pixel separately..
DUP 7
ld a, (de) ; Read pixels from data
ld (hl), a ; Write to screen
inc de ; Increment de and hl..
inc h
EDUP
ld a, (de) ; Read pixels from data
ld (hl), a ; Write to screen
One INC DE is gone from here, since we don't need to skip data anymore.
pop hl ; tile index
ld de, minitilecolors
add hl, de ; hl now points at tile color
Again, simplification: *4 is gone.
General Physics Fixes
Since we're going to have different kinds of physics interactions than just falling, we'll need to scan the whole map, which means we need to check that we're not falling through the bottom of the map.
The changes to scan the whole map are trivial:
physofs:
db 0, 176
jr nz, physics_notlooped
ld hl, 176
physics_notlooped:
Stopping items from falling below the map requires a new check at the start of physics_drop.
physics_drop:
ld a, ixl
cp 16*11+map
jr nc, physicsdone
Since IX points at the map data and is not just the tile index, we also need to take the map address into account. Which is bad. After all, the map address is 16 bit. Let's move the map data to a 256 byte offset so we don't need to deal with this - after this change the bottom byte of the address and tile index is the same thing. (This is one of those things that would be really complicated in C, but trivial in assembly).
We'll remove the +map from above, and move the map definition to be the very last thing in the file, with ORG to say what address it should start from.
ORG $fd00
map:
BLOCK 16*12,0
Careful now. The interrupt jump table is at 0xfe00 - 0xff01 and the table points at 0xfdfd, meaning we should really leave the memory area 0xfdfd-0xff01 alone. Our map will now be at 0xfd00-0xfdc0, so it's fine. There's also plenty of loose bytes in that region, but unless we're really hurting for space, we should keep everything under 0xfd00 now.
While we're dealing with things like this, let's stop player from pushing rocks outside the screen. The movestone handling gets a new check at the start.
movestone:
ld a, l
and a, 0xf
jr z, movedone ; left border; can't be pushed
cp 15
jr z, movedone ; right border; can't be pushed
Since the player can't be between the border and a rock that's already at a border, the pushing is impossible.
All that out of the way, we can start adding more physics stuff. Let's start with the falling rock.
Making Player go Splat
Making the player go splat requires a few changes. We need to change the stone's state when it's moving, add handler for falling rock, and add a flag that says the player's inputs are no longer desired.
cp 0
jr nz, physicsdone
ld a, (ix)
cp 6 ; stone
jr nz, notstone
ld a, 9 ; falling stone
notstone:
or 0x80
In the physics_drop after we see that an item is about to fall downwards we need to check if it's a stone or not, and if it is, we replace it with a falling stone. Running the game after this change is a bit boring as the falling stone doesn't do anything. The sprite changes, though.
cp 5
jr z, physics_drop
cp 6
jr z, physics_drop
cp 9
jr z, physics_stone
In the physics handler we'll check for the moving stones and jump to a new handler, physics_stone.
physics_stone:
ld a, ixl
cp 16*11
jr nc, stone_stop
ld a, (ix+16)
cp 0
jr z, stone_fall
stone_stop:
ld (ix), 0x86 ; inert stone
jp physicsdone
stone_fall:
ld (ix), 0x80
ld (ix+16), 0x89
jp physicsdone
The handler is pretty simple; we check if we're at the bottom and go back to inert stone if we can't, then check if there's empty space below us and if there is, move there, otherwise go inert. Testing this we see the sprite change from inert to moving back to inert like we wanted.
One thing to note is that we're using JP to physicsdone here, because our function has grown so large that JR can't reach it anymore. The assembler will complain in these cases, and the fix is trivial. I'll be doing a lot of these changes as the program grows, and won't be mentioning about it every time.
Next we'll deal with the game state. After adding a single byte variable playerdone, we need to clear it at the start of levelselect or the player won't be able to keep playing:
levelselect:
ld a, 0
ld (movekey), a
ld (playerdone), a
To stop player from moving (but not from hitting space), the moveplayer function's start changes to:
moveplayer:
ld a, (movekey)
bit 4, a
jp nz, levelselect
ld a, (playerdone)
cp 1
ret z
What's left is changing the moving stone's behaviour to accept moving on top of the player.
cp 0
jr z, stone_fall
cp 1
jr z, stone_splat
Simply check if the falling stone's target is 1 (player)...
stone_splat:
ld (ix), 0x80
ld (ix+16), 0x8a ; splat
ld a, 1
ld (playerdone), a
jp physicsdone
...move the stone there (as the splat sprite), mark player as done, and that's it. The player has been splatted.
This chapter's version of the source as well as the new assets and source of the decompressor is available here.
Size is at 4385 bytes, which tells me that sjasmplus does not include zero bytes in the raw output (as the size should be closer to 32k if it was). The good news there is that we can keep using the .raw file for size estimates; the bad side is that we can't be sure if the zeroed data is actually zeroed. Doesn't matter in our case, but something to keep in mind. Since our optimized size at the start was 3868 bytes, we increased by 517 bytes, mostly from the new tile graphics.
Any comments etc. can be emailed to me.