Nostalgia trip: QBasic game programming

Posted on 2015/01/18

10


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:

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.

PIXELPlus 256 running in DOSBox.

PIXELPlus 256 running in DOSBox.

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.

QBasic 1.1 IDE.

QBasic 1.1 IDE.

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.

QBasic game running in DOSBox.

QBasic game running in DOSBox.

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.

Posted in: Software