R.T.Russell Home Page

Chapter 19 - Games and SOUND




Now we've covered user-defined graphics, the next logical step would be to combine them and make a little game. After all, what's the point in aliens if you can't blast them to bits? Actually, my effort is a little more friendly: you are in charge of an ore collection vessel flying through an asteroid belt in deepest space (i.e. a black background!). You have to pick up the meteors but avoid the aliens. We'll develop this in steps and introduce some new techniques as we go. First we need some characters to play with. Here are mine. We can define them then wrap them in a string with the chosen colour.
REM Meteor run
MODE 6
VDU 23,220,129,219,255,255,126,60,60,24
VDU 23,221,24,60,118,221,251,110,60,24
VDU 23,222,153,189,219,126,36,60,36,36
REM Build characters
Ship$=CHR$(17)+CHR$(3)+CHR$(220)
Rock$=CHR$(17)+CHR$(4)+CHR$(221)
Alien$=CHR$(17)+CHR$(2)+CHR$(222)
 
REM Print to see how they look
PRINT Ship$
PRINT Rock$
PRINT Alien$
 
END
Once we've printed the characters, we need a way to make them move up the screen. Fortunately this is easy. By printing on the bottom line, we can cause the whole screen to scroll up, which is a cheap and cheerful way of achieving animation. On each loop we print a meteor and an alien in a random column, by printing a semicolon after the alien we suppress the line feed. Press ESC to finish.
REM Meteor run
MODE 6
VDU 23,220,129,219,255,255,126,60,60,24
VDU 23,221,24,60,118,221,251,110,60,24
VDU 23,222,153,189,219,126,36,60,36,36
REM Build characters
Ship$=CHR$(17)+CHR$(3)+CHR$(220)
Rock$=CHR$(17)+CHR$(4)+CHR$(221)
Alien$=CHR$(17)+CHR$(2)+CHR$(222)
 
 
REPEAT
  WAIT 15
  REM Print at bottom of screen
  PRINT TAB(RND(40),24);Alien$;
  PRINT TAB(RND(40),24);Rock$
UNTIL FALSE
 
END
We need to add the ship. As the screen is scrolling up it must be reprinted each time on the top line. We can use the cursor keys to move it left and right but need a variable to record the position. Let's switch the cursor off too so it doesn't get in the way.
REM Meteor run
MODE 6
VDU 23,220,129,219,255,255,126,60,60,24
VDU 23,221,24,60,118,221,251,110,60,24
VDU 23,222,153,189,219,126,36,60,36,36
REM Build characters
Ship$=CHR$(17)+CHR$(3)+CHR$(220)
Rock$=CHR$(17)+CHR$(4)+CHR$(221)
Alien$=CHR$(17)+CHR$(2)+CHR$(222)
 
X%=20
OFF
REPEAT
  WAIT 15
  REM Print at bottom of screen
  PRINT TAB(RND(40),24);Alien$;
  PRINT TAB(RND(40),24);Rock$
  REM Reprint ship
  PRINT TAB(X%,0);Ship$
  REM Detect ship movement
  IF INKEY(-26) AND X%>0 X%-=1
  IF INKEY(-122) AND X%<39 X%+=1
UNTIL FALSE
ON
END
Getting there, but there's no indication of when we hit a rock or a baddie. We need to be able to detect the character directly in front of our ship so we can award points or subtract a life as the case may be. When a new object is created, we could remember the co-ordinates and update them on each loop, but that seems like a lot of work just to test that one little square in front of the spaceship. Fortunately, BBC BASIC is capable of reading the character back from a given screen location. Even better, you already know the keyword involved: GET. When supplied with a pair of co-ordinates, GET will return the ASCII code of the character at the given location. This enables us to make our game truly interactive. First we read the screen then test the character returned to see if it is an alien or a meteor. As well as making the game pause for a short while so the player knows something has happened, we can award points and remove lives as the occasion demands. Our program now looks like this:
REM Meteor run
MODE 6
VDU 23,220,129,219,255,255,126,60,60,24
VDU 23,221,24,60,118,221,251,110,60,24
VDU 23,222,153,189,219,126,36,60,36,36
REM Build characters
Ship$=CHR$(17)+CHR$(3)+CHR$(220)
Rock$=CHR$(17)+CHR$(4)+CHR$(221)
Alien$=CHR$(17)+CHR$(2)+CHR$(222)
 
