This is an implementation of the VIC-20 game Blitz in Sinclair BASIC using a modern toolchain. Some bits are faithful and some bits are different depending on what I could use or figure out, or wanted to try in the time. .tap file for use with a ZX Spectrum emulator (tested with Fuse).
Talk is cheap, show me the code.
I used a custom font (thanks @jimblimey), pasting the defb values into a Z80 .asm file and assembling with pasmo to create a .tap file. Deciding I only needed upper case text meant I had 26 lower case characters to provide graphics while still being able to use the nice and easy BASIC PRINT command.
You could use some sort of tool to design your UDGs graphically but I find I can visualise them well enough if they’re defined using binary in the .asm file e.g.
defb %11111110 ; w - building body type 1
defb %11010110
defb %11010110
defb %11111110
defb %11111110
defb %11010110
defb %11010110
defb %11111110
Telling pasmo where in memory to assemble by adding this to the top of the file…
org 64000
…means you can load the font from BASIC with the following…
CLEAR 63999
LOAD "" CODE
REM load font at 64000
POKE 23607,249
The BASIC .zxb file was also converted to a .tap file using zmakebas, which gives you a few quality of life improvements such as labels instead of line numbers. One nice thing about .tap files is that you can just append your Z80 .tap file to your BASIC .tap file, which loads it using LOAD "" CODE and the resulting .tap file just works. The .bat build file is therefore pretty straight forward…
.\tools\pasmo.exe --tap main.asm code.tap
.\tools\zmakebas -l -a @begin -o basic.tap main.zxb
type basic.tap code.tap > eigenblitz.tap
del basic.tap
del code.tap
This build pipeline could also be used to include machine code routines which can be called from BASIC so long as we know the org location. Or even a game fully implemented in Z80 with the “BASIC loader” (which is what I believe it’s usually called) loading the code then kicking it off. The BASIC loader is also where you could add a loading screen. The rest of the BASIC file in our case is the game implementation.
Blitz is a simple game. The player’s plane moves from left to right on a screen filled with buildings, moving a row down each time it reaches the far side. Bombs can be dropped that fall at the same speed the plane flies, that destroy up to 5 storeys, and only 1 bomb can be dropped at a time. If the player collides with a building they crash and the game restarts. If the player destroys all buildings then they can land and the next level is loaded.
The flowchart below shows the control flow and this is what was implemented in Sinclair BASIC (code).
%%{init: {
"themeVariables": {
"fontSize": "11px"
}
}}%%
flowchart TD
mainloop("@mainloop:")
gosub_startscreen("GO SUB @startscreen")
gameloop("@gameloop:")
gosub_flyoff("GO SUB @flyoff")
load_citydata("READ @citydata + level")
print_city("PRINT each building adding game*2 height")
for_row_column("FOR row : FOR column")
print_plane("PRINT plane at row, column")
q_bomb_power{{"IF bomb power > 0"}}
print_bomb("PRINT bomb at bomb row, bomb column")
q_plane_collision{{"IF plane collision"}}
gosub_planecrash("GO SUB @planecrash")
q_bomb_collision{{"IF bomb collision"}}
update_bomb_power("LET bomb power = bomb power - 1 : LET score = score + 1")
update_bomb_row("LET bomb row = bomb row + 1")
q_bomb_ground{{IF bomb row = max OR bomb power = 1}}
remove_bomb("LET bomb power = 0")
q_player_fire{{"IF player fire"}}
player_fire("LET bomb row = row + 1, bomb column = column, bomb power = 5")
next_column_row("NEXT column : NEXT row")
gosub_manwaving("GO SUB @manwaving")
level_up("LET level = level + 1")
q_max_level{{"IF level = max"}}
game_up("LET level = 1 LET game = game + 1")
mainloop --> gosub_startscreen
gosub_startscreen --> gameloop
gameloop --> gosub_flyoff
gosub_flyoff --> load_citydata
load_citydata --> print_city
print_city --> for_row_column
for_row_column --> print_plane
print_plane --> q_plane_collision
q_bomb_power -- yes --> print_bomb
print_bomb --> q_bomb_collision
q_bomb_power -- no --> q_player_fire
q_plane_collision -- yes --> gosub_planecrash
gosub_planecrash --> mainloop
q_plane_collision -- no -->
q_bomb_power
q_bomb_collision -- yes --> update_bomb_power
update_bomb_power --> update_bomb_row
update_bomb_row --> q_bomb_ground
q_bomb_collision -- no --> update_bomb_row
q_bomb_ground -- yes --> remove_bomb
q_bomb_ground -- no --> next_column_row
q_player_fire -- yes --> player_fire
q_player_fire -- no --> next_column_row
player_fire --> next_column_row
remove_bomb --> q_player_fire
next_column_row --> for_row_column
next_column_row --> gosub_manwaving
gosub_manwaving --> level_up
level_up --> q_max_level
q_max_level -- yes --> game_up
q_max_level -- no --> gameloop
game_up --> gameloop
It probably took as long to create the flowchart, which was partly an exercise in learning mermaidjs, as it did to write the Sinclair BASIC so that might be more useful for things like build pipelines. This whole game was a bit of a learning exercise in fact, but having an end product i.e the VIC-20 game gave me something to work towards and I was able to figure things out until I was happy enough that they did the job. It also meant I ended up with a game that was quite playable as this had already been thought out, where in the past I have just made things up as I’ve gone along and the end product was a bit all over the place. There’s still plenty of room for creativity and even creating the build pipeline was really satisfying and fun even. So I quite like this way of working and will try to have a clear idea of what I’m trying to achieve for future projects.