Coronado Enterprises C++ TUTOR: Chapter 12
Chapter 12: FLYAWAY ADVENTURE GAME
Now that you have learned lots of things about C++, and know how to write and use a single isolated class, you have the problem of how to build a program with several classes that work together to accomplish some task. After some amount of thought, it seems that an adventure game would be a good candidate for a relatively large example program. It has lots of input and output and requires a good deal of flexibility while running since there are so many things that can be included in the game as obstacles, mazes, items to find, and puzzles to solve.
The adventure game presented in this chapter is unique as far as I know, since I have never heard of another adventure game featuring an airport. The location is not nearly as important as the code used to get through the airport. You are advised to play the game to get familiar with what the code does, then study the code to see how it works. Finally, you are given an assignment to extend the code which will be the real test of whether you understand its operation.
PLAYING THE GAME
Prior to studying the source code for this game, it would be to your advantage to spend some time playing the game to get familiar with what the game does. Load the file FLYAWAY.EXE and begin the adventure through the airport. The executable file is precompiled for you so you can begin executing the program before you have to compile and link the whole thing. The entire program is composed of 15 files and will take a little effort on your part to properly compile and link it, but that will come later.
If you have played adventure games before, sometimes called interactive fiction, you should begin trying various commands to find your way through the airport to your proper plane. If you have not played before, a few hints are in order concerning how to play the game.
The object of the game is to get to your proper plane on time so you can fly away to your vacation. Of course there a few obstacles and problems along the way and they will be brought up at the appropriate time. It will be up to you to solve the puzzles associated with each problem. To add a little excitement, you only have about twenty-five minutes to get to your plane, with each move taking a minute, so you must hurry. Of course, just getting to the plane on time is not enough, there are a few additional requirements. You will find what they are as you progress through the game. You will probably find it necessary to restart the game many times before you arrive at your destination unscathed and on time.
THE METHOD OF PLAY
The method of play is extremely simple. You simply wander around the airport looking for things to do and places to go. You move around the airport by giving the system commands to go in a direction with four choices available, north, south, east, or west. You can abbreviate any of these four direction commands to the first letter only, and you can use either upper or lower case. The system may move you to another area of the airport, or it may tell you that you can't go that way. Try loading the game now and typing the four directions once each to see what happens. If this is not clear, enter the word help to get you started.
In addition to moving around, you can pick up items or ask for more information in any of the rooms. Try telling the system to look around the room and see what additional information it gives you for each room, some of the clues for solving the puzzle are given in the clues issued in response to a look command. Another important command is inventory which will give you a list of the items you possess at any given point in time. Type the word inventory at this time to see what items you possess.
The remainder of the commands consist of two words, a verb and a noun. These can be given in either order, since the system is smart enough to know the difference, and additional words may be given following the legal words. If you give the system a command that is not in its limited vocabulary, it will tell you it doesn't understand that word. Try telling the system to drop an item you possess, or get an item that is located in the room you are currently in.
Several friends have played this game with no more knowledge than you have been given. One solved it in 40 minutes, but most took about an hour to complete the game. After you play the game for awhile, return to the text and we will study the source code for the game. The entire source code for the game is on your distribution disk. The game was purposely kept small so the code could be easily grasped by a programming student. There is no reason the game could not have been made much larger by the addition of more rooms, items, and traps. You may choose to do just that to gain experience in working with C++.
A FEW SPECIAL CONSTANTS
The file named FLYAWAY.H contains the definitions for TRUE and FALSE as well as the enumerated type defining the legal dictionary of words for use in playing the game. The list was started at a value of 1 so the value of zero can be used to indicate that the word in question was not in the library and hence not a legal word for use with the game.
The #ifndef in line 5 is required because this header file is included in many of the other files and if it is included more than once, there will be a multiple definition, and hence an error. A class only needs to be defined once, so after it is defined by one of the includes, the name ITEMS_H will be defined and any other defines will be ignored. This is necessary because of the separate compilation capability of C++. This was described in more detail near the end of chapter 7.
FLYAWAY.H
// This file contains a few general purpose definitions to be used
// with several of the FLYAWAY adventure game classes.
#ifndef FLYAWAY_H
#define FLYAWAY_H
#define FALSE 0
#define TRUE 1
enum word {north = 1, east, south, west, drop, get,
look, inventory, read, buy, help, quit,
keys, candy, ticket, money, monitor, paper};
#endif
THE FIRST CLASS - clock
Examine the file named CLOCK.H for the definition of the clock class. This is the class for the game clock, and only one instance of this class will be used. It will be used for the object time_of_day defined in line 23 of FLYAWAY.CPP.
The class is very simple, consisting of only two variables, the hour and the minute, and four methods. The first method is the constructor used to initialize the clock to 8:51 as you can see if you refer to the implementation of this class in the file named CLOCK.CPP. The next two methods are used to get the current values of the two variables. The final method is much more interesting since it does more. It updates the time of day clock and outputs the user prompt to ask for the next command. This may not be the best place to output the user prompt since this class is devoted to the time of day and associated operations, but this was chosen as the place to do it since the time of day is part of the user prompt. You will notice that the clock was initialized to 8:51, but the first time output was 8:52 when you played the game. In order to simplify the coding later, when we need to decide if we made it to the plane on time, the time was incremented at the beginning of each game move. The time is therefore the same when the command is entered and when it is executed, hence the time is incremented prior to even the first output.
The clock class is by far the simplest class in the adventure game and should be simple for you to understand. After you are sure you understand it, we will go on to the next class.
CLOCK.H
// This is the game clock. It increments once for every
// move, the increment being accomplished in the method
// named inc_and_print_time.
#ifndef CLOCK_H
#define CLOCK_H
class clock {
int hour;
int minute;
public:
clock(void);
int present_hour(void) {return hour;}
int present_minute(void) {return minute;}
void inc_and_print_time(void);
};
#endif
CLOCK.CPP
#include <stdio.h>
#include "clock.h"
clock::clock(void)
{
hour = 8;
minute = 51;
}
void clock::inc_and_print_time(void)
{
minute++; // Add one minute to the time
if (minute > 59) {
minute -= 60;
hour++;
}
// Output the user prompt
printf("\n It is now %d:", hour);
printf("%02dam", minute);
printf(", what do you wish to do? ");
}
INPUT COMMAND PARSING
The input command parsing routines are defined within the words class and the code for the class is in WORDS.CPP. The code is straightforward and simple to understand if you study it, so only a few comments will be made about this class.
The method get_command() reads two words from the keyboard by calling the function read_a_line() and stores the words in the class members verb and noun. It stores zero for either or both of the words if it does not find a valid noun and a valid verb.
Two methods are included to provide the verb or noun which was input as the last user input. This allows any code that has visibility of the object based on this class to find out what the player would like to do.
There are four methods beginning with is_ in this class that are used to determine if a word is a verb, a noun, a direction, or an operation. These are called upon from various places within the program. What they do should be easy for you to understand, but it will take a little thought on your part to see why these are needed in other parts of the code.
Finally the simple method named stop_game() is used to set the verb to the value of quit so the game will be ended by the control logic in the main program FLYAWAY.CPP.
All of the source code for the implementation is given in the file named WORDS.CPP. Since this code is fairly simple and well commented, you will be left on your own to study it to whatever depth you desire.
WORDS.H
// This class reads and parses the user input, checks that a valid
// verb has been input, and checks for a valid noun.
#ifndef WORDS_H
#define WORDS_H
#include "flyaway.h"
class words {
enum word verb;
enum word noun;
void read_a_line(word &wd1, word &wd2);
int get_an_ASCII_word(char in_string[]);
int find_in_dictionary(char in_string[]);
int is_a_verb(enum word input_word);
int is_a_noun(enum word input_word);
int is_a_direction(enum word input_word);
int is_an_operation(enum word input_word);
public:
void get_command(void);
enum word get_verb(void) { return verb; };
enum word get_noun(void) { return noun; };
int is_a_verb(void) { return is_a_verb(verb); };
int is_a_noun(void) { return is_a_noun(noun); };
int is_a_direction(void) { return is_a_direction(verb); };
int is_an_operation(void) { return is_an_operation(verb); };
void stop_game(void) { verb = quit; };
};
#endif
WORDS.CPP
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "words.h"
#include "clock.h"
extern clock time_of_day;
// This function reads a line of text from the keyboard, parses
// it, and does some limited checking. Only the first two
// words are considered.
void words::get_command(void)
{
enum word wd1, wd2;
do {
time_of_day.inc_and_print_time();
verb = (enum word)0;
noun = (enum word)0;
read_a_line(wd1, wd2); // Get a line from the player
if (wd1) { // If there is a value for wd1
if (is_a_verb(wd1)) verb = wd1; // it is a verb
if (is_a_noun(wd1)) noun = wd1; // or a noun.
}
if (wd2) { // If there is a value for wd2
if (is_a_verb(wd2)) {
if (verb == 0)
verb = wd2; // it is a verb
else {
verb = noun = (enum word)0; // Two verbs, illegal
printf("Two verbs are illegal, ignored!\n");
}
}
if (is_a_noun(wd2)) {
if (noun == 0)
noun = wd2; // It is a noun.
else {
verb = noun = (enum word)0;
printf("Two nouns are illegal, ignored!\n");
}
}
}
if ((verb == 0) && (noun != 0)) {
verb = noun = (enum word)0;
printf("A verb is required, ignored!\n");
}
} while (verb == 0);
}
// This function reads words in ASCII form from the keyboard
// ignoring any words after two have been read. The words
// are checked to see if they are in the dictionary as de-
// fined by the enumeration variable named "word".
void words::read_a_line(word &wd1, word &wd2)
{
char string1[25], string2[25], string3[25];
char last_char;
last_char = get_an_ASCII_word(string1); // Get first word
if (last_char != '\n') {
last_char = get_an_ASCII_word(string2); // Get second word
while (last_char != '\n') { // Ignore all trailing words
last_char = get_an_ASCII_word(string3);
}
} else {
string2[0] = 0; // No second word
}
wd1 = (enum word)find_in_dictionary(string1);
wd2 = (enum word)find_in_dictionary(string2);
}
// This function reads a string, after ignoring the leading
// blanks. The string is terminated when any character is
// read that is not alphabetic. All characters are converted
// to lower case for internal use to allow typing flexibility.
int words::get_an_ASCII_word(char in_string[])
{
int char_count = 0;
int char_found = FALSE;
char c;
for (int index = 0 ; index < 80 ; index++) {
c = tolower(getchar());
if (c == '\n') { // End of line found
in_string[char_count] = 0;
return c;
}
if (isalpha(c) && char_count < 25) {
in_string[char_count++] = c;
char_found = TRUE;
} else {
if (isspace(c) && !char_found)
; // Ignore leading blanks
else {
in_string[char_count] = 0; // ASCIIZ terminator
return c;
}
}
}
}
// This function uses the dictionary pairs to convert the
// ASCII input strings into the internal enumeration values.
// This list must be maintained along with the enumerated
// type "word".
struct dict_pair {
char dict_string[10];
word found_word;
};
dict_pair pair[] = {"north" ,north,
"n" ,north,
"east" ,east,
"e" ,east,
"south" ,south,
"s" ,south,
"west" ,west,
"w" ,west,
"drop" ,drop,
"get" ,get,
"look" ,look,
"inventory",inventory,
"read" ,read,
"buy" ,buy,
"help" ,help,
"quit" ,quit,
"keys" ,keys,
"candy" ,candy,
"ticket" ,ticket,
"money" ,money,
"monitor" ,monitor,
"paper" ,paper,
"" ,(enum word)0 }; // List terminator
int words::find_in_dictionary(char in_string[])
{
int wd;
dict_pair *pointer = &pair[0];
if (in_string[0] == 0) return 0; // No string to look up
do {
if (strcmp(in_string, pointer->dict_string) == 0)
return pointer->found_word;
pointer = pointer + 1; // Next word in list
} while (pointer->found_word); // End of word list
printf("I don't know what \"%s\" is.\n",in_string);
return 0; // Word not found in list
}
// Is the input word a verb?
int words::is_a_verb(enum word input_word)
{
return ((input_word >= north) && (input_word <= quit));
}
// Is the input word a noun?
int words::is_a_noun(enum word input_word)
{
return ((input_word >= keys) && (input_word <= paper));
}
// Is the input word a direction?
int words::is_a_direction(enum word input_word)
{
return ((input_word >= north) && (input_word <= west));
}
// Is the input word a operation?
int words::is_an_operation(enum word input_word)
{
return ((input_word >= drop) && (input_word <= quit));
}
THE SECOND CLASS - items
If you examine the files named ITEMS.H and ITEMS.CPP, you will find the complete definitions of the handling of the items that you carried around the airport in the game.
There were exactly four transportable items that could be located in each room or carried by yourself, the keys, the candy, the ticket, and the money. The keys and the money keep you from getting through security and the ticket and candy are required to get you safely on the plane and enroute to your destination.
The four items are stored in the class named items in the form of TRUE or FALSE since that is the only required indication. A TRUE means the item is located here, and a FALSE means the item is not here. The values of TRUE and FALSE are defined in FLYAWAY.H. Finally, there are six methods to operate on these items.
The first method is a constructor to set all items to FALSE, and the next two are used to either get a specific item, or drop one. The fourth method is used to tell us if the item is located here and the last two are used to tell us what items are on hand in this location. You will notice that the final two are different because different text was desired depending on whether you are carrying the items, or they are located in a room somewhere.
This file, like all other header files, is protected from multiple inclusion by the #ifndef construct discussed earlier.
This class is used in line 24 of FLYAWAY.CPP to define an object for the player named personal_items which stores the list of items the player is carrying around. It is also used in the class location as an embedded or nested object to store the items that are located in each of the 19 locations in the game.
Once again, the implementation for this class is so simple that you will have no difficulty in understanding it.
ITEMS.H
// This stores the items located in each room and also in
// the players posession. It consists of four boolean
// variables and methods to use them.
#ifndef ITEMS_H
#define ITEMS_H
#include "words.h"
class items {
int keys_on_hand; // TRUE if keys are here, otherwise FALSE
int candy_on_hand;
int ticket_on_hand;
int money_on_hand;
public:
items(void); // Constructor, set all to FALSE
void add_item(word item_to_add); // Add one item to list
void drop_item(word item_to_drop); // Drop one item from list
int item_here(word item_to_check); // Returns TRUE or FALSE
void list_items(void); // List personal items
void list_items_in_room(void); // List location items
};
#endif
ITEMS.CPP
#include <iostream.h>
#include "flyaway.h"
#include "items.h"
items::items(void)
{
keys_on_hand = FALSE;
candy_on_hand = FALSE;
ticket_on_hand = FALSE;
money_on_hand = FALSE;
}
void items::add_item(word item_to_add)
{
switch (item_to_add) {
case keys : keys_on_hand = TRUE;
break;
case candy : candy_on_hand = TRUE;
break;
case ticket : ticket_on_hand = TRUE;
break;
case money : money_on_hand = TRUE;
break;
default : break;
}
}
void items::drop_item(word item_to_drop)
{
switch (item_to_drop) {
case keys : keys_on_hand = FALSE;
break;
case candy : candy_on_hand = FALSE;
break;
case ticket : ticket_on_hand = FALSE;
break;
case money : money_on_hand = FALSE;
break;
default : break;
}
}
int items::item_here(word item_to_check)
{
switch (item_to_check) {
case keys : return keys_on_hand;
break;
case candy : return candy_on_hand;
break;
case ticket : return ticket_on_hand;
break;
case money : return money_on_hand;
break;
default : return FALSE;
break;
}
}
void items::list_items(void)
{
if (keys_on_hand)
cout << "You have the keys to your car.\n";
if (candy_on_hand)
cout << "You have two candy bars.\n";
if (ticket_on_hand)
cout << "You have a ticket for your dream vacation.\n";
if (money_on_hand)
cout << "You have a couple of dollars of loose change.\n";
}
void items::list_items_in_room(void)
{
if (keys_on_hand)
cout << "There are car keys here.\n";
if (candy_on_hand)
cout << "There are some candy bars here.\n";
if (ticket_on_hand)
cout << "There is an airplane ticket here.\n";
if (money_on_hand)
cout << "There is some money here.\n";
}
THE FLIGHT AND GATE CLASS - schedule
Examine the files named SCHEDULE.H and SCHEDULE.CPP for our first example of a rather large class, the one that does the flight and gate scheduling. You will find a large number of variables in this class, and eight methods to handle the variables. Instead of a detailed description of each variable and method, we will only give a brief overview of the class.
Only one object of this class is declared named flight_info in line 22 of the main program named FLYAWAY.CPP. The constructor initializes the flight possibilities, and the method named shuffle_gates() shuffles all gates around if the player arrives at his correct gate without reading the monitor in the waiting area. Once the monitor in the waiting area is read, the flights_frozen variable is made TRUE. Likewise, the players destination is changed during every move by the method named shuffle_flights() until the player reads his ticket invoking the method named list_actual_destination().
This class contains the methods to list the data seen on the monitor, as well as the data seen when invoking the command look at one of the gates. Finally, this class contains the method named check_flight() which searches through the list of requirements to see if the player has completed all requirements to successfully reach the final destination for his vacation.
You will notice that several of the location objects were required to be available within this code and are listed as extern in lines 12 through 21 of the implementation of the class. The only other thing to point out is the rest room requirement prior to boarding the flight. Line 28 is where the global variable is defined and initialized, then in line 77 it is set TRUE if the current location is the rest room, since this is called once during each player move. Finally, the state of this variable is checked in line 230 of this file and the appropriate action taken. You will note that the main program is not aware that the rest room variable exists or that anything happens as a result of this variable. In addition to information hiding, we may coin a new term, something like "Information Ignorance", since the main program did not even need to be aware that there was a requirement to visit the rest room.
Even though this is a relatively large and complex class, it is well commented so no further information will be given concerning the implementation.
SCHEDULE.H
// This takes care of all gate assignments and flight scheduling.
// The players flight is shuffled (changed) each move until he reads
// his ticket. If he gets to the proper gate prior to reading the
// monitor in the waiting area, (reading the monitor at the ticket
// counter doesn't matter), the gates are rescheduled.
//
// The method named check_flight does all of the required checking
// to see that everything was done properly prior to getting on
// the plane. It only does checking if the player is on one of the
// planes.
#ifndef SCHEDULE_H
#define SCHEDULE_H
#include "location.h"
class schedule {
location *gate[4]; // Gate names
int flight_number[4]; // There are four flights [0] to [3]
char *destination[4];
int depart_hour[4];
int depart_minute[4];
int flights_frozen; // Frozen after monitor is read in the
// waiting area
int gates_frozen; // Frozen after ticket is read
int my_gate;
public:
schedule(void);
void shuffle_flights(void);
void shuffle_gates(void);
void list_flights(location *current_location);
void gate_message(location *current_location);
void list_actual_destination(void);
void list_time(int index);
void check_flight(void);
};
#endif
SCHEDULE.CPP
#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include "flyaway.h"
#include "schedule.h"
#include "items.h"
#include "map.h"
#include "location.h"
#include "clock.h"
extern location rest_room;
extern location waiting_area;
extern location gate1;
extern location gate2;
extern location gate3;
extern location gate4;
extern location plane1;
extern location plane2;
extern location plane3;
extern location plane4;
extern items personal_items;
extern clock time_of_day;
extern words input_words;
extern map airport;
int visited_rest_room = FALSE; // TRUE if the rest room was visited
// as checked in shuffle_gates
schedule::schedule(void)
{
flight_number[0] = 222;
flight_number[1] = 17;
flight_number[2] = 141;
flight_number[3] = 79;
destination[0] = new char[7];
strcpy(destination[0], "HAWAII");
destination[1] = new char[7];
strcpy(destination[1], "PARIS ");
destination[2] = new char[7];
strcpy(destination[2], "ROME ");
destination[3] = new char[7];
strcpy(destination[3], "TAHITI");
depart_hour[0] = 9;
depart_hour[1] = 9;
depart_hour[2] = 9;
depart_hour[3] = 9;
depart_minute[0] = 19;
depart_minute[1] = 17;
depart_minute[2] = 16;
depart_minute[3] = 18;
gate[0] = &gate1;
gate[1] = &gate2;
gate[2] = &gate3;
gate[3] = &gate4;
flights_frozen = FALSE;
gates_frozen = FALSE;
my_gate = 0;
}
void schedule::shuffle_flights(void)
{
if (!flights_frozen) // Until flights are frozen,
my_gate = (my_gate + 1) % 4; // select the next flight
// at the next gate.
}
void schedule::shuffle_gates(void)
{
int temp;
char *temp_point;
if (airport.get_current_location() == &rest_room)
visited_rest_room = TRUE;
if ((airport.get_current_location() ==
gate[my_gate]) && (!gates_frozen)) {
temp = flight_number[0];
flight_number[0] = flight_number[1];
flight_number[1] = flight_number[2];
flight_number[2] = flight_number[3];
flight_number[3] = temp;
temp_point = destination[0];
destination[0] = destination[1];
destination[1] = destination[2];
destination[2] = destination[3];
destination[3] = temp_point;
temp = depart_hour[0];
depart_hour[0] = depart_hour[1];
depart_hour[1] = depart_hour[2];
depart_hour[2] = depart_hour[3];
depart_hour[3] = temp;
temp = depart_minute[0];
depart_minute[0] = depart_minute[1];
depart_minute[1] = depart_minute[2];
depart_minute[2] = depart_minute[3];
depart_minute[3] = temp;
my_gate = (my_gate + 3) % 4; // Subtract one from my_gate
cout <<
"A message is heard on the airport paging system, \"All"
" gates\nhave been rescheduled due to bad weather. No "
"flights have\nbeen cancelled at this time.\"\n";
}
}
// This freezes the gate assignments
void schedule::list_flights(location *current_location)
{
if (current_location == &waiting_area)
gates_frozen = TRUE;
for (int index = 0 ; index < 4 ; index++) {
printf("Gate %d - Flight %3d - %s - ",
(index + 1),
flight_number[index],
destination[index]);
list_time(index);
}
}
void schedule::gate_message(location *current_location)
{
int index;
// Find gate we are at and print message
for (index = 0;index < 4;index++)
if (current_location == gate[index]) goto gate_is_found;
return; // If not a gate
gate_is_found:
printf("Flight %3d - %s - ", // If a gate is found
flight_number[index],
destination[index]);
list_time(index);
}
// This freezes the players flight
void schedule::list_actual_destination(void)
{
flights_frozen = TRUE;
printf("Flight %3d - %s - ",
flight_number[my_gate],
destination[my_gate]);
list_time(my_gate);
}
void schedule::list_time(int index)
{
if (depart_minute[index] < 10)
printf("%d:0%d\n",depart_hour[index], depart_minute[index]);
else
printf("%d:%d\n",depart_hour[index], depart_minute[index]);
}
void schedule::check_flight(void)
{
if ((airport.get_current_location() == &plane1) ||
(airport.get_current_location() == &plane2) ||
(airport.get_current_location() == &plane3) ||
(airport.get_current_location() == &plane4)) {
// You must have a ticket
if (!personal_items.item_here(ticket))
cout <<
"Unfortunately, you don't have a ticket, you are arrested\n"
"as a stowaway and drug off the plane screaming. No vaca-\n"
"tion for you.\n";
// On plane 1 from gate 1
else if ((airport.get_current_location() == &plane1) &&
(my_gate != 0))
cout <<
"Unfortunately, you are at gate 1 and this flight is going\n"
" to " << destination[0] << ". Better luck next time.\n";
// On plane 2 from gate 2
else if ((airport.get_current_location() == &plane2) &&
(my_gate != 1))
cout <<
"Unfortunately, you are at gate 2 and this flight is going\n"
" to " << destination[1] << ". Better luck next time.\n";
// On plane 3 from gate 3
else if ((airport.get_current_location() == &plane3) &&
(my_gate != 2))
cout <<
"Unfortunately, you are at gate 3 and this flight is going\n"
" to " << destination[2] << ". Better luck next time.\n";
// On plane 4 from gate 4
else if ((airport.get_current_location() == &plane4) &&
(my_gate != 3))
cout <<
"Unfortunately, you are at gate 4 and this flight is going\n"
" to " << destination[3] << ". Better luck next time.\n";
// You must be on time
else if ((time_of_day.present_hour() > depart_hour[my_gate]) ||
(time_of_day.present_minute() > depart_minute[my_gate]))
cout <<
"Unfortunately, you are too late for your flight and are\n"
"aboard a cargo plane to Greasy Creek, Missouri. Better\n"
"luck next time.\n";
// You must have candy
else if (!personal_items.item_here(candy))
cout <<
"Unfortunately, you failed to bring any food along and you\n"
"died of malnutrition half way to your destination.\n";
// You must visit the rest room
else if (!visited_rest_room)
cout <<
"Unfortunately, you forgot to take care of your bladder\n"
"problem back at the airport. The restrooms on this plane\n"
"are out-of-order. You suffer a ruptured bladder and die\n"
"enroute to your destination.\n";
// A successful trip through the airport
else
cout <<
"Congratulations, you are comfortably enroute to your well\n"
"deserved vacation. You can study the source code to this\n"
"program on the plane. If you do not have the source code,\n"
"you can read the paper in the lobby for the address where\n"
"you can write for more information.\n";
cout << "\nHit any key to end the game.\n";
getch(); // Wait for keyhit
input_words.stop_game(); // End the game
}
}
THE MOST USED CLASS - location
The file named LOCATION.H is the header file for the class named location. It is the class that controls all of the moves from location to location.
This class is a bit unusual in that most of the stored data is in the form of pointers to the various entities. The first four are the locations to which we will go if we move in one of the four directions from the current location. You will note that they are pointers to those four locations. Next we have pointers to two different character strings associated with this room. Finally in line 22, we declare the object named list_of_items which is an object of class items defined earlier. Note that this is an embedded class, a class embedded within the location class. It is not a parent class which we are inheriting something from. In fact we are instantiating an object of class items for use within the room since the room is allowed to store any combination of the four items contained in the class named items.
There is no constructor used with this class since we choose to initialize the locations one by one. The method named init() has 6 variable parameters, all of which are pointers, associated with it which it uses to initialize the first six variables of this object. The last variable, an object of class items, is initialized through use of the constructor associated with its class. Referring to lines 40 through 171 of the implementation for the map class, you will find all of the initialization code for the 19 objects of class location. If you drew a map when you played the game, you will see the interconnections between the various locations embedded in the initialization statements. Notice there is no way to get back to the car from the passenger drop off area, because presumably the car leaves when you get out of it.
The next method, named move(), returns a pointer to the new location if a move was legal, otherwise it returns a NULL value. The observant student will also notice that there are special cases involved with getting out of the snack bar and getting through security. These are located here because they are part of the move logic. If you played the game to the complete conclusion, you surely had trouble with at least one of these situations.
The rest of the methods in this class should be self explanatory and will not be discussed any further.
LOCATION.H
// This is the definition of the 19 different locations it is
// possible to enter. This class contains an embedded object of
// class "items" to store the elements in each location. The
// message is output automatically when the location is entered,
// and the look_message is output when the player gives the look
// command.
#ifndef LOCATION_H
#define LOCATION_H
#include "items.h" // This gets the definition of the item list
class location {
location *north_move; // Where we go to, north of here
location *east_move; // Where we go to, east of here
location *south_move; // Where we go to, south of here
location *west_move; // Where we go to, west of here
char *message; // Message output when we enter here
char *look_message; // The message output for a "look"
items list_of_items; // The list of items in this location
public:
void init(location *valid_north, // These four directions are
location *valid_east, // initialized when init
location *valid_south, // is called.
location *valid_west,
char *local_message,
char *local_look_message);
location *move(word direction); // Move to another location
void add_item(word item_to_add); // This puts an item here
void drop_item(word item_to_drop);// Item picked up by player
char item_here(word item_to_check);// Is this item here?
void display_message(void); // This displays the message
void display_list_of_items(void); // Display items found here
// and a few room details.
};
#endif
LOCATION.CPP
#include <iostream.h>
#include "flyaway.h"
#include "location.h"
#include "items.h"
#include "schedule.h"
extern location snack_bar; // Special action in "move"
extern location security; // Special action in "move"
extern items personal_items; // Reference to players items
extern schedule flight_info;
void location::init(location *valid_north,
location *valid_east,
location *valid_south,
location *valid_west,
char *local_message,
char *local_look_message)
{
north_move = valid_north;
east_move = valid_east;
south_move = valid_south;
west_move = valid_west;
message = local_message;
look_message = local_look_message;
}
location *location::move(word direction)
{
int no_go = FALSE;
// Getting out of the snack bar
if ((this == &snack_bar) && (personal_items.item_here(money)) &&
(personal_items.item_here(candy)))
cout <<
"You took a candy bar and didn't pay for it. Airport security\n"
"grabs you and after much verbal abuse agrees to let you go if\n"
"you will either return the candy bar or pay for it.\n";
// Getting through security
else if ((this == &security) && (direction == north) &&
((personal_items.item_here(keys)) ||
(personal_items.item_here(money))))
cout <<
"You cannot get through security because the beeper detects\n"
"some metallic object on you. Many people are staring at\n"
"you and thinking that you look dangerous.\n";
// A normal move somewhere
else
switch (direction) {
case north : if (north_move)
return (north_move); // Location to north
else
no_go = TRUE; // You can't go that way!
break;
case east : if (east_move)
return (east_move); // Location to east
else
no_go = TRUE; // You can't go that way!
break;
case south : if (south_move)
return (south_move); // Location to south
else
no_go = TRUE; // You can't go that way!
break;
case west : if (west_move)
return (west_move); // Location to west
else
no_go = TRUE; // You can't go that way!
break;
default : cout << "This is not a move.\n";
return(NULL);
}
if (no_go) cout << "Sorry, you cannot go that way!\n";
return (NULL);
}
// This adds an item to the items object in this room
void location::add_item(word item_to_add)
{
list_of_items.add_item(item_to_add);
}
// This drops an item from the list in this room
void location::drop_item(word item_to_drop)
{
list_of_items.drop_item(item_to_drop);
}
// This returns TRUE if the item is located here
char location::item_here(word item_to_check)
{
return (list_of_items.item_here(item_to_check));
}
// This displays the message when the room is entered
// It also displays the flight information at a gate
void location::display_message()
{
cout << message;
}
// This displays the items located in this room
void location::display_list_of_items(void)
{
cout << look_message;
list_of_items.list_items_in_room();
flight_info.gate_message(this); // List the flight information
// if you are at a gate
}
THE LOCATION MESSAGES
Examine the file named MESSAGE.TXT for a complete listing of the messages output to the monitor when each location was entered. You will also find the text for each of the messages output in response to a look command in this file. These were put into a separate file only for the purpose of reducing the size of the map class implementation file. It does not reduce the compile time since these messages are not separately compiled. They are included into the file and compiled each time the map file MAP.CPP is compiled. You will note that a few of the messages have no text at all, only the empty quote marks, but are included in order to have something for the initialization code to work with.
Three other messages are stored here for convenience in lines 5 through 40. Their use and meaning should be self-evident.
MESSAGE.TXT
// The first three messages are relatively long, so they have
// been removed from the text in order to make the text more
// readable.
char startup_message[] =
" Welcome to Flyaway, version 2.20\n\n"
" Your best friend offered to drop you off at the airport\n"
" so you can begin your dream vacation and you have just\n"
" arrived at the passenger drop off area. You have about\n"
" 25 minutes to get to your plane, you haven't had any \n"
" lunch, and you have a full bladder. Be very careful, \n"
" there is a lot of construction going on all around the \n"
" airport. Good luck!\n\n"
" Type help if you want a few clues and a word list.\n\n";
char paper_message[] =
"\n C++ TUTORIAL RELEASED\n"
"Coronado Enterprises has a full line of computer language\n"
"programming tutorials available. Write and ask for the\n"
"latest information.\n"
" Coronado Enterprises\n"
" 12501 Coronado Ave NE\n"
" Albuquerque, NM 87122\n\n"
"There is another story about danger at the airport due to\n"
"construction. Be very careful!\n";
char help_message[] = // Help
"Each action requires a verb, or a verb and a noun, and only\n"
"the first two words of the command are significant, any \n"
"other words on a line are ignored. The four directions can\n"
"be abbreviated to the first letter to make it easier to get\n"
"to your flight. The entire vocabulary is given as;\n\n"
" ------- verbs ------- ---- nouns ----\n"
" north drop read keys money\n"
" east get buy candy monitor\n"
" south look help ticket paper\n"
" west quit inventory\n\n"
" look = give more information on current location\n"
" inventory = list items I am carrying\n\n"
"You better hurry, you just wasted a minute reading this.\n\n";
// These messages are output when entering a location
char your_car_message[] = ""; // Never needed - can't enter here
char pass_drop_off_message[] =
"You are in the passenger drop off area.\n";
char lobby_message[] =
"You are in the airport lobby.\n";
char baggage_claim_message[] =
"You are in the baggage claim area. There are a few bags left\n"
"over from the last flight slowly going around the carrosel.\n";
char dark_room_message[] =
"You are in a poorly lit room and as you move about, you step\n"
"on a board that gives way under your weight. You fall into \n"
"a subbasement and are killed. No vacation - game over.\n";
char ticket_counter_message[] =
"You are at the ticket counter area.\n";
char tunnel_message[] =
"You are in the tunnel to the gates.\n";
char rest_room_message[] =
"You are in the rest room, and it really feels good to have\n"
"taken care of that problem.\n";
char snack_bar_message[] =
"You are in the snack bar and gift shop.\n";
char security_message[] =
"You are in the security and inspection area leading toward\n"
"all gates.\n";
char waiting_area_message[] =
"You are in the waiting area between the gates.\n";
char gate1_message[] =
"You are in the gate 1 waiting area.\n";
char gate2_message[] =
"You are in the gate 2 waiting area.\n";
char gate3_message[] =
"You are in the gate 3 waiting area.\n";
char gate4_message[] =
"You are in the gate 4 waiting area.\n";
char plane_message[] =
"You are inside of a large Jumbo-jet. The doors close, and\n"
"the plane taxis away from the gate to prepare for takeoff.\n";
// These are output in response to a "look" command
char y_c_look_message[] =
"You are in your car with your friend, but that should have\n"
"been obvious. You better hurry, you may miss your plane.\n";
char p_d_o_look_message[] =
"The airport entrance is to the north.\n";
char l_look_message[] =
"A small newsstand is here, and the latest edition of the news-\n"
"paper is on the newsstand. A group of three ragged looking\n"
"characters have signs that proclaim \"BAN THE BOMB\", and are\n"
"trying to sell you some books.\n";
char b_c_look_message[] =
"There is a dark room to the west with a sign near the door\n"
"that cautions you to keep out - danger. You should not enter\n"
"this room under any circumstances.\n";
char d_r_look_message[] = "";
char t_c_look_message[] =
"There is a departure monitor on the west wall with a list of\n"
"all of the currently active flights. Wilbur Snuffle is stand-\n"
"ing behind the ticket counter. He asks if you have any baggage\n"
"to check through.\n";
char t_look_message[] =
"A sign on the north wall says \"TO ALL GATES\".\n";
char r_r_look_message[] =
"That's not at all polite to look around in here, and there is\n"
"nothing of interest here, you better hurry to your flight.\n";
char s_b_look_message[] =
"There are many expensive items for sale here, but have no need\n"
"of any of those expensive things. They are always overpriced\n"
"at the airport anyway. A sweet looking old lady is ready to\n"
"help you find what you need.\n";
char s_look_message[] =
"The security inspectors are to the north, and they look you\n"
"over very carefully as you enter. The tall inspector is Ralph\n"
"and the short one is either Homer or Bill.\n";
char w_a_look_message[] =
"There is a departure monitor on the north wall, and a lot of\n"
"construction material laying around everywhere.\n";
char g1_look_message[] = "";
char g2_look_message[] = "";
char g3_look_message[] = "";
char g4_look_message[] = "";
char plane_look_message[] = "";
THE MAIN PROGRAM
We finally reach the main program, the one that actually does the top level control. Examine the program named FLYAWAY.CPP and we will look at some of its interesting characteristics.
Beginning with the main() entry point itself, we see that following a call to airport.initialize(), we enter a single do while loop which terminates when the player enters the word quit or when the verb quit comes up some other way. There are other ways to set the verb to quit because it is generated internally in some cases such as at end of game.
The loop itself consists of 5 method calls. First we call the function named input_words.get_command() to get the players input command in line 30. Next we send two messages to the object named flight_info to shuffle the flights and gates if the proper actions have not been performed, then we call airport.perform_action() which we will describe in a few paragraphs. Finally, we send a messages to the object named flight_info to check if the player has reached one of the gates. Remember that within most of the methods we perform checks to see if we need to do the thing requested in the message, then either perform the action or simply return to the caller or message sender.
FLYAWAY.CPP
// XXXXX X X X X X X X X X
// X X X X X X X X X X X X
// X X X X X X X X X X X X
// XXX X X X X X X X X X
// X X X XXXXX X X X XXXXX X
// X X X X X X X X X X X
// X XXXXX X X X X X X X X
// FLYAWAY - version 2.20
// Written by: Gordon Dodrill - Jan 20, 1992
// Copywrite 1989, 1990, 1992 - Coronado Enterprises
#include <stdio.h>
#include "words.h"
#include "map.h"
#include "schedule.h"
#include "clock.h"
#include "items.h"
words input_words; // The player's command inputs
map airport; // The physical layout of the airport
schedule flight_info; // Schedule and gate information
clock time_of_day; // The system timekeeper
items personal_items; // Things the player carries with him
main()
{
airport.initialize();
do {
input_words.get_command(); // Get user inputs
flight_info.shuffle_flights(); // Until monitor read
flight_info.shuffle_gates(); // Until ticket read
airport.perform_action(); // Try to perform the request
flight_info.check_flight(); // Did you get there?
} while (input_words.get_verb() != quit);
}
THE WORKING METHOD
The only function we have not mentioned yet is the one that does most of the interesting work, the function named perform_action() which begins in line 183 of the MAP.CPP file. This function looks at the verb and noun, if there is one, and causes the correct action to be performed. Because of the way we packaged all of the other routines, this function is a snap to implement and to study. If you go through each of the else if clauses in this function, you will have no trouble understanding what action is taken for each of the input commands. You will notice that many of the actions have conditional clauses before the action is taken. For example, it is illegal to buy candy unless the player has money, the location has candy, and the location must be the snack_bar according to the rules of the game.
Finally, at the end of this method in line 277, we have the default case if nothing else was accomplished. It is assumed that there was something funny requested such as a request to get a monitor. Both of these are legal words but they make no sense together.
MAP.H
// This class handles the airport layout. It defines which direc-
// tion you can go from each location and performs the actions
// requested by the player.
#ifndef MAP_H
#define MAP_H
#include "location.h"
class map {
location *present_location;
location *result;
public:
void initialize(void);
void perform_action(void);
location *get_current_location(void) { return present_location; };
};
#endif
MAP.CPP
#include <iostream.h>
#include "conio.h"
#include "map.h"
#include "message.txt"
#include "words.h"
#include "items.h"
#include "schedule.h"
location your_car;
location pass_drop_off;
location lobby;
location baggage_claim;
location dark_room;
location ticket_counter;
location tunnel;
location rest_room;
location snack_bar;
location security;
location waiting_area;
location gate1;
location gate2;
location gate3;
location gate4;
location plane1;
location plane2;
location plane3;
location plane4;
extern schedule flight_info;
extern items personal_items;
extern words input_words;
void map::initialize(void)
{
cout << startup_message;
present_location = &your_car;
your_car.init(&pass_drop_off, // North from here
NULL, // East from here
NULL, // South from here
NULL, // West from here
your_car_message, // message when entering here
y_c_look_message); // message for look command
pass_drop_off.init(&lobby,
NULL,
NULL, // You cannot go back to the car, it leaves
NULL,
pass_drop_off_message,
p_d_o_look_message);
lobby.init(&ticket_counter,
NULL,
&pass_drop_off,
&baggage_claim,
lobby_message,
l_look_message);
baggage_claim.init(NULL,
&lobby,
NULL,
&dark_room,
baggage_claim_message,
b_c_look_message);
dark_room.init(NULL,
NULL,
NULL,
NULL,
dark_room_message,
d_r_look_message);
ticket_counter.init(&tunnel,
NULL,
&lobby,
NULL,
ticket_counter_message,
t_c_look_message);
tunnel.init(&security,
&snack_bar,
&ticket_counter,
&rest_room,
tunnel_message,
t_look_message);
rest_room.init(NULL,
&tunnel,
NULL,
NULL,
rest_room_message,
r_r_look_message);
snack_bar.init(NULL,
NULL,
NULL,
&tunnel,
snack_bar_message,
s_b_look_message);
security.init(&waiting_area,
NULL,
&tunnel,
NULL,
security_message,
s_look_message);
waiting_area.init(NULL,
&gate3,
&security,
&gate2,
waiting_area_message,
w_a_look_message);
gate1.init(&plane1,
&gate2,
NULL,
NULL,
gate1_message,
g1_look_message);
plane1.init(NULL,
NULL,
NULL,
NULL,
plane_message,
plane_look_message);
gate2.init(&plane2,
&waiting_area,
NULL,
&gate1,
gate2_message,
g2_look_message);
plane2.init(NULL,
NULL,
NULL,
NULL,
plane_message,
plane_look_message);
gate3.init(&plane3,
&gate4,
NULL,
&waiting_area,
gate3_message,
g3_look_message);
plane3.init(NULL,
NULL,
NULL,
NULL,
plane_message,
plane_look_message);
gate4.init(&plane4,
NULL,
NULL,
&gate3,
gate4_message,
g4_look_message);
plane4.init(NULL,
NULL,
NULL,
NULL,
plane_message,
plane_look_message);
personal_items.add_item(keys); // Player gets keys
personal_items.add_item(money); // Player gets money
your_car.add_item(ticket); // Ticket is in car
snack_bar.add_item(candy); // Candy is in snack bar
}
void map::perform_action(void)
{
if (input_words.is_a_direction()) { // Move to a new location
result = present_location->move(input_words.get_verb());
if (result) { // If Non-NULL
present_location = result; // Valid move found
present_location->display_message();
}
// Force end of game if in dark room
if (present_location == &dark_room) {
input_words.stop_game(); // Set the verb to "quit"
cout << "Hit any key to end the game.";
getch();
}
}
// Inventory
else if (input_words.get_verb() == inventory)
personal_items.list_items();
// Look
else if (input_words.get_verb() == look)
present_location->display_list_of_items();
// Drop item
else if (input_words.get_verb() == drop) {
if (personal_items.item_here(input_words.get_noun())) {
personal_items.drop_item(input_words.get_noun());
present_location->add_item(input_words.get_noun());
cout << " Dropped.\n";
} else {
cout << "You can't drop what you don't have.\n";
}
}
// Get item
else if (input_words.get_verb() == get) {
if (present_location->item_here(input_words.get_noun())) {
present_location->drop_item(input_words.get_noun());
personal_items.add_item(input_words.get_noun());
cout << " Picked up.\n";
} else {
cout << "It isn't here so you can't pick it up.\n";
}
}
// Buy candy
else if ((input_words.get_verb() == buy) &&
(input_words.get_noun() == candy) &&
(present_location == &snack_bar)) {
if ((personal_items.item_here(money)) &&
(present_location->item_here(candy))) {
personal_items.drop_item(money);
personal_items.add_item(candy);
present_location->drop_item(candy);
present_location->add_item(money);
cout << " You now have candy.\n";
} else
cout << "Surely you are not serious about that!\n";
}
// Read ticket
else if ((input_words.get_verb() == read) &&
(input_words.get_noun() == ticket))
if (personal_items.item_here(ticket))
flight_info.list_actual_destination();
else
cout << "You don't have a ticket to read.\n";
// Read monitor
else if ((input_words.get_verb() == read) &&
(input_words.get_noun() == monitor) &&
(present_location == &ticket_counter))
flight_info.list_flights(present_location);
// Read monitor
else if ((input_words.get_verb() == read) &&
(input_words.get_noun() == monitor) &&
(present_location == &waiting_area))
flight_info.list_flights(present_location);
// Read paper
else if ((input_words.get_verb() == read) &&
(input_words.get_noun() == paper) &&
(present_location == &lobby))
cout << paper_message;
// Help
else if (input_words.get_verb() == help)
cout << help_message;
// Quit
else if (input_words.get_verb() == quit) // Ignore to prevent
; // message output
else
cout << "I don't understand what you want.\n";
}
FINAL COMMENTS ON FLYAWAY
Now that you have played the game for awhile and studied the game in detail, you should have an appreciation for how this game can be written. Of course, it could be written in any of several thousand different ways of packaging and definition. This has been only one of the ways.
Because the student may be left with the sinking feeling that this method simply fell out of the sky or was arrived at in some other esoteric way, it would only be fair to point out that several earlier attempts at outlining this project were attempted and rejected prior to this arrangement. Also, when this tutorial was being updated from version 2.0 to 2.2, the entire program was restructured. In version 2.0 and prior versions, about 50% of the code was in classes, but due to additional programing experience, about 98% of the flyaway program is now encapsulated in classes.
Object oriented programming requires the same forethought as non- object oriented programming, but the object oriented compiler will help you in the coding and debugging phase since the compiler will find and flag many of the oversight errors we are so good at introducing into our code. It was observed during the coding and debugging phase of this project that in nearly every case, when the program finally got through the compiler, the program would actually run without bombing out the system. This is not always the case using any standard procedural programming language.
YOUR PROGRAMMING PROJECT
This programming assignment is intended to give you a little experience in working with a relatively large project as opposed to the very small programs we have been working with in this tutorial.
Add a suitcase to the game, to be found in the car at arrival, and which must be checked in at the ticket counter prior to attempting to get through airport security. This will not be trivial since several classes will be affected. Some of the operations you will have to do are listed below.
- Add the noun "suitcase" and the verb "check" to the word list. Of course, they must be entered at the right place in the list.
- Add the suitcase to the items class, including additional code to each of its methods.
- Initialize the items at location your_car to include the suitcase.
- Add an additional check when passing through security to check that the player is not carrying the suitcase. You can add any sort of penalty desired, including death by firing squad for attempting such an obviously crooked deed.
- You will need to add a check when the player finally gets on his correct airplane to see that he checked his suitcase. If he did not, you could output any desired text indicating stupidity or forgetfulness.
Since I have not actually added the suitcase to the game and tested it, I am not sure that this is all that will be required, but it should be the majority of effort required. The bottom line of this effort is that if you understand this program enough to perform this modification, you have a good understanding of how the program works and how objects work together to perform a task.
Once you understand this program, you should define a programming project for yourself that will use object oriented programming techniques and begin designing and programming it. The best way to learn to use OOP is to actually use it.
Good luck in your OOP endeavors.