Game Programming in Practice Part I
Game Programming in Practice Part I
In this first tutorial we will develop a game called "Memory Train." No, it will
not involve motorized strolls down Memory Lane, and neither will we deal with
wagonloads
of microchips. This game will train your memory, hence the title.
Contents
The story of every game begins with an idea, an initial metaphorical spark in the
designer's brain. This is the one creative impulse out of which the entire project
evolves. It is vital that we hold on to that initial idea throughout the entire
process of designing, implementing, and testing. Only when we hold on to that first
creative impulse will we later be able to determine if we are still on track and
take corrective measures if it turns out that we are not. Such an initial idea
will usually fit into one small paragraph of text, often a single sentence.
Before we dive into programming tasks, let us talk briefly about the process that
leads from that initial idea to the finished game. This process involves a number
of activities, including gameplay design, story design, sound design,
implementation, and testing. For the purposes of describing the game creation
process, let
us assume that each of these activities is carried out by another person.
The central role is played by the gameplay designer because her job is to create
the high-level design of how the game will operate. If we were to design a card
game, for instance, the gameplay designer would primarily concern herself with
creating the rules of the game. Here are some questions the gameplay designer would
need to ask herself: How many cards does the game use? Will it be a trump game?
Will there be suits and ranks? How will the score be determined? How long should
the typical game take? Will it be possible to play against the computer? Against a
human opponent at the same computer? Over the network? All of these questions
can be answered without drawing a single picture, without recording a single sound
effect, and without writing a single line of code.
In determining the high-level design, the gameplay designer generates the input
required by most of the other team members. For example, the programmer needs to
know the exact rules of the game in order to be able to translate them into code.
Eventually, the combined effort of programmer and sound designer will culminate in
a finished prototype. This is in turn closely inspected by the gameplay designer
to ensure that the finished product will meet her expectations. In case the
prototype does not live up to the gameplay designer's expectations, the appropriate
changes will be performed by the various team members and a new prototype is
created. This loop continues until the gameplay designer determines that the
resulting
prototype is in fact the game she designed. Finally, as with any piece of software,
the game will have to go through the various stages of alpha and beta testing
to ensure its quality. Alpha testing is performed behind closed doors by a
dedicated team of testers, while beta testing is performed by potential customers
volunteering
to give your product a try before it is finally released.
The objective in our game is to repeat sequences of tones which get more complex as
the game progresses.
Here's how it works: The game is played using the arrow keys, and each of the four
arrow keys is associated with a specific tone. When the game begins, the computer
randomly plays one of the four tones, and the player has to hit the corresponding
arrow key. Next, the computer plays that first tone again and then adds a second
tone, and the player has to hit the two corresponding arrow keys in sequence. If
the player gets it right, the computer adds yet a third tone, and the player has
to repeat all three tones in sequence by hitting the corresponding keys. This
process continues until the player makes a mistake by either pressing the wrong key
or failing to react within a certain amount of time. When the player makes a
mistake the computer will play a special sound after which the final score will be
announced. The final score is the number of tones which the player was able to
repeat in sequence.
Here's an example:
The computer plays the tone for the up arrow key.
The player hits the up arrow key.
The computer plays the tones for up arrow and left arrow, one after the other.
The player hits up arrow then left arrow.
The computer plays the tones for up arrow, left arrow, left arrow.
The player hits up arrow, left arrow, left arrow.
The computer plays the tones for up, left, left, down.
The player presses up arrow, right arrow, thereby making a mistake.
The game ends, and the final score is 3 points because the longest sequence the
player successfully repeated consisted of three tones.
A key aspect of successfully playing this game is memorizing which arrow key is
tied to which tone. To make this as easy as possible we will implement a keyboard
practice mode in which the player can press the arrow keys and listen to the tones
they generate without any time constraints. To enter this practice mode, the
player selects the appropriate option from a menu. Not only will this make our game
look considerably more professional but it will also allow me to explain how
to create menus in BGT. And if you thought it would end there, let me inform you
that our menu will also have background music.
Exercise:
Reread the above game design and create a list of the sounds the game will contain.
Since this is a tutorial about programming rather than sound design, you will need
to create the sounds that you wish to use for the game. In the below code, we
have selected the filenames that we use for each of these sounds. The four tones
are named 1.wav, 2.wav, 3.wav and 4.wav, the error sound is named error.wav, and
the music loop is named, appropriately enough, music.wav.
3.1 Preparations
It is almost time to actually start programming! But before diving into source
code, let us get some preliminaries out of the way.
Given the fact that you are reading this tutorial, it is quite likely that you have
already installed BGT on your computer. But just in case you haven't, now is
the time to do so.
Next, please create a new, empty directory for this project, and place the sounds
you created inside this directory.
Finally, open your favorite text editor and create an empty text file called
memory_train.bgt in the newly created directory. Some text editors, including
Notepad,
have a tendency to add a .txt extension to any file name that does not already end
in .txt. A tried and tested remedy for this is to include the file name in double
quotes. Another way to prevent the automatic .txt extension is to choose "all
files" in the file type field before clicking the "Save" button.
If you have studied the language tutorial, you will know that every BGT script
contains one or more functions. The word "function" comes from the Latin "functio"
which means "execution" or "performance." In programming, a function is some code
that performs a well-defined task.
A function, in performing its task, can call other functions to carry out subtasks.
The important thing to keep in mind here is that when one function calls another,
the calling function is not finished. This leads to the rather peculiar fact that
several functions may be executing at the same time but only one of them is in
control. As an example from daily life, let us assume that the function
"get_dressed" has called the function "put_on_socks". Now the function
"put_on_socks" is
in control but the function "get_dressed" is not finished. Instead, it is patiently
waiting in the wings for "put_on_socks" to finish. As soon as this happens,
"get_dressed" is back in control and continues executing where it left off, which
will be just after the call to "put_on_socks".
I strongly urge you to reread the previous paragraph until you understand it
completely. One of the main reasons why people find programming confusing is that
they
have misunderstood the concept of functions and function calls. In case you are
still confused, the next time you put on your socks you will feel a warm glow
inside
and the concept of function calls will make perfect sense.
As an added bonus, once a function finishes it may return a value to its caller.
This is appropriately called the function's return value, or simply its result.
It is well worth our time to take a look at the smallest BGT script which could
possibly be written. For although it stops almost immediately after it starts and
does exactly nothing in between, it yet provides the structure, or framework, of
any other BGT script including Memory Train. Since it will eventually become part
of Memory Train anyway, now is also the time to copy it into your memory_train.bgt
file. Here it is, in full:
void main()
{
}
This defines a single function called main. The word "void" specifies that this
function has no return value, which makes sense once you realize that it is the
solitary function in this script and thus has nobody to return anything to. But its
lack of a return value has a much more practical reason, and this has to do
with the special significance of the name "main". The main function is special in
that it is where execution of every BGT script begins, and the execution of every
BGT script ends when the main function finishes. The two braces you see above
embrace between themselves the entire execution of your script.
Let us now make our script actually do something by placing some instructions
between those braces.
Exercise:
Find the alert function in the BGT reference, and with the help of the reference
modify your script to display a message box. The title of the message box should
be "Important Information", and the message text should be "Hello, I am John Doe,
and I am a programmer." Instead of John Doe, put your own name into the message.
Hint on using the reference: The "alert" function is part of BGT's foundation
layer.
void main()
{
alert("Important Information", "Hello, I am Jane Smith, and I am a programmer.");
}
To execute your script, simply save the file, locate it in Windows Explorer and hit
enter on it. Note that this only works when BGT is installed.
Let's see what happens if we add another function call. Our new script is as
follows:
void main()
{
alert("Important Information", "Hello, I am Jane Smith, and I am a programmer.");
alert("How many roads must a man walk down?", "The answer is 42.");
}
When you run this script, you will notice that, as you might expect, it displays
two message boxes, one after the other. This serves to illustrate the point I made
above, namely, that the main function is not stopped when it calls the alert
function. As soon as the alert function has finished doing its first job, control
flows
back into the main function which then proceeds to call the alert function again.
A BGT script can do much more than carry out instructions in sequence. Sooner or
later our scripts must learn to make decisions based on what the user does. Only
then can we develop interactive scripts, and interactivity is, after all, what
differentiates a game from a play.
void main()
{
question("A personal question", "Do you believe in the flying spaghetti monster?");
}
When you execute this script, it displays a message box similar to the one
displayed by a call to alert. However, this time the solitary OK button is replaced
by
a Yes and a No button.
Exercise:
Asking the user a question without reacting to the answer is pretty pointless, so
we will now modify the script to make a decision based on which of the two buttons
the user clicks. As you learned by consulting the reference, the "question"
function returns a value of 1 for the Yes button, 2 for the No button, and 0 if an
error
occurs.
This defines a variable named "answer" which can hold a value of type "int". "int"
is shorthand for "integer" and means a whole number, i.e. a number without a
decimal point. If you would like to learn about the various other types supported
by BGT, let me refer you to the language tutorial.
When we have defined a variable, we can assign a value to it by writing the name of
the variable, an equals sign, the value we would like to assign, and finally
a semicolon:
answer = 42;
In our running example, we don't want to place the number 42 in our variable.
Instead, we would like our variable to store the value that the "question" function
returned. We achieve this by simply replacing our 42 with the call to the
"question" function:
void main()
{
int answer;
answer = question("A personal question", "Do you believe in the flying spaghetti
monster?");
alert("Thank you!", "Your answer was " + answer + ". See you later.");
}
If you try out this script, you will notice that you get different messages
depending on which of the two buttons you choose. This is because the variable
"answer"
receives the value returned by the "question" function. So this is in fact our
first interactive script.
Note also how we used the plus sign to tie several things together into something
larger:
"Your answer was " + answer + ". See you later."
Scanning this from left to right, we find some text in double quotes, a plus sign,
a variable name, another plus sign, and some more text in double quotes. In this
case, the plus signs serve to tie these three things together into the message we
would like to display, like tying together three strings of pearls into a longer
string of pearls. Programmers refer to a piece of text as a string because it is
easily visualized as a string of characters. To use this term in context, we have
just added a string, a number, and another string to form our message, which is
itself a string.
You might be wondering, if the plus sign is used for tying strings together, how
would you add two numbers? The surprising answer is that the plus sign is used
for that purpose as well. The plus sign is a symbol which may refer to different
activities, depending on context. In technical terms, the plus sign is an
overloaded
operator.
In our running example, our script is now able to react differently depending on
which button the user clicks, but our reactions are not yet very meaningful.
Wouldn't
it be great if we could display completely different messages depending on the
user's response? The following script does exactly that:
void main()
{
int answer;
answer = question("A personal question", "Do you believe in the flying spaghetti
monster?");
if(answer == 1)
{
alert("How interesting!", "Thank you for being that honest.");
}
else if(answer == 2)
{
alert("I thought so!", "Now don't tell me the invisible pink unicorn got you
first.");
}
else
{
alert("Whoops!", "Something is dreadfully wrong here! Maybe it's the monster taking
revenge.");
}
}
This script uses the "if" statement to make a decision based on the value of the
"answer" variable. If the value is 1, the first message is displayed. Otherwise,
if the value is 2, the second message is displayed. Finally, if the value is
neither 1 nor 2, the third message is displayed, indicating that an error has
occurred.
A condition is something which can either be true or false. This is in line with
the usage of the word in every-day English. For example, the condition for going
by train is that you are in possession of a valid ticket. If the condition is false
you may not board the train.
The condition to test whether two things are equal looks like this:
a == b
Note the use of a double equal sign. This is required because the single equal sign
is already used for something else.
Exercise:
What is the single equal sign used for? Hint: The answer is contained earlier in
this tutorial.
Note that we have enclosed every branch of the above "if" statement in its own pair
of braces. Strictly speaking, this is necessary only if a branch consists of
multiple instructions. Since all three branches in the above script consist of a
single instruction, namely, a call to "alert", we could just as well have written
the following:
void main()
{
int answer;
answer = question("A personal question", "Do you believe in the flying spaghetti
monster?");
if(answer == 1)
alert("Ramen to you, then!", "Thank you for being that honest.");
else if(answer == 2)
alert("I thought so!", "Now don't tell me the invisible pink unicorn got you
first.");
else
alert("Whoops!", "Something is dreadfully wrong here! Maybe it's the monster taking
revenge.");
}
Over the years of my programming career I have formed the habit of always including
the braces from the start even in such cases where they are not strictly necessary.
This way, when I add more statements at a later date I never have to worry about
whether or not to add another pair of braces.
Just so that you know, the braces in a function definition are mandatory. For
instance, the definition of your main function will always contain braces even if
the function consists of a single instruction---in fact, even if it should consist
of no instructions at all!
Exercise:
Read the relevant parts of the language tutorial to familiarize yourself with the
various programming constructs for making decisions. In particular, pay close
attention to the if, while, and for statements as they will be used in this
tutorial.
In our quest for theoretical background we have digressed quite a bit from our
original endeavor, which was to implement the game we designed in the previous
chapter.
Now is the time to get back to it.
void main()
{
}
Exercise:
The helper layer contains a helpful tool for creating menus. Can you find it in the
reference?
The tool we are after is called dynamic_menu. It allows us to easily put together a
menu in which the player can select an item with the up and down arrow keys
and activate it by pressing enter. As you undoubtedly know, such menus abound in
audiogames, with typical menu items being "start game", "test speakers", "options",
Let's take a moment to summarize what you already know about functions and
variables.
In this section we will go one step further and talk about a kind of variable which
has other variables and functions living inside it. A value with variables and
functions inside it is called an object, and a variable holding an object is called
an object variable, just as a variable holding an int is called an int variable.
If this all sounds just a bit confusing, let me assure you that it will quickly
become second nature to you once you see it in action. In just a few paragraphs
you will realize that objects are all about simplicity.
#include "dynamic_menu.bgt"
void main()
{
show_game_window("Memory Train");
dynamic_menu menu;
menu.add_item_tts("Start game");
menu.add_item_tts("Keyboard practice");
menu.add_item_tts("Exit game");
menu.allow_escape = true;
menu.wrap = true;
menu.run("Please choose a menu item with the arrow keys, then hit enter to activate
it.", true);
}
Exercise:
With the help of the reference, try to figure out what the above script will do.
Test your hypothesis by running the script. Note: This script contains some
constructs
we have not covered yet, but they will all be explained below.
Let's start with the elements you already recognize. First, you will immediately
have noticed that this is your standard main function with a sequence of
instructions
between the braces. Next, examine the following line:
show_game_window("Memory Train");
This is some name followed by a pair of parentheses with some data between them,
and finally a semicolon. What do we call such a thing? Yes, a function call.
Exercise:
How many parameters does the above function call have?
Exercise:
Find out what the function does.
You may be wondering why we would want to display a window in the first place when
our game will not have any visual elements. The answer is that on Microsoft
Windows,
any program that handles keyboard input should display a window. Our game will only
be able to react to keyboard input when our game window is active. This will
also allow the player to switch back and forth between our game and other running
applications.
Both of these statements are variable definitions. One of them defines a variable
of type int and gives it the name "answer", the other one defines a variable of
type dynamic_menu and gives it the name "menu". And just as int is a data type for
numbers, dynamic_menu is a data type for menus. The word "dynamic" is used here
meaning "flexible" because you, as game programmer, can decide which items the menu
will contain.
Notice the striking similarity to function calls? The only difference is that this
time we are calling a function that lives inside our menu variable. Recall that
an object is a value with variables and functions living inside it. In this case we
have on our hands an object of type dynamic_menu.
To call a function inside an object, we write the variable name, a full stop, and
then the name of the function we are calling. This is followed by the usual pair
of parentheses which, as we have learned, is part of every function call. A
function inside an object is called a method. To use this term in context:
add_item_tts
is a method of the menu object.
An object gets its methods from its type. Thus, to find out what methods an object
provides we need only know what type, or class, the object belongs to. The "menu"
object is of class dynamic_menu, so we need only consult the reference of
dynamic_menu to find out about the available methods.
Exercise:
With the menu set up the way it is, we need to perform one more step to present it
to the user:
menu.run("Please choose a menu item with the arrow keys, then hit enter to activate
it.", true);
This section covers two additional building blocks required for putting Memory
Train together. Let us begin with sound, which will be an essential feature of any
audio game you will ever develop.
void main()
{
sound intro; // Creates the sound object.
intro.load("intro.wav"); // Loads the sound; equivalent to putting a tape into the
player.
intro.play(); // Starts playing.
while(intro.playing)
{
// Do something while the intro is playing.
wait(5); // Give other Windows tasks 5 milliseconds time.
}
// The sound has stopped.
}
Some games, including Memory Train, make use of a speech synthesizer on the
player's computer. The way to do this in BGT is to use a tts_voice object, where
tts
stands for "text to speech." The two most interesting methods of tts_voice are
speak and speak_wait. The speak method will begin speaking the string which was
passed
to it and will continue speaking in the background while your program may do other
things. In comparison, the speak_wait method will additionally wait until the
speech has stopped, and only then will your program continue with the next
instruction.
3.7 Time
All but the simplest of games need to keep track of time in some way. BGT provides
a timer object for this purpose. The most important features of the timer object
are the restart method and the elapsed property. By calling the restart method,
your program is able to reset the timer back to zero in much the same way as if
you were restarting a stopwatch. The elapsed property will always contain the
number of milliseconds since the timer was last restarted, or, if it was never
restarted,
elapsed will contain the number of milliseconds since the timer was created.
In order for your game to be able to respond to keyboard input, BGT contains two
functions which let you check the status of any given key on the user's keyboard.
Use the key_down function to find out if a given key is being held down at that
moment. Note that key_down will return true as long as the key is being held down.
If you are not interested in how long a key is being held but rather would like to
be informed of every keypress just once, use the key_pressed function instead.
When a key is held down, key_pressed will return true only the first time you check
that particular key, and false on subsequent calls. Only if the key was released
and is now being held down once more will key_pressed return true again.
To find out which keys can be checked and how they are expressed in BGT, consult
appendix A of the BGT help system.
Armed with a basic understanding of the fundamental features of BGT, we can now
begin coding the bulk of Memory Train. Let us start with the main function which,
as you learned earlier in this tutorial, is the heart of any BGT script. The
responsibilities of the main function are as follows:
� 1. It loads the various sounds required by the game into memory.
� 2. It sets up the game menu.
� 3. It speaks a short intro.
� 4. It repeatedly runs the menu and executes the function the user has chosen.
� 5. The repetition ends as soon as the user presses the escape key in the menu or
chooses the "exit game" entry.
While we are at it, we will also define some global variables the main function
refers to. Our code now looks as follows:
#include "dynamic_menu.bgt"
// Sound objects.
sound music;
sound error_sound;
sound[] tone(4); // The four tones used for sequences.
void main()
{
// Load the four tones.
tone[0].load("1.wav");
tone[0].volume = -10;
tone[1].load("2.wav");
tone[1].volume = -10;
tone[2].load("3.wav");
tone[2].volume = -10;
tone[3].load("4.wav");
tone[3].volume = -10;
// Set up voice.
tts_voice voice;
// Set up menu.
dynamic_menu menu;
menu.allow_escape = true;
menu.wrap = true;
menu.add_item_tts("Start game");
menu.add_item_tts("Keyboard practice");
menu.add_item_tts("Exit game");
do
{
choice = menu.run("Please choose a menu item with the arrow keys, then hit enter to
activate it.", true);
if(choice==1)
{
music.stop();
play_round(); // We will define this function later.
music.play_looped();
}
else if(choice==2)
{
music.stop();
keyboard_practice(); // We will define this function later.
music.play_looped();
}
}
while(choice!=0 and choice!=3);
If the user chooses the keyboard practice item from the menu, our above main
function will call a function named keyboard_practice. This has the following
responsibilities:
� 1. It speaks instructions on how to operate the keyboard practice mode.
� 2. It waits for the user's keypresses.
� 3. When an arrow key has been pressed, it plays the appropriate tone.
� 4. When escape has been pressed, keyboard practice is aborted.
Here is the code for the keyboard_practice function:
void keyboard_practice()
{
tts_voice voice;
voice.speak_wait("Press the arrow keys to find out which key generates which
tone.");
voice.speak_wait("Press escape to stop practicing.");
while(!key_pressed(KEY_ESCAPE))
{
if(key_pressed(KEY_LEFT))
{
play_tone(0);
}
else if(key_pressed(KEY_DOWN))
{
play_tone(1);
}
else if(key_pressed(KEY_RIGHT))
{
play_tone(2);
}
else if(key_pressed(KEY_UP))
{
play_tone(3);
}
wait(5);
}
}
Notice how we introduced the play_tone function in the above code for
keyboard_practice. The responsibility of play_tone is to play a single tone. Here
is the corresponding
code:
void play_tone(int i)
{
tone[i].stop();
tone[i].play();
}
This function first stops the tone in case it is already playing, then restarts
playing it. By the way, if the expression tone[i] confuses you, this might be a
good time to read up on the subject of arrays in the language tutorial.
If the user chooses the "start game" item from our game menu, the main function
calls the play_round function. This function is responsible for carrying out an
entire round of play, after which the score is announced. Here is the code for
play_round:
void play_round()
{
// Initialize game state.
bool game_over = false; // The game will continue as long as this is false.
int[] sequence; // The running sequence.
int sequence_length = 0;
float time_between_tones = 500; // The initial speed at which tones are played, in
milliseconds.
float time_between_inputs = 2000; // Maximum time to input next key before player
gets bounced.
do
{
// Add another tone to the sequence.
sequence_length++;
sequence.resize(sequence_length);
sequence[sequence_length-1] = random(0,3);
// Play back the sequence from start to finish.
output_sequence(@sequence, time_between_tones);
// Let them repeat it if they can.
game_over = input_sequence(@sequence, time_between_inputs);
// Every time another five tones have been mastered:
// increase the speed ever so slightly.
if((sequence_length%5) == 0)
{
time_between_tones = time_between_tones*0.9;
// Make sure it does not fall below 150.
if(time_between_tones < 150)
{
time_between_tones = 150;
}
}
}
while(!game_over);
int score = sequence_length-1; // minus 1 because they failed on the last.
tts_voice voice;
voice.speak_wait("Your final score was " + score);
}
The output_sequence function plays a sequence of tones so that the player may try
to memorize it. It requires two parameters, the sequence to output, and the time
between the tones. This flexible approach was taken to enable the game to increase
the speed over time. Here is the code for output_sequence:
The final puzzle piece is the input_sequence function. This function is responsible
for testing if the player remembers the sequence correctly. If the player fails
to press one of the arrow keys in a given time, or presses the wrong arrow key,
this function will return true to indicate that the game is over. If, on the other
hand, the player manages to replay the sequence correctly, then this function
returns false, indicating that the game is to continue. Here is the code for
input_sequence:
The complete source code for Memory Train is contained in the code fragments given
in sections 3.9 to 3.14. In order to test the game, you can simply paste all
of the code fragments into the file memory_train.bgt which you created earlier. To
run the game, simply navigate to the file memory_train.bgt in Windows Explorer,
then press enter to start.
Final exercises:
� 1. Extend the game to provide a two-player mode. Both players sit at the same
keyboard. Player one uses the arrow keys to play, and player two uses the keys a,
s, d, and w. In this variant, sequences are not randomly generated by the computer.
Instead, player 1 starts with one tone, player 2 repeats the one tone and adds
another, player 1 repeats the two tones and adds a third, etc. The game ends when
one of the two players fails to press a key in time, presses the wrong key, or
presses a key when it was not his or her turn. The player making the mistake loses,
and the other player wins with a score equal to the longest sequence he typed.
� 2. Extend the game so that it provides different difficulty levels. Note that
this is both a design and an implementation exercise.