16.1 The Airhockey game


(S1) Concept

The Airhockey game is inspired by the table game of the same name. The user slides the player piece around and tries to knock the puck into the opponent's goal. The opponent, which is run by the computer program, tries to knock the puck into the player's goal. In order to make the game more challenging, neither the player nor the opponent can move across the center line of the playing field.

(S2) Appearance

We show a picture of it below. The ball with the triangular tail is the player, this critter is controlled by moving the mouse. The puck is the round critter, and the enemy is a robot player with an icon based on a photo of the author's face.

The Airhockey game. 'Robot' sprite is a picture of the author


(S3) Controls

Simplicity itself: the user moves the player piece with the mouse and uses the player piece to bump the puck. The faster you move the mouse the harder you can bump the puck.

(S4) Behavior

The robot is aware of the puck, and accelerates towards it. The user or the opponent gets a point for each goal. The game is over when the player or opponent reaches seven points.


We represent the class design by the following UML diagram (Figure 16.1), which shows that the cGameAirhockey owns five special instances of the classes above it, that is, a cCritterHockeyPlayer, a cCritterHockeyRobot, a cCritterHockeyPuck, and two cCritterHockeyGoal instances.

Figure 16.1. UML diagram for the Airhockey game


A non-obvious trick we use is to have each goal be a critter that uses its _ptarget field to be aware of which of the two competing players wants to knock the puck into it, and it awards that player a point whenever a puck goes into it.

We could draw navigation lines from each critter class to the cGameAirhockey, but this would clutter the picture a little too much. We do, however, draw a navigation line from cCritterHockeyGoal to cCritter to emphasize the fact that each goal is going to use its _ptarget field to track the identity of the player who is shooting at it.

The Airhockey code

One special thing we do in this game is to set up all the game's critters inside the cGameAirhockey constructor. We construct the critters in a certain order, making the player first, as is our habit, and then being sure to make the puck before the hockey robot, and being sure not to define the goals until after the player and the hockey robot. The relevant part of cGameAirhockey::cGameAirhockey looks like this.

//Define _pplayer. 
    setPlayer(new cCritterHockeyPlayer(this)); /* Sets the _pplayer 
        field. */ 
//Define _ppuck 
    _ppuck = new cCritterHockeyPuck(this); 
//Define _phockeyrobot. Need to define _ppuck before _phockeyrobot. 
    _phockeyrobot = new cCritterHockeyRobot(this); 
//Define _pmygoal. Need to define _phockeyrobot before _pmygoal 
    _pmygoal = new 
            -GOALRADIUS), //Low point 
        cVector(_border.lox()+GOALOFFSET, GOALRADIUS), //High point 
        GOALTHICKNESS, this); //Thickness 
    _pmygoal->setOpenside(BOX_LOY);/* cCritter wall views the two 
        points you give it in the constructor as the neg and pos 
        sides of the x axis, so it works out that "LOY" in this 
        system is right on the screen. */ 
    _pmygoal->setTarget(_phockeyrobot);/* The guy who gets points when 
        puck goes in pmygoal. */ 
//Define _probotgoal. Need to define _pplayer before _probotgoal 
    _probotgoal = new cCritterHockeyGoal(cVector(_border.hix() 
        cVector(_border.hix()-GOALOFFSET, GOALRADIUS), GOALTHICKNESS, 
    _probotgoal->setOpenside(BOX_HIY); /* cCritter wall views the two 
        points you give it in the constructor as the neg and pos 
        sides of the x axis, so it works out that "HIY" in the this 
        system is left on the screen. */ 
    _probotgoal->setTarget(pplayer());/* The guy who gets points when 
        puck goes in probotgoal. */ 

cCritterHockeyRobot will be the computer-operated opponent for the Airhockey game. Most of its behavior will be produced by adding a cForceObjectSeek(_ppuck), but in its update we'll add another condition: that it move towards its own goal if the puck is between it and its goal.

The cCritterHockeyGoal will inherit from cCritterWall so as to bounce things off its corners and sides in a reasonable way. We will specify that one side of it is 'open'. In the cCritterHockeyGoal::collide, we call cCritterWall::collide, but also do something special if the colliding critter is the puck. If it's the puck we move the puck to the center with a reset call, and we also add a score to the player who is shooting for this goal. We use the _ptarget field of cCritterHockeyGoal to keep track of the identity of the team who is shooting for this goal.

The gameairhockey.h file has some fairly detailed comments on how the code is implemented, so you might want to read that for more information.

The Robot opponent

A key part of a sports game is having a computer-operated opponent that plays at an appropriate level. The robot player should be beatable, and it's better if it appears a bit erratic so that you can't easily predict and outsmart it.

In the Airhockey game, we have the cCritterHockeyRobot constructor give the hockey robot a basic urge to go towards the puck with this line.

addForce(new cForceObjectSeek(ppuck, ROBOTACCELERATION)); 

But this alone isn't enough. A robot that blindly charges the puck is (a) likely to knock the puck into its own goal, and (b) unlikely to accurately aim the puck at the opponent's goal. So the cCritterHockeyRobot::update has some more complicated tricks in it.

void cCritterHockeyRobot::update(CPopView *pactiveview) 
    cCritter::update(pactiveview); /* This sets _acceleration on the 
        basis of the cForceObjectSeek(_ppuck) you added in the 
        constructor. We ALWAYS call the base class update. */ 
    cGameAirhockey *phgame = (cGameAirhockey*)(pgame()); 
        /* This gives us access to all the fields of the game. Note 
            that we need the cast, as pgame() returns a cGame*. */ 
    cCritter *ppuck = phgame->ppuck(); 
    cCritterHockeyGoal *probotgoal = phgame->probotgoal(); 
    Real puckx = ppuck->position().x(); 
    Real robotgoalx = probotgoal->position().x(); 
    Real pucky = ppuck->position().y(); 
    Real robotgoaly = probotgoal->position().y(); 
    cVector togoal = directionTo(probotgoal); 
    cVector topuck = directionTo(ppuck); 
/* If the puck is between robot and the goal go towards the goal, 
    while avoiding hitting the puck into the goal yourself. */ 
    if (_position.x() < puckx && puckx < robotgoalx && 
          (_position.y() > pucky && pucky > robotgoaly || 
           _position.y() < pucky && pucky < robotgoaly) 
        _acceleration = ROBOTACCELERATION * togoal; 
            //Head for the goal 
        if (togoal % topuck > COSINESMALLANGLE && 
            tangent() % topuck > COSINESMALLANGLE) 
            _velocity.turn( (1 & _personality)?PI/2:-PI/2); 
                /* If puck, my goal and me are in a line, 
                and I'm moving towards the puck, 
                then veer left or right. */ 

This code represents only a second-level approximation to good play; with more thought you might think of some better strategies.

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