Nostalgia trip: QBasic game programming

After I wrote my last post I couldn’t stop thinking about old days, and how I programmed in the early nineties, when I was around ten years old. I used to write small games for myself, but I spent far more time coding than ultimately playing them, because creating was the fun part, understanding what the computer could do and ordering it to do it with the right words. So here we are today: I still develop software but with different languages, with more experience and vastly more resources in terms of information. I rode the waves of nostalgia and coded a simple dude that runs according to key strokes. These are the tools I required today:

DOSbox, an open source DOS emulator.

Microsoft QBasic 1.1; it’s the interpreter that came with DOS 6.22.

PIXELPlus 256, a freeware graphic interface to draw game sprites and pixel art.

To install the programs I created a QB and a PP directory in C: and unzipped the content inside. First, I wanted to have a character (sprite) that I could then move around. PIXELPlus has some sample files, and one of them is RUNNER.PUT, which is a stick figure that runs to the right. It’s composed by four frames of 32×32 pixels, with a palette of 256 colours. I take the RUNNER.PUT file and copy it into the QB directory where I will write and run my code. The PUT file contains a file header (7 Bytes), then a frame header of 4 Bytes, containing the frame dimensions, followed by the frame data consisting of a Byte per pixel, and the Byte contains the pixel colour; the next frames follow, up to the end of file.

Then I had to write the code in QBasic. QBASIC.EXE presents itself as an editor, with split screen for help and immediate evaluation. You write a program and run it inside the editor, which is also an interpreter.

The basic operations of the game are:

Load sprite graphics file into an array in memory with BLOAD command.

Change graphical mode to 320×200, 256 colors with SCREEN command.

Draw sprite frames with PUT command, using the correct memory offset in the array.

Read keystrokes with INKEY$ and make them move the game character.

Manage frame rate using TIMER functions and wait loops.

The following is the source code of PP256.BAS:

DECLARE SUB delayframerate () DECLARE FUNCTION spriteoffset& (w AS LONG, h AS LONG, frame AS LONG) DIM spritew AS LONG 'Sprite width. DIM spriteh AS LONG 'Sprite height. DIM spriten AS LONG 'Number of frames. DIM spritesize AS LONG 'Size of array containing sprite data. spritew = 32: spriteh = 32 'Dimensions: 32x32. spriten = 4 spritesize = spriteoffset(spritew, spriteh, spriten) DIM sprite(spritesize) AS INTEGER 'Reserve memory for sprite data. DEF SEG = VARSEG(sprite(0)) 'Select memory segment containing array. BLOAD "RUNNER.PUT", sprite(0) 'Load file into array. DEF SEG 'Reset memory segment DIM x AS INTEGER DIM y AS INTEGER DIM xmax AS INTEGER DIM ymax AS INTEGER DIM xlast AS INTEGER DIM ylast AS INTEGER DIM xspeed AS INTEGER DIM yspeed AS INTEGER DIM spritei AS LONG 'Frame index. DIM spriteolast AS LONG 'Offset in array. xmax = 320 - spritew - 1 ymax = 200 - spriteh - 1 x = 100: y = 50 xspeed = 0: yspeed = 0 spritei = 2 xlast = x: ylast = y SCREEN 13 'Resolution 320x200, 256 colours spriteolast = spriteoffset(spritew, spriteh, spritei) PUT (xlast, ylast), sprite(spriteolast), XOR DO x = x + xspeed: y = y + yspeed 'Bounce on screen limits. IF x > xmax THEN x = xmax: xspeed = -xspeed IF y > ymax THEN y = ymax: yspeed = -yspeed IF x < 0 THEN x = 0: xspeed = -xspeed IF y < 0 THEN y = 0: yspeed = -yspeed 'Choose frame for walking movement. DIM spriteincr AS LONG 'spriteincr can be +1,0,-1 spriteincr = xspeed IF spriteincr = 0 THEN spriteincr = ABS(yspeed) IF spriteincr <> 0 THEN spriteincr = spriteincr \ ABS(spriteincr) spritei = (spritei + spriteincr + spriten) MOD spriten DIM spriteo AS LONG spriteo = spriteoffset(spritew, spriteh, spritei) IF x <> xlast OR y <> ylast OR spriteo <> spriteolast THEN PUT (x, y), sprite(spriteo), XOR 'Draw current sprite. PUT (xlast, ylast), sprite(spriteolast), XOR 'Delete last drawn sprite. END IF spriteolast = spriteo xlast = x: ylast = y DIM K AS STRING 'Keys: WASD for moving, ESC to exit. K = INKEY$ IF UCASE$(K) = "W" THEN yspeed = yspeed - 1 IF UCASE$(K) = "A" THEN xspeed = xspeed - 1 IF UCASE$(K) = "S" THEN yspeed = yspeed + 1 IF UCASE$(K) = "D" THEN xspeed = xspeed + 1 IF K = CHR$(27) THEN END delayframerate 'Manage framerate. LOOP SUB delayframerate STATIC lasttimer AS SINGLE 'The value is retained between calls. DIM nexttimer AS SINGLE DIM maxfps AS SINGLE maxfps = 10 nexttimer = lasttimer + 1! / maxfps DO WHILE TIMER < nexttimer LOOP lasttimer = TIMER END SUB FUNCTION spriteoffset& (w AS LONG, h AS LONG, frame AS LONG) DIM framesize AS LONG 'Size of integer array containing one frame. 'One frame is: ' 2 Bytes that contain width*bits per pixel, ' 2 Bytes that contain height, ' 1 Byte per pixel, ' 0 or 1 Bytes of padding so that size is multiple of 2. framesize = (w * h + 4 + 1) \ 2 'INTEGER is 2 Bytes. spriteoffset = framesize * frame END FUNCTION

By pressing Shift-F5 the editor runs the program into the QBasic interpreter, and it is visualized full-screen. The character runs all around the screen, bouncing at the edges and going faster and slower according to the key strokes.

Now that I can program in C, Python, C#, Java and other languages, the Basic syntax seems somewhat clunky and silly, but at the time it had its reasons to exist, partly because the resources were limited and so the interpreter had to be simple, partly because there were few languages around at the time, and so a shorter history of programming language design. It was also more difficult to collect feedback from developers without widespread Internet. Anyway it felt good to program in this way again: it was like visiting a house of your childhood and wandering the same rooms, the light through the window, the smell. The memory are rushing, one called by the other, and you see yourself as a boy again. And even if the place today looks corroded by rust and mold, it stills means something to you. I am grateful that I had these humble programming experiences in my youth because they shaped me, they taught me, and they were fun.