14.1 The Spacewar game


Let's write up a description of the Spacewar game with sections similar to what one might use for a presentation or homework project about a program's specification and about its design .

The name 'Spacewar' was chosen as an homage to the very first graphical computer game ever. The original SpaceWar was developed for the PDP-1 computer at MIT in 1962! If you do a web search you'll find that modern versions of the classic SpaceWar abound. The game that we are calling Spacewar in the Pop Framework actually resembles the old Asteroids rather than being like the classic SpaceWar, which is a two-player game taking place in the gravity field of a central star. We've left turning our Spacewar into a 'real' SpaceWar game as an open -ended exercise.

Specification

A specification should include (S1) a concept, (S2) a picture (hand-drawn is okay when you're starting out) of the game screen's appearance, (S3) a summary of the user controls, and (S4) an outline of the game play.

(S1) Concept

The concept behind this game is to make something similar to the traditional arcade and home game Asteroids. A ship on the screen tries to avoid polygonal asteroids and bullets and missiles fired by occasional UFOs. The ship can shoot back; some targets split in two and must be shot again.

To make the game a bit fresher, we make the asteroids run away from the bullets fired by the ship. To make the game more physically interesting, we have the asteroids bounce off each other.

(S2) Appearance

The Spacewar game with two UFOs attacking

graphics/14icon01.gif

(S3) Controls

We use the traditional Asteroids 'Spaceship' controls. The Left and Right Arrow keys rotate the ship, and the Up and Down Arrow keys accelerate the ship forward or backwards along its current direction. The spacebar fires bullets in the direction the ship points. We also allow the user to aim the ship and fire by left-clicking the mouse.

(S4) Game play

Whenever you kill all the asteroids, a fresh wave of them appears, and the new asteroids move faster than the last wave. You lose a health point whenever an asteroid bumps you or a UFO bullet hits you. Your own bullets can't hurt you.

You don't get any points when you first shoot an asteroid or a UFO and split it in two; you only get the score when you shoot the smaller pieces and remove them from the screen. Your score value for killing various creatures is the following.

¢ Asteroid

¢ UFO

¢ Green enemy bullet

¢ Blue enemy missile

4

6

4

8

Your player's health is improved by one point for every additional 100 score points you accumulate.

Every time you accumulate 40 more points, a new UFO will appear, provided that no UFO is currently present. The UFOs are randomly chosen to be either regular UFOs which have polypolygon sprites and shoot straight-moving green bullets or smart UFOs which have bitmap sprites and shoot smart missiles that track you. The UFO bullets will bounce off of asteroids they run into.

Design

For a design description we want (D1) a UML diagram and (D2) a draft version of the header file to show the method overrides and the additional class members , if any.

(D1) UML diagram

As it often turns out, our UML diagram will not in fact include every possible class or class relationship that we could mention. A characteristic thing about UML diagrams is that it's hard to fit them on a page! One can of course go to a white board or to a UML design program in order to draw a bigger UML diagram. But it can be self-defeating to make a UML too complete; always remember that the purpose of these images is to communicate the structure of the program in the form of a comprehensible overview . If you have too much information to fit into a single UML diagram, it's a good idea to use more than one of them. A general rule of thumb is that if your UML has any pairs of intersecting lines, then you should split it into more than one diagram.

Our UML class diagram for the Spacewar game appears in Figure 14.1. As is our custom, we use the diamond-ended composition line. Recall that the meaning of this line is that objects of the class type at the 'diamond' end have as members some objects of the class type at the other end of the line.

Figure 14.1. UML diagram for the Spacewar critters

graphics/14fig01.gif

(D2) Draft of header file

Here's a simplified version of the Spacewar game header spacewar.h to show which method overrides and which additional members (in this case one) our classes have.

 class cCritterArmedPlayerSpacewar : public cCritterArmedPlayer //Our      player.  {  public:      cCritterArmedPlayerSpacewar(cGame *pownergame = NULL);      void reset();  };  class cCritterAsteroid : public cCritter  {  public:      cCritterAsteroid(cGame *pownergame = NULL);      virtual int damage(int hitstrength);  };  class cCritterUFO : public cCritterArmedRobot  {  public:      cCritterUFO(cGame *pownergame = NULL);      virtual int damage(int hitstrength);      virtual void update(CPopView *pview); /* override to keep turning          wrap flag off. */  };  class cCritterUFOSmart : public cCritterUFO  {  public:      cCritterUFOSmart(cGame *pownergame = NULL);  };  class cGameSpacewar : public cGame  {  private:      int _lastinvasion score;  public:      cGameSpacewar();      virtual void Serialize(CArchive& ar); /* Override for          _lastinvasionscore. */      virtual void reset();      virtual void adjustGameParameters();      virtual CString statusMessage();      virtual void initializeView(CPopView *pview);      virtual void seedCritters();  }; 

The Spacewar code

At this point, you may want to look at the gamespacewar.cpp code inside Visual Studio. Let's discuss some of the highlights.

The cGameSpacewar constructor makes the _border square and gives it a black background. A cCritterArmedPlayerSpacewar is used so that we can make special adjustments to the player's _health , _newlevelscorestep , _newlevelreward , and so on. Also we want to make sure that our player's sprite uses white lines so as to show up well against the game's black background. The _lastinvasionscore is initialized to 0.

The cGameSpacewar::seedCritters gets rid of any asteroid or bullet critters, leaving any UFOs alone, and adds _seedcount cCritterAsteroid objects with this line.

 for (int i=0; i < _seedcount; i++)      new cCritterAsteroid(this); 

The reason we made the decision to not have seedCritters remove the UFOs was as follows . Suppose you have two asteroids left and you kill one. Killing this asteroid raises your score enough to start up a UFO. If you kill the one remaining asteroid, the program will call the seedCritters method to start a new wave of asteroids. Should you at this time wipe out that one remaining UFO? Our design decision was to leave it, so as to make the game a bit harder for the player. As always, of course, any decision like this needs to be tested by user play.

The game is in the cGameSpacewar::adjustGameParams . The method does three things.

  • Ends the game if the player's health is gone.

  • Reseeds the screen with asteroids if all asteroids and UFOs are dead; also speed the game up when you do a reseed.

  • Adds a new UFO each time the player gets a certain amount of additional score.

Here's the full code for how it works.

 void cGameSpacewar::adjustGameParameters()  {  // (1) End the game if the player is dead      if (!health() && !_gameover) //Player's been killed and game's          not over.      {          _gameover = TRUE;          pplayer()->addScore(_scorecorrection); // So user can reach          _maxscore playSound("Tada");          return;      }  // (2) Reseed the screen if all the asteroids are gone (There may  //  still be UFOs present)      if (_pbiota->count(RUNTIME_CLASS(cCritterAsteroid)) == 0)      { //If the crittercount  bulletcount = 1, then player's alone,        //and the enemies are all gone.          cCritter::MAXSPEED *= SPEEDMULTIPLIER;//Speed the critters up.          if (cCritter::MAXSPEED >                  cGameSpacewar::CRITTERMAXSPEEDLIMIT)              cCritter::MAXSPEED =                  cGameSpacewar::CRITTERMAXSPEEDLIMIT;          seedCritters();      }  // (3) Attack with a UFO if enough score has elapsed and there aren't  // any other UFOs      if (score()  _lastinvasionscore > cGameSpacewar::UFOATTACKSCORE          && //More score brings more UFOs          !_pbiota->count(RUNTIME_CLASS(cCritterUFO)))              //Don't start a new UFO till old ones are gone.      {          playSound("TaDa"); //Warning sound.          if (cRandomizer::pinstance()->              randomBOOL(cGameSpacewar::SMARTUFOPROBABILITY))              new cCritterUFOSmart(this);          else              new cCritterUFO(this);          _lastinvasionscore = score();      }  } 

One trick is that at each new level we generically allow all the critters to move more rapidly by changing the static cCritter::MAXSPEED value. This is about as close as object-oriented programming comes to using a global variable.

Now let's say a bit about the critters. The cCritterArmedPlayerSpacewar is almost the same as a cCritterArmedPlayer . We only have a special child class for it so that we can put some specific initialization code into the constructor (for instance of the _health ).

The constructors for the cCritterAsteroid , the cCritterUFO , and the cCritterUFOSmart do more work. They select special sprites and they attach some forces so as to make the creatures into livelier opponents. The sprites used are, respectively, a cPolygon , a cPolyPolygon , and a cSpriteIcon . Here's what the cCritterAsteroid constructor looks like.

 cCritterAsteroid::cCritterAsteroid(cGame *pownergame):  cCritter(pownergame)  {      setHealth(cCritterAsteroid::HEALTH);          /* One more than the number of times it splits. Splitting is              also capped by the cCritterAsteroid::OVERPOPULATIONCOUNT,              which might be about 10; if there are more than this many              asteroids they don't split when you shoot them. */      setValue(cCritterAsteroid::VALUE);      if (pownergame) //Just to be safe.          setSprite(pgame()->randomSprite(pownergame->spritetype()));              /* Let the game pick the current default sprite. */      randomize(cCritter::MF_VELOCITY  cSprite::MF_RADIUS);      psprite()->setLineColor(cColorStyle::CN_WHITE); /* White edges as          we use black background. */      addForce(new cForceClassEvade(cCritterAsteroid::DARTACCELERATION,          cCritterAsteroid::DARTSPEEDUP, RUNTIME_CLASS(cCritterBullet),              FALSE));              /* cForceClassEvade is an all purpose evading force used                  for evading any class I like.                  The third argument says which class to evade, and                  the fourth argument says whether to evade objects                  that are of classes that are child classes of the                  class to evade. Putting FALSE means I don't evade                  cCritterBulletSilver and CritterBulletSilverMissile                  (as that would be a waste of energy since these                  can't hurt me.)              */      moveToMoveboxEdge();      if (pownergame) /* Then we know we added this to a game so          pplayer() is valid. */          addForce(new cForceObjectSeek(pplayer(),              cCritterAsteroid::CHASEACCELERATION));              /* Force for accelerating towards a critter. */  } 

Although we really just want the asteroid sprite for this critter, we allow the user to set the game sprite type and have the asteroids use that kind of sprite. This feature is present partly because this lets you test out different versions of the game, and partly because it might be interesting later on to change the asteroid's sprites at different game levels.

The only differences between the cCritterUFO and the cCritterUFOSmart are that (a) they have different sprites, (b) a call to setMaxspeed(2*cCritter::MAXSPEED) lets Smart UFOs move twice as fast so they're harder to shoot, and (c) the Smart UFOs shoot cCritterBulletSilverMissile bullets that steer towards the player. The cCritterBulletSilverMissile type was defined in the critterarmed.cpp file; unlike other bullets, these SilverMissiles don't die when they hit the screen edges.

A new style of override for cCritter::damage is used by our enemy critters. The asteroids and the UFOs split in two when hit. The way we work this is to have the damage method shrink the critter and make a copy of it. It would be too confusing to split more than once, so we peg the splitting to the _health value of the critters and start their _health out at 2. Here's how it works for the asteroids.

 int cCritterAsteroid::damage(int hitstrength)  {      if (_shieldflag  recentlyDamaged())          return 0; /* Bail out right here if you were just damaged or              if you were just replicated from a just-damaged guy.              Otherwise a newly replicated critter hit by one of a              fusillade of bullets that split its parent will get              split as well. */      int deathreward = cCritter::damage(hitstrength); /* This is          _value (typically nonzero) you get for killing off the          critter if you did that here. */      playSound("Ding"); //Signal the hit.      if (_health) /* If not dead yet, let's replicate unless there's          too many cCritterAsteroid. */      {          setRadius(radius()/sqrt(2.0));          mutate(cCritter::MF_NUDGE); //Crude way to show a hit.          if (pownerbiota()->count(RUNTIME_CLASS(cCritterAsteroid)) <                  cCritterAsteroid::OVERPOPULATIONCOUNT)              replicate(); /* Ask cBiota to make a copy in next                  updateServiceRequests call. */      }      return deathreward;  } 

The effect of the replicate call is to make a copy of the critter and, when the copy is made, to have it also call a mutate(cCritter::MF_NUDGE) , which has the effect of randomizing its angle, and of moving it a slight random amount.



Software Engineering and Computer Games
Software Engineering and Computer Games
ISBN: B00406LVDU
EAN: N/A
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net