X%=20
Score%=0
Lives%=3
OFF
REPEAT
  WAIT 15
  REM Print at bottom of screen
  PRINT TAB(RND(40),24);Alien$;
  PRINT TAB(RND(40),24);Rock$
  REM Reprint ship
  PRINT TAB(X%,0);Ship$
  REM Read character in front of player
  Ch%=GET(X%,1)
  IF Ch%=222 THEN
    Lives%-=1
    WAIT 20
  ENDIF
  IF Ch%=221 THEN
    Score%+=10
    WAIT 20
  ENDIF
  REM Detect ship movement
  IF INKEY(-26) AND X%>0 X%-=1
  IF INKEY(-122) AND X%<39 X%+=1
UNTIL Lives%=0
CLS
COLOUR 7
PRINT TAB(13,12);"You scored ";Score%
ON
END
There's not much more to say about GET when used like this. Keep the co-ordinates in the range of the MODE you are using, remembering that the top left corner is 0, 0 and all will be well.

REFRESHing the screen

Depending on the speed of your computer, you may notice that the scroll / update cycle in our game causes an irritating flicking of the characters, especially the player's ship. This flicker can be viewed as a minor distraction or a major annoyance, depending on how generous you feel. BB4W has a rather neat way of eliminating it. When we write to the screen normally, BASIC and Windows take care of actually displaying the pixels we want. This is well and good most of the time but there are occasions when we require a little more control. BB4W has a set of commands that relate to the operating system in the same way that the VDU commands affect the display on the screen. All these commands start with an asterisk (*) and if you look under 'Operating System Interface' in help, you'll see them listed there. They are generally referred to as star commands for reasons that I'll let you guess. We'll concentrate on *REFRESH. Using *REFRESH we can actually tell BASIC when we want to update the screen. First we can draw all our characters and, when finished, say 'Right here's the complete screen, display it now'. To do this we call *REFRESH OFF which suppresses the automatic update and then call *REFRESH when we want to show our finished screen. Here's a simple example:

REM Using *REFRESH
*REFRESH OFF
REM Write some text
PRINT "Hello, world"
REM Wait for key press
Dummy$=GET$
REM Display screen
*REFRESH
 
*REFRESH ON
END
The star commands are not recognised as keywords so the syntax colouring doesn't pick them up. Although the text is printed to the screen, it is not actually displayed until the *REFRESH command is reached. The *REFRESH ON just restores the default behaviour when we no longer require the facility. You only need to call *REFRESH OFF once. After that call *REFRESH each time you need to update the screen.
REM Using *REFRESH
*REFRESH OFF
REM Write some text
PRINT "Hello, world"
REM Wait for key press
Dummy$=GET$
REM Display screen
*REFRESH
 
CLS
Dummy$=GET$
*REFRESH
 
PRINT "Back again"
Dummy$=GET$
*REFRESH
 
*REFRESH ON
END

Applying all this to our little game, I'm sure you can see that we need to do the scroll and redraw of the ship before the showing the screen again. Add three lines so the result looks like this:

REM ...
OFF
*REFRESH OFF
REPEAT
  WAIT 15
  REM Print at bottom of screen
  PRINT TAB(RND(40),24);Alien$;
  PRINT TAB(RND(40),24);Rock$
  REM Reprint ship
  PRINT TAB(X%,0);Ship$
  *REFRESH
  REM rest of code ...
