QB64 Programming — Loading and Saving Binary Files
Following on from my recent articles about my Commodore 64 (and others) Dungeon Delve game that I am building with TRSE. I started work on my level/tile editor using QB64, and today I wanted to share how I got the saving and loading to work.
I LOVE BASIC
There will be some of you turning their noses up right now at my use of BASIC. Well, sorry not sorry.
On my main Retro Game Coders blog, I have a Commodore 64 BASIC Programming Tutorial. In that tutorial, I start with a video where I stand up in defense of BASIC. Check it out here:
TL;DR — a lot of the complaints about BASIC are based on myths or out-of-date thinking.
Sure, don’t like it out of subjective taste, that is fine — there are lots of languages I don’t personally enjoy using — but don’t ever say it is objectively bad because it certainly is not.
Files in QB64
In a language such as Python or JavaScript, we lean on text format files quite a lot, JSON formatted data especially.
When working on retro games, it is best to prioritize file size and machine readability instead. Not least because of the small amount of RAM in these 40-year-old machines, but also a JSON file can be bigger than the capacity of the disk formats that these guys have to use.
Fortunately, QB64 has a nice binary file format where you can put a byte at a time, or more conveniently, a whole array of data.
' OUTPUT THE TILES TO FILE
SUB save_cells ()
' SAVE THE TILES
OPEN tiles_file$ FOR BINARY AS #1
PUT #1, , cells()
CLOSE
LOCATE 1, 1
PRINT "TILE DATA SAVED"
END SUB
When it comes to loading the data back, we reverse the process using GET#
, but of course we need to update the display too.
' LOAD THE CELL DATA
SUB load_cells ()
' GET THE CURRENT TILE DATA
OPEN tiles_file$ FOR BINARY AS #2
GET #2, , cells()
CLOSE #2
this_cell = 0
cell_row = 0
cell_col = 0
FOR cell_row = 0 TO 17
FOR cell_col = 0 TO 20
tmp_X = cell_col * 8
tmp_Y = cell_row * 8
CALL print_char(cells(this_cell), tmp_X + 141, tmp_Y + 47)
this_cell = this_cell + 1
NEXT
NEXT
END SUB
Drawing the Graphics
Another convenience built right into QB64 is multi-platform graphics.
QB64 works on Mac, Windows, and Linux, but can output simple graphics on any of these operating systems without resorting to switching out low-level libraries.
For each custom-defined character that is selected as either the draw graphic or erase graphic, I need to paint the bits onto the screen.
This is used on character selection (so you know which are currently in use) as well as to draw on the map canvas.
I built a subroutine that accepts the chosen character number, and the starting pixel location for the x and y coordinates.
We already covered that our character set is stored in a two-dimensional array.
As you can clearly see at this low resolution, each character is made up of 8x8 pixels, so we simply iterate over each byte that represents the row and paints a colored pixel if the bit position represents a 1, or black pixel if it represents a 0.
There is a nice GUI system for QB64 called InForm, but for now that is a lot more than I need, so I created a simple save button with the box graphic function:
' BUTTON DRAWING
SUB draw_button (label AS STRING, x AS INTEGER, y AS INTEGER)
LINE (x, y)-(x + 49, y + 8), dgrey, BF
LINE (x, y)-(x + 50, y), white, BF
LINE (x, y)-(x, y + 8), white, BF
LINE (x + 2, y + 8)-(x + 49, y + 8), dgrey, BF
_PRINTSTRING (x + 8, y + 1), label
END SUB
To detect when it is clicked I simply use the x, y coordinates for when you hit the left mouse button. If it is over the area of the screen taken up by the button, it is a click!
Next Steps
Now I have to decide if to use this tool as a screen painter, or stick to the plan of creating meta-tiles.
Or maybe a hybrid? I could go through the map as drawn, split it into 3x3 tiles, and then eliminate duplicates — essentially creating a compression system.
Hmmm …