Blitz2D Newbies: A Beginner's Guide to Low-Level File Functions in Blitz Basic/Blitz3D
by Snarkbait

I have seen a lot of confusion and questions on the forums about writing and reading files, for level data, text files and suchlike, so I feel it would helpful for newbies/beginning programmers to have a tutorial about file access functions. Blitz really has an excellent set of file functions, and I will attempt to cover most of them.

Introduction:

First, a basic introduction to files in general. Information in a file on your computer is stored in Bytes. A byte is a value from 0 - 255, also represented by 8 Bits. A bit is either a 1 or a 0. Bytes are most easily represented in the Hexadecimal format, which uses base-16: the numbers 0 - 9, then the letters A - F. Each hex digit can be a value from 0 - 15, so 2 hex digits are a value from 0 - 255. For example, the value 255 is 'FF' in hex, or '11111111' in binary. You can pull up the Windows calculator and set it into 'Scientific Mode' to see this in action: Put in a decimal value, then select 'Hex', then select 'Binary' and you will see it convert.

Text files vs. Binary:

A text (.txt) file that can be read by Notepad or a similar program consists of the following: Text data, where each byte is the ASCII value corresponding to the text character, followed by carriage-return codes and linefeed codes ( chr$(13) and chr$(10), or Hex $0D and $0A). Many other file types are actually text files, including blitz basic program (.bb) files.

Text files are good to use if:

  1. You want the ability to easily edit them in Notepad or the Blitz IDE.
  2. You are not worried about hiding your data from prying eyes.
  3. You are creating them to be used as a text file for some other purpose. (i.e. an INI file)

A binary file can be almost anything, really, but the contents may or may not be readable in a text file viewer, you would need to use a hex editor to decipher the contents, or just read/write the data through blitz and not worry about what the contents look like. You can name the extension anything you like (i.e. .dat).

Binary files are good to use if:

  1. You don't want people to easily see what the contents are.
  2. You are only going to manipulate the data in the file with a program.
  3. You want to keep your files small, with no extraneous information.

For this tutorial, we will create a simple, "Hello World" text file, then a simple map file.

Opening/Creating files for reading and writing

Before you can start to read or write from a file, you need to open it, and sometimes create it. There are three main commands for this, each for a different purpose.

Command Action Conditions
ReadFile(filename$) Opens an existing file for Read operations only. File must exist.
OpenFile(filename$) Opens an existing file for Read and Write operations. File must exist.
WriteFile(filename$) Creates a new, blank file for Write operations only. If file already exists, this command will overwrite it.

Handles and Return values

It is important to understand how to use a Handle to use this command properly. A handle is a variable that will be used to reference the file without having to use the filename. The syntax for using the above commands is as follows:


So, to make our "Hello World" file, we would begin with:


This will create a new, blank file in the current directory, with the handle 'myfile'. Now, anytime we reference the file we can just say 'myfile' instead of "Hello.txt".

All three of these commands will return a zero if it was unable to complete the command. For example, if you had used ReadFile() and the file did not exist, the handle will have a value of zero. This is handy to test for incorrect or missing files. After the command, you can include the following code:


This will cause the program to bail out if there was a problem. You do not have to do this, but it is follows good programming practices.

O.k., now we have a file open and ready for the next step…

Reading and Writing Commands

There are quite a few commands for reading and writing to a file, and it helps to use the right one for the job. Here is a chart:

Command Action Usage/Example
WriteByte handle,value* Writes 1 byte to file, with a value from 0 - 255 Writebyte myfile,32
Binary: You can use for any data you know will not exceed 256.
Text: You can write ASCII characters directly to a text file without a linefeed/carriage return.
ReadByte(filename$) Reads 1 byte from file x = ReadByte(myfile)
WriteShort handle,value Writes a 2-byte integer, with a value from 0 -65535 WriteShort myfile,2000
Binary: When you need more than 256 but still want to save space.
Text: No.
ReadShort(filename$) Reads a 2 byte integer x = ReadShort(myfile)
WriteInt handle,value Writes a 4-byte integer, with a value from 0 -2147483648 to 2147483647 WriteInt myfile,783455
Binary: Large integer values.
Text: No.
ReadInt(filename$) Reads a 4 byte integer x = ReadInt(myfile)
WriteFloat handle,value# Writes a 4-byte floating point value, -32768.123 to 32767.123 WriteInt myfile,25.34
Binary: When you need floats - i.e. Blitz 3d
ReadFloat(filename$) Reads a 4 byte float x# = ReadFloat(myfile)
WriteString handle,value$ Writes a variable length string to a file. Stores the length of the string as a 4-byte integer, then the string itself WriteString myfile,"Hello"
Binary: Good for writing string data to a binary file.
Text: Not good for text files.
ReadString(filename$) Reads a variable length string x$ = ReadString(myfile)
WriteLine handle,value$ Writes a string to file, followed by an end-of-line mark (carriage-return & Linefeed) WriteLine myfile,"Hello"
Binary: Not very useful.
Text: Best for writing text files.
ReadLine(filename$) Reads string from file until it finds an end-of-line character x$ = ReadLine(myfile)
Binary: No
Text: Yes

*A note on syntax: all of the READ commands require the parentheses. The WRITE commands do not, although you can use them if you want. 'WriteByte myfile,10' is acceptable, and so is 'WriteByte(myfile,10)'.

There is also ReadBytes and WriteBytes for passing data from files to banks, but that is out of the scope of this tutorial.

Remember that you can use any combination of the above commands, so long as you read them back in the same order that you wrote them.

File Position and EOF

Files in blitz are read sequentially, from beginning to end. Blitz automatically keeps track of where you are in the file, so if you read data out in the same order you put it in, you don't need to do anything to track the file position. However, if you need to, the file position is there for you handily named FilePos(). You can use the SeekFile() command to move that position around if you need to. If the file position is at the end of the file, the EOF() (end-of-file) function will return True. So, when you first open/create the file, the filepos() will be automatically at '0'. If you do a 'writebyte' function, the position will move one byte, 'writeint' it will move 4 bytes, etc.

Example:


Back to our tutorial example:

So far we have this:


So now we can write to the file. We'll start with WriteLine()


Voila! That was easy, huh? Now let's do it one character at a time:


O.k., now let's read the file:


That's all there is to it.

Now, a simple mapfile save/load routine: (See Krylar's tutorial for more on map files. This is just to see an example of writing/reading different variable types to file.)

We have a 30x30 grid of tiles, in an array that simply keeps track of the Frame number for the given tile. We will store the map name, description, width and height and then the array into a file.

Assuming you have an array map(x,y):


Advanced Topics

Hex editing

If you find yourself doing a lot of file operations, it is a good idea to get a Hex Editor program - a program that lets you look 'under the hood' of your files, to see what the file looks like to the computer. I use XVI32, found here: http://www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm.

It's free, and full featured.

Here's what our "Hello.txt" file looks like in the hex editor:

On the left is the hex representation of the data in the file, on the right is the text.

Note the first byte, '48' in hex, which is '72' in decimal, is the ASCII code for an upper-case H. Also note the space is hex '20' which is '32' in decimal. At the end is '0D' and '0A', which is 13 and 10, carriage return and line feed codes. That shows that this is readable as a text file.

Byte order (significance)

If we enter the following code:


And then look at the file in the hex editor we see:

You might not understand how the above is equal to '2000'. This is because the integer data stored on disk is stored Least Significant Byte first. If you use the windows calculator and convert '2000' to hex, you see it is $7D0. But, on a PC it is stored backwards, byte-by-byte, so you have $D0 (dec 208) + (7 * 256) which is 2000. The remaining 2 bytes are not needed for this value, but WriteInt will use 4 bytes regardless. In this case we could have used WriteShort to write the value with 2 bytes.

Overview

Now we should know:

  1. How to create/open/close files for reading and writing.
  2. How to write and read different file types to a file.
  3. How the different file types are stored on disk.
  4. The differences between the file commands.

Any questions (or if you spot any mistakes!), feel free to email me at timsanford@pacificadhesives.com.

Thanks to Krylar for posting this tutorial, and for providing an excellent web resource for the excellent Blitz Basic/Blitz3D programs.


For a printable copy of this article, please click HERE.


This site is Copyright© 2000-2004, BlitzCoder. All rights reserved.