UNTIL Lives%=0
*REFRESH ON
CLS
COLOUR 7
REM ...
Try these lines in the game and notice that the whole affair is much easier on the eye. Don't forget the final *REFRESH ON after the main loop or you won't get to see your score. When refresh is disabled, the display sometimes leaves ghost cursors scattered around the screen. If this is important to your application, disable the cursor using OFF.

Introducing SOUND

I wasn't aiming for Half-Life here and what we've got isn't bad for a few lines of code but our little game still lacks something. One way we can instantly upgrade our epic is by adding some sound. The obvious places would be a small explosion when we hit an alien and perhaps a little tune when we pick up a meteor. BBC BASIC is a pretty unique programming language in that it has a command that will allow us to make simple noises without having to distribute WAV files that are 50 times larger than our simple program. Usually languages allow you to make the speaker go bleep. BB4W allows you to access the soundcard straight from native commands. Entire books have been written on SOUND and its best friend ENVELOPE, but we'll just give the aerial view here. If you want more, the best way is to experiment and read the help files - well worth it.

Right, let's make a simple explosion - just a burst of white noise will do. Go into immediate mode and type:

SOUND 0,-15,4,20
SOUND takes four parameters. In order they are:

Channel values 0 to 3. 0 is for noises, as above, 1 to 3 allow musical notes.
Volume values -15 (loudest) to 0 (silent)
Pitch on channel 0 this is the type of noise - values are 0 to 7 on channels 1 to 3 this is the pitch of the note - value 0 to 255
Duration values -1 to 254, the length of time to play the sound in twentieths of a second. -1 plays forever.

When using channel 0, the third parameter alters the noise produced. 0 to 3 give buzzing noises and 4 to 7 give white noise or hiss. Here are some examples to start you off:

SOUND 0,-5,4,20
SOUND 0,-15,0,100
SOUND 0,-10,3,-1
There's no actual difference between the white noise generated with pitch set from 4 to 7.

To play a tune, we use channels 1 to 3. The channels are the same in terms of capacity to play notes, they all have the same range. There are three so you can play chords and counter-melodies if you wish. As the second parameter (volume) and the fourth (duration) retain the same function, we only need to concentrate on the pitch. Values range from 0 to 255, but with 0 you don't get a sound, so realistically the lowest pitch is 1 up to the highest of 255. Try:

SOUND 1,-15,1,20
SOUND 1,-15,255,20
Increasing the pitch by one raises the pitch by one quarter of a semitone, if you understand such things. Put another way, the difference between two consecutive frets on a guitar or two consecutive notes on a keyboard is equal to four increments of pitch in the SOUND statement. There is a complete list of the values for all the available notes in the help files under SOUND and if you're writing tunes, you'll need to get a feel for these values. For our part, we just want a little three-note sequence like this:
SOUND 1,-15,200,2
SOUND 1,-15,208,2
SOUND 1,-15,212,2

Put the noise in the alien collision, the tune in the meteor section and here, at last, is our complete game.

REM Meteor run
MODE 6
VDU 23,220,129,219,255,255,126,60,60,24
VDU 23,221,24,60,118,221,251,110,60,24
VDU 23,222,153,189,219,126,36,60,36,36
REM Build characters
Ship$=CHR$(17)+CHR$(3)+CHR$(220)
Rock$=CHR$(17)+CHR$(4)+CHR$(221)
Alien$=CHR$(17)+CHR$(2)+CHR$(222)
 
X%=20
Score%=0
Lives%=3
OFF
*REFRESH OFF
REPEAT
  WAIT 15
  REM Print at bottom of screen
  PRINT TAB(RND(40),24);Alien$;
  PRINT TAB(RND(40),24);Rock$
  REM Reprint ship
  PRINT TAB(X%,0);Ship$
  *REFRESH
  REM Read character in front of player
  Ch%=GET(X%,1)
  IF Ch%=222 THEN
    SOUND 0,-15,4,10
    Lives%-=1
    WAIT 20
  ENDIF
  IF Ch%=221 THEN
    SOUND 1,-15,200,2
    SOUND 1,-15,208,2
    SOUND 1,-15,212,2
    Score%+=10
    WAIT 20
  ENDIF
  REM Detect ship movement
  IF INKEY(-26) AND X%>0 X%-=1
  IF INKEY(-122) AND X%<39 X%+=1
