QB64 Programming - Loading and Saving Binary Files

in #programminglast year

Screen Shot 2022-01-26 at 2.54.56 PM.png

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.


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.

SUB save_cells ()

    OPEN tiles_file$ FOR BINARY AS #1
    PUT #1, , cells()

    LOCATE 1, 1


When it comes to loading the data back, we reverse the process using GET#, but of course we need to update the display too.

SUB load_cells ()
    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


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:

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

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 ...