Exercise 3.1: Testing the Pop program

Any large program's code is likely to have bugs in it, and it's even more likely that there are places where the documentation is out of sync with the actual behavior of the program. While going through the help file and testing the Pop program, look for these three kinds of problems.

Bugs. A bug is when the program does something that seems wrong. Crashing is the extreme case, but other kinds of odd behavior can be bugs as well. For a useful bug report, explain exactly how to reproduce the bug. Note that it possible that something the tester thinks of as a bug may be what the programmer thinks of as a feature.

Bad features. Features of the program that you find bad or confusing or which look like bugs. Explain what you don't like. If many testers have the impression that a feature is a bug, then the feature needs be changed or, at the very least, better documented.

Bad documentation. Find cases where the help file description does not seem to match the behavior of the program. Also note cases where some program feature is not well-explained.

Exercise 3.2: First build

Install Visual Studio. Put the Pop code onto your hard drive, find the pop.sln file (Version 7.0) [or pop.dsw file (Version 6.0)] file in the Windows Explorer and double-click on it to open up the project in Visual Studio. Press Ctrl+Shift+B (Version 7.0) [or F7 (Version 6.0)] to build the Pop program, watching the messages that go by in the Output pane that can be found at the bottom of the Visual Studio window. If you get a successful build, press F5 to run the Pop program inside the Visual Studio debugger. If you have any problems or questions check Chapter 20: Using Microsoft Visual Studio for more information. After the build, use Windows Explorer to see what kinds of files have been added to your disk by the build.

Exercise 3.3: Code hand-off

First clean your Pop code directory by closing Visual Studio, using Windows Explorer to navigate into the directory, and clicking on clean.bat. After clean.bat runs, use Windows Explorer to see if there are any *.exe still in the directory. If there are, delete them so as to minimize your directory size.

If you don't have WinZip on your machine, go to www.winzip.com and download and install a free evaluation copy. Choose the 'Classic' settings as your preferred WinZip default.

Right-click on your Pop code directory and select WinZip from the context menu to zip it up.

Note these considerations about the WinZip settings. Let's assume you are running WinZip in the 'Classic' interface mode and that you are using the current (as of Spring, 2002) Version 8.1. WinZip will save your directory name, which is good, as the directory name will probably have version and date information. Also WinZip automatically saves your directories subdirectories, which is good, as you need the res subdirectory to be able to rebuild the code.

In the Options field of the Add dialog box, don't check Save Extra Folder Info. You don't want to check Save Extra Folder Info because, for portability, you don't want to include the full path to directory where your files live. Even if this isn't checked, WinZip will save the name of the directory you are zipping.

Choose the name mypop1.zip for your zip file and save it somewhere where you can find it, perhaps in the C:\Temp directory. Don't save it in with the same code that you're zipping. After its been zipped, find mypop1.zip and unzip it (not into the same location as the original Pop code that you were working with). Open its pop.dsw with Visual Studio and see if it will build. If this works, try sending mypop1.zip to yourself as an email attachment, see if you can then unzip it and build it. Practice these steps until you can do them all.

Exercise 3.4: Changing the date information for your build