UNTIL Lives%=0
*REFRESH ON
CLS
COLOUR 1
PRINT TAB(13,12);"You scored ";Score%
ON
END
Notice that with the SOUND statements we still have to put a WAIT in there. BASIC doesn't halt the execution of the code until the duration of the sound has expired. Instead it places the instructions in a queue and continues on its way. The sound queue is then processed at its own rate.

Most of what you can do with SOUND involves experimentation and imagination. If you're interested investigate the entries in help. They tell you how to synchronise the sounds on different channels so you can play chords and how to alter the 'shape' of the sounds using ENVELOPE. Here are a couple of effects to whet your appetite:

REM Siren
 
Inc%=1
Pitch%=100
FOR I%=1 TO 20
  IF I%=10 Inc%=-1
  Pitch%+=Inc%
  SOUND 1,-15,Pitch%,2
NEXT I%
 
END
 
REM Modem
 
FOR I%=1 TO 10
  SOUND 1,-15,RND(4)*4+150,1
  SOUND 1,-15,0,2
  WAIT 3
NEXT I%
 
SOUND 0,-15,2,15
SOUND 0,-15,0,15
SOUND 0,-15,4,25
 
END
I would like to emphasize here that this is not the limits of BBC BASIC in terms of graphics and if you are interested in games, there is even an entire sprite library. Using these simple techniques, however, you can liven up your programs quite considerably. Once upon a time, the computer world was awash with little programs like our game, basically because the micros of the day didn't have enough memory to do anything else! I believe there is still a place for fun programs like this. Just because you have gigabytes of memory doesn't mean your program is better for using as much as possible. As you progress as a programmer, your programs will get bigger. How to plan and develop these is the subject of the next chapter. In the meantime, you can have a lot of fun practising BASIC by concocting little programs such as this.

Exercises

1) Create a sound effect that uses short bursts of hiss to generate a noise like a machine gun.

2) Here is an incomplete game:

REM Chicane
MODE 6
VDU 23,220,255,213,171,213,171,213,171,255
VDU 23,221,90,126,90,24,90,126,90,24
Road$=CHR$(17)+CHR$(3)+STRING$(4,CHR$(220))
Car$=CHR$(17)+CHR$(4)+CHR$(221)
 
CarX%=20
RoadX%=18
Score%=0
Lives%=5
Count%=0
OFF
REM Fill screen before we start
FOR I%=1 TO 24
  WAIT 20
  PRINT TAB(RoadX%,24);Road$
NEXT I%
*REFRESH OFF
REM Main loop
REPEAT
  WAIT 20
  REM Score
  Score%+=1
  
  REM Create new road
  RoadX%=RoadX%+RND(3)-2
  REM Keep on screen
  IF RoadX%<5 THEN RoadX%=5
  IF RoadX%>30 THEN RoadX%=30
  PRINT TAB(RoadX%,24);Road$;
  
  PRINT TAB(0,24)
  PRINT TAB(CarX%,0);Car$
  *REFRESH
  
  REM Add car control code here ...
  
UNTIL Lives%=0
 
FOR I%=200 TO 150 STEP -1
  SOUND 1,-15,I%,1
  WAIT 1
NEXT I%
*REFRESH ON
CLS
COLOUR 7
PRINT TAB(6,12);"You scored ";Score%
ON
END
Try it. You will see a road snake its way up the screen. Your task is to add the lines of code that control the car. This should be added where indicated. Inspect the left and right keys and adjust the position, CarX%, accordingly. Keep the car in the range of 5 to 34. Examine the character in front of the car. If it is not 220 (the road character), make a noise and lose a life.

Left CONTENTS

CHAPTER 20 Right


Best viewed with Any Browser Valid HTML 3.2!
© Peter Nairn 2006