Set the date of your Pop build to match the current date in three places. (a) Put a version number and a build date into the name of your executable file. In Visual Studio, Version 7.0, first make sure that you have View | Project Explorer open and that you have clicked on the Pop node, and then use View | Property Pages | Linker | General | Output File. If another node is active, Property Pages will open up a different dialog. [In Version 6.0, you can always simply use the Program | Settings | Link | General | Output File Name.] Change the name of the executable both for the Release build and the Debug build. You switch between them in either Version 6.0 or 7.0 by using the settings for control in the upper left-hand corner of the dialog box with which you are editing the output file name. (b) Open the Resource view. You can do this in Version 7.0 with View | Resource | View. [In Version 6.0, use View | Workspace | Res, where you'll find the 'Res' as a tab at the bottom of the Workspace window.] Click on the String Table resource in Resource View and then click on IDR_MAINFRAME to use this string to include the build number and the date; this string is what appears in the caption bar of your *.exe. (c) Change the name of the directory where you code lives by highlighting the the directory name in Explorer and pressing F2 so you can edit it.

Exercise 3.5: Look at some Pop code files

With the Pop project in Visual Studio, use File | Open to open the gamespacewar.h and gamespacewar.cpp files to get an idea of how much code goes into a game definition. You'll see that it's not all that much, as you only need to mention the methods that you plan to override. Now look at the game.h and game.cpp files to get an idea of what kinds of methods cGameSpacewar inherits from cGame.

Now take a brief look at the critter.* and critterarmed.* files. This code is fairly gnarly (in the sense of 'complex'), but we'll explain a lot of it later on. For now just scan over critter.h to get an idea of what the cCritter methods are.

Exercise 3.6: Look at the Pop resources

Open the Pop project in Visual Studio and then open the Resource View. You can do this in Version 7.0 with View | Resource View. [In Version 6.0, use View | Workspace | Res, where you'll find the 'Res' as a tab at the bottom of the Workspace window.] Now click on the various items in the Resource view to view them ? the way this works is that anything with a + next to it is like a directory to be opened up. Click down through the things with + till you get some bottom level things like bitmaps, menus, etc. Find the IDB_BACKGROUND bitmap and the IDR_POPTYPE menu. Note that the Resource view of the menu is 'live,' that is, you can open up the menu selections and edit them. This is useful as, later, when you want to turn Pop into a single game, you can simply remove the menu references to the other game modes so that the users won't have the possibility of going into them.

Exercise 3.7: Renaming a game

Changing the names of a game that appear in copies of existing files is a little tricky. In this problem we ask you to practice. In Windows Explorer, select the gamestub *.h and *.cpp files, and use Ctrl+C and Ctrl+V to copy them. Highlight the file names one by one, press F2 and change the names to, say, gamemyproject.h and gamemyproject.cpp. Now open up the Pop Framework in Visual Studio and use the Project | Add Existing Item... dialog (Version 7.0) [or Project | Add to Project | Files...dialog (Version 6.0)] dialog to add your new files to the project. Edit the files in Visual Studio to replace every instance of the phrase 'Stub' by the phrase 'MyProject,' being sensitive to upper and lower case. That is, you must replace 'Stub' by 'MyProject', 'stub' by 'myproject' and 'STUB' by 'MYPROJECT'. You can do this by using Ctrl+H to do a search and replace several times in each file, with the Match Case checkbox turned on. Now see if you can get the altered project to compile. This may take a couple of tries, especially if you weren't careful about case sensitivity in the search and replaces. If you've totally messed things up (always easy to do when starting out!) make fresh copies of the files and start over. Once it compiles, edit the CPopDoc constructor in the popdoc.cpp file so that the default start up game class is cGameMyProject. You'll have to add a line #include "gamemyproject.h" to popdoc.cpp so this will compile. If you want to do a bit more, look at Chapter 27: Menus and Toolbars and figure out how to add and implement a My Project option on the Game menu.

Exercise 3.8: Expanding a UML diagram

The UML diagram given for the cGameStub in this chapter is missing the classes cCritterArmedPlayer, cCritterArmedRobot, and cCritterBulletSilver. Redraw the picture, with these intermediate classes squeezed into the tree of inheritance.

Exercise 3.9: Writing a Space Invaders game

The rest of the problems on this chapter have to do with converting the Game Stub game into a Space Invaders game.

Game stub modified to resemble a Space Invaders game


Rather than carrying out the slightly tricky task of changing all the names in the gamestub.* files, let's just use these files as is, and make some changes in them. You might want to save off reference copies of these files called gamestubold.* in case you want to get the old code back after a while. Or simply make sure that you do your work in a fresh copy of the whole Pop source directory.

The following exercises describe a specific series of changes to make to the files. The purpose is simply to have you get a feel for how you might make your own game out of the Pop code. Don't feel you need to be able to understand all of the code you see, just go ahead and carry out the following steps to see how you might work with it.

Just in case you've never seen a Space Invaders game, the idea is that the player controls an upwards-pointing critter that can be moved left and right along the bottom of the screen with the arrow keys. The critter shoots a bullet upwards when the spacebar is pressed. Falling down from the top of the screen are enemy critters. Shooting enemies gives the player score points, and each time an enemy survives to touch the bottom of the screen, the player loses a health point. Whenever all the falling creatures have been shot, a new wave of them appears; alternately we can bring in new enemies as fast we kill them off. Typically the player starts with three or maybe five health points and plays until he or she loses them all. The new waves of enemies move faster than the earlier waves, so that as time goes on, the game gets harder and harder to play, inevitably ending in the player's death. The score points accumulated are a measure of how long the player managed to stay alive.

A Space Invaders style game is generally not considered to be an acceptable project for a course taught with the Software Engineering and Computer Games textbook. The reason is that (a) this project is too easy and (b) the one-dimensionality of the Space Invaders game player motion makes the game pretty boring. Once you finish the Space Invaders game in this section, you should set it aside and make a fresh start for your real course project.

Exercise 3.10.1: Change the default game. Beware the wrong-directory-gotcha

When you get into tweaking one particular game mode, it saves time to have the Pop program start up in the game mode that you want to play with. The way to control this is to edit the CPopDoc constructor in popdoc.cpp. Simply comment in exactly the one setGameClass line corresponding to the game you want to play. If you make a new game class, add a line for it. For the following exercises, have your startup game be cGameStub.

/* Choose the type of game you want at startup by commenting in ONE 
setGameClass line. The setGameClass sets brandnewgameflag to TRUE. */ 

//  setGameClass(RUNTIME_CLASS(cGameSpacewar)); 
//  setGameClass(RUNTIME_CLASS(cGameAirhockey)); 
//  setGameClass(RUNTIME_CLASS(cGameBallworld)); 
//  setGameClass(RUNTIME_CLASS(cGameDambuilder)); 
//  setGameClass(RUNTIME_CLASS(cGamePickNPop)); 
//  setGameClass(RUNTIME_CLASS(cGameWorms)); 
//  setGameClass(RUNTIME_CLASS(cGameStub3D)); 
//  setGameClass(RUNTIME_CLASS(cGameDefender3D)); 

If you do this exercise and the game still starts up in the original Spacewar Game mode, it's very likely that you edited the wrong copy of popdoc.cpp. One of the gotchas of Visual Studio is that when you use the File | Open command, the file selection dialog doesn't make it clear which directory you are in. Visual Studio has a certain persistence of state, and if you open a file in DirectoryA, the next time you open a file the dialog is likely to search in DirectoryA again, even if you are now working on a project in DirectoryB. One often has multiple copies of the Pop Framework code on one's disk, and it is easy to be editing a file in the wrong directory.

A sure sign that you're editing the wrong files is if (a) your program always compiles and runs with no warnings or error messages and (b) the appearance of the executable looks the same after each 'build.'

How to avoid this gotcha? If you see signs of (a) and (b), close all your files, close your project, reopen your project in your desired directory, and then open your file, only this time use the File | Open dialog to back a step or two up the directory tree to find out what directory you're really in, and then go back down into the correct directory.

Exercise 3.10.2: Change the cGameStub world

We edit some of the cGameStub methods in the gamestub.cpp file to change the appearance of the game world.

  1. Our goal here is to make a simple Space Invaders game. Let's not use the cCritterStubRival at all, let's just have dumb non-shooting cCritterSpaceInvadersProp falling down on us. We can do this by changing the two static critter count numbers that are used in the cGameStub constructor to initialize _rivalcount and _seedcount. The statics are defined right before the cGameStub::cGameStub() constructor. Change the lines to read:

    int cGameStub::DEFAULTSEEDCOUNT = 8; 
    int cGameStub::DEFAULTRIVALCOUNT = 0; 
  2. Let's make our world tall and thin. We can do this by changing a line in the cGamestub::cGamestub constructor. Take the line _border.set(60.0, 40.0), and change it to _border.set(20.0, 40.0).

  3. We don't want to start out zoomed in on the world. Find the code for the void cGameStub::initializeViewpoint(cCritterViewer *pviewer) method and comment out two lines.



  4. We don't want the view to move with the player, so find the void cGameStub::initializeView(CPopView *pview) code and comment out a line.


Exercise 3.10.3: Change the cCritterStubPlayer

  1. Before changing the constructor, change the value of a static variable used in the constructor. At the start of the gamestub.cpp file, change the PLAYERHEALTH line to this.

    int cGameStub::PLAYERHEALTH = 3; 

    Now we're going to add some code to the end of the cCritterStubPlayer::cCritterStubPlayer(cGame *pownergame) code in gamestub.cpp.

  2. We want to use the arrow keys to move our player. Either add this line to the end of the constructor, or alternately use it to replace the existing setListener line with this line.

    setListener(new cListenerArrow()); 
  3. To make the arrow key motion a little peppier, give the player a higher maximum speed (which is the speed the arrow moves it at). Add this line.

    setMaxspeed(30.0); // Careful not to write setMaxSpeed 
  4. Limit the player to moving back and forth along the bottom of the screen. This means we want to change the player's cRealBox _movebox field.

    We do this in the player's constructor. Our framework is set up so that in this code block you can assume that the player's _movebox has already been set to match the game's _border box. We now want to use setMoveBox to change the _movebox.

    The setMoveBox call takes a cRealBox as argument. The cRealBox constructor we use here takes cVector specifying two opposite corners as arguments, the lower left front corner and the upper right back corner. Add this block of code to the end of the constructor code. What we're doing here is to move the 'high corner' down almost to the bottom of the _border box.

    /* At this point the player's _movebox matches the _border it got 
    from pownergame. 
    Now we want to make the _movebox just be the bottom edge 
    of the world. */ 
            _movebox.hicorner() ? 
            //Move high corner almost to the bottom of world. 
            (_movebox.ysize()-2*radius())* cVector::YAXIS 
  5. Another aspect of a Space Invaders game is that the player's gun always points straight up. We'll make this change in the cCritterStubPlayer constructor. Change the old cCritterStubPlayer constructor by adding these lines to the bottom of it.

        /* The default for _attitude motion lock is TRUE, which means 
    that by default a critter turns its heading to match its direction 
    of motion. We turn this behavior off so the player can always 
    point up. */ 
    setAttitudeTangent(cVector::YAXIS); /* Call this AFTER turning off 
    the lock setAimVector(cVector::YAXIS); */ 

Exercise 3.10.4: Change the cCritterStubProp constructor

Now we make some changes to the bottom of the cCritterStubProp::cCritterStubProp(cGame *pownergame) code in gamestub.cpp.

  1. Let's have the props automatically be positioned up near the top of the world. Since the cCritterStubProp constructor uses a cGame argument, its base class constructor will have set its _movebox to match the game's _border. To move the critters up to the top of the world, add these lines.

        _movebox.locorner() + 
            (_movebox.ysize() ? 2*radius()) * cVector::YAXIS, 
  2. Let's put a force of gravity on the cCritterStubProp critters. Usually when you have gravity, it's a good idea to put in some 'air friction' as well. Add these lines to the end of the constructor code.

    addForce(new cForceGravity()); 
    addForce(new cForceDrag()); 
  3. Let's soup up the game by allowing the critters to fall a bit faster. Add this line. You might find the value 8.0 to be a shade too low or high.

    setMaxspeed(8.0); //Careful not to write setMaxSpeed 
  4. Now let's try having the cCritterStubProp critters run away from the bullets. Try adding a line like this. You may not like the effect of this, so it's optional.

    addForce(new cForceClassEvade(4.0, 1.0, 

Exercise 3.10.5: Change cCritterStubProp::update

Each critter has an int _outcode field that is an OR combination of bit flags telling you which, if any, edge of its cRealBox _movebox the critter touched during its last move. The bit flags, which are defined in the realbox.h file, have simple names like BOX_LOY. We will use the _outcode to take action when a cCritterStubProp hits the bottom or the top of the screen.

When one of the cCritterStubProp critters hits the bottom of the screen, we want to kill off the critter and reduce the player's health by calling its damage method.

If you have called setWrapflag(cCritter::WRAP), then the cCritterStubProp might get to the bottom by going around the top. When our cCritterStubProp run away from bullets they might sometimes do this. It would unfairly punish the player if we let the cCritterStubProps get away with that, as then they would be in a position to cross back and the game might think they landed on the bottom. Therefore if a cCritterStubProp hits the top of the world we kill it off without charging the player a damage point.

We do all this by changing the cCritterStubProp update method to look like this.

void cCritterStubProp::update(CPopView *pactiveview, Realdt) 
    cCritter::update(pactiveview, dt); //Always call this first 
    if (_outcode & BOX_LOY) //Landing damages me 
    if (_outcode & BOX_HIY) //So they don't sneak around over the top. 

Exercise 3.10.6: Change cCritterStubPlayer::collide

Let's eliminate the feature of cGameStub which rewards the player for bumping into a cCritterStubProp critter. This means you should comment out this line from within the lines from thecCritterStubPlayer::collide(cCritter *pcritter) code.

//    setHealth(health() + 1); 

Exercise 3.10.7: Change cCritterStubPlayer::shoot, and the cCritterStubPlayerBullet behavior

  1. In the gamestub.cpp file, try giving yourself prop-seeking missiles for your bullets. Change the code of the cCritterStubPlayer::shoot() as follows.

    That is, give your bullets a cForceObjectSeek so they turn into smart missiles that hunt down whichever critter was closest to the line you aimed along. If you think it makes the game too easy or too hard, leave it out or perhaps use a smaller value for the argument 50.0 passed to the cForceObjectSeek constructor.

    cCritterBullet* cCritterStubPlayer::shoot() 
        cCritterBullet *pbullet = cCritterArmedPlayer::shoot(); 
        cCritter* paimtarget = 
            pgame()->pbiota()->pickClosestAhead (cLine(position(), 
                aimvector()), this); 
            /* Find the critter closest to your aiming line. Including 
                "this" as the second argument means to exclude 
                yourself from consideration as the closest critter. 
                Note that you can use additional params with 
                pickClosestAhead to narrow the angle the critter 
                "sees" and also to limit the possible targets to 
                certain kinds of critters, see biota.h for details. */ 
        pbullet->addForce(new cForceObjectSeek(paimtarget, 50.0)); 
        return pbullet; 
  2. You may now find the game is now hard to play because your bullets die at the screen edges and sometimes do this before hitting a prop. Fix this by adding this line to the cCritterStubPlayerBullet::cCritterStubPlayerBullet() constructor.

    _dieatedges = FALSE; 
  3. A downside of (b) is that you'll now notice that some silly bullets bounce off the top and get confused and bumble around on the bottom of the world. To fix this, you have to override the void cCritterStubPlayerBullet::update(CPopView *pactiveview, Real dt) as follows.

    Add this line to the prototype in gamestub.h.

    virtual void update(CPopView *pactiveview, Realdt); 

    Add this code to gamestub.cpp.

    void cCritterStubPlayerBullet::update(CPopView *pactiveview, Realdt) 
        cCritterBullet::update(pactiveview, dt); 
            //Always call base update first 
        if (_outcode & BOX_LOY || _outcode & BOX_HIY) 
            //Landing damages me 

Exercise 3.10.8: Change the cGameStub::adjustGameParameters

  1. To make this more of a game, it's better to have the action be non-stop. As it presently stands, you can kill off all the attackers. We fix it so each time you kill an attacker, some new ones come in. We do this by adding this code to the bottom of the cGameStub::adjustGameParameters code.

    int propcrittercount = 
        pbiota()- >count(RUNTIME_CLASS(cCritterStubProp)); 
    if (propcrittercount < _seedcount) 
        new cCritterStubProp(this); 
            //The constructor automatically adds the critter to the game. 

    Note that if you've killed, say, three cCritterStubPropsall at once, it will take the game three steps of calling adjustGameParameters to restore the full cCritterStubProp count. This is fine, as visually it's just as well not to change the game too rapidly.

  2. What about making the game get harder as you play it? This step is optional.

    You could keep track of the cGame::score(), and each time this gets larger than some increment size, make the game harder in some way, perhaps by increasing the size of _seedcount.

    Or you could add a cCritterStubRival whenever the score passes a certain size, similar to how the Spacewar game adds cCritterUFO. To make this effective you might need to tweak the cCritterStubRival methods a bit.

Exercise 3.11: Hand in a Space Invaders project

This is an assignment the author usually gives his classes fairly early in the semester. Typically, the credit assigned for the three parts of this problem is in a 25%, 50%, 25% ratio for, respectively, mechanics, basics and improvements.

Even if you're studying this book on your own, you will find it worthwhile to carry through the mechanics steps so as to have experience in putting your code into a form that can be handed off.

Mechanics. Hand in the following: (1) A sheet of paper with a little 'User's Guide' describing the controls of your game and listing any special features you added. (2) Two floppy disks: one disk with a release build of the executable in the root directory, and another disk with clean, minimal-sized, buildable source code. Label the disks with your name and with a word to indicate if this is the EXE or the SOURCE disk. You will probably need to WinZip your source and probably your executable to fit onto floppies. If the *.zip is larger than 1 Meg you probably haven't cleaned your source directory properly (or you've included a lot of extra big sounds and bitmap files). Another option is to write the information onto a CD-ROM. Emailing your homework to the professor as a gigundo attachment is forbidden! (3) Put disks and paper in a two-pocket folder with your name on it. (4) Put your name on the program caption bar. To change the caption bar, see Section 23.9 of this book.

Basics. Carry out the steps outlined in the series of Space Invaders exercises 3.10.1? 3.10.8 just above. You don't necessarily need to use all the exact same parameter values suggested. Get the program working so that the critters are neither too hard nor too easy to hit, the game itself should be neither too hard nor too easy. You may need to tweak some parameters to get it right.

Improvements. Possibilities: change the background, use different kinds of sprites, add code to make the game use levels that get progressively harder, change the code so that the enemies jiggle back and forth like in the traditional Space Invaders rather than running away from bullets. Add sound effects. Have some enemies that shoot at you. Have the enemies change appearance when you hit them before disappearing, maybe have them shatter or show a cSpriteIcon or cSpriteLoop explosion bitmap for a few seconds. Looking at the gamespacewar.cpp or the gamedefender3d.cpp files may provide inspiration.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference