Previous Page TOC Next Page See Page



18


Serious Play: Game Applets


by Jim Morey

What's your game? Maybe that should be, What's your definition of a game? Games play an important role in our society. But what are games?

There are common components to all games: a goal, rules, and a world (or a framework) in which the game is played. The goal of a game drives the players to perform or to achieve. In chess, the goal is to capture the opponent's King. The rules, in a way, are impediments to achieving goals instantly. Often, the rules make the game fair so that anyone playing the game has the same chances at winning. In tic-tac-toe, for example, one rule is that players must alternate turns; otherwise, one player easily would dominate the game and win. The world in which games are played provides a place where both the goal and the rules make sense; it is the ground on which the struggle for the goal can be enacted. As the rules always require, to succeed at the game, players must remain in that world. The world of Monopoly, for example, is a microcosm of the business world, and the players' goal is to achieve financial domination. Players must travel around this world searching for business prospects, acquiring properties, and becoming wealthy. They are given the neighborhoods, currency, and power to achieve that goal. Sometimes these components (goal, rules, world) are difficult to identify, but in varying forms, they always exist. For every game, when players know the rules, understand the world, and want the goal, they can build strategies they can use to play each game.

The ability to build strategies is important for every aspect of life, so this chapter takes the viewpoint that games are a testing ground for life skills. Consider the game of chess as a testing ground for strategy, concentration, and sequencing; whereas the game of poker focuses on the skills of computing probability, managing stress, and judging other players' reactions and behaviors. This viewpoint gives you a common starting point.

What makes a good game? Good games have innumerable qualities to them, as diverse as people's varying talents, and just as impossible to list with any accuracy or completeness. Qualities that make a game good engage players on the following levels:

Creativity--imagination/allusion/aesthetic

A game that is rich with these attributes can be a good game only if this richness is balanced with approachability. Players then can experience the game's richness because it is accessible. This chapter examines some examples of games and their various attributes.

Computer Games


How do these ideas about the concept of games relate to computers? The first step is to examine the medium. Computers are good at manipulating and massaging information, but they have very little tactile input and diminished human interaction. A good computer game must take all this into account. In Solitaire, for example, there is no human interaction, and shuffling and redealing are time consuming. Solitaire therefore is a good example of an ideal game to be computerized. Although the tactile handling of the cards is lost, most people prefer the efficiency of computer shuffling and redealing.

Instead of computerizing an existing game, many games have been designed especially for computers. Descent, a fully 3-D battle game, makes wonderful use of the computer medium. Specifically, the game uses the computer's capability to manipulate 3-D information. Any other medium would have great difficulty creating the world of Descent. A problem with Descent, however, is that it has a complex interface. This detracts from its popularity.

The game Lemmings, on the other hand, makes innovative use of the computer medium with an approachable interface. The concepts in Lemmings break new ground with interface and game design. Players guide a group of lemmings through a maze-like world by assigning a series of jobs to individual lemmings who aide the others in completing the maze. Most other contemporary computer game designs focus on manipulating only one object for success, whereas Lemmings' focus is on a group's success, possibly at the expense of a few individuals. The innovation in the interface is the player's ability to control multiple individuals within one group.

Net Games


Now that home computers have the capacity to use the Internet, there are many innovative possibilities for games. An existing board game that would greatly benefit from a networked computer is Diplomacy, which reenacts the First World War. The players role-play the leaders of important European countries. Because there is no element of chance to the game, players must conspire with other leaders to achieve their individual goals of European domination. Unfortunately, while playing the board game, the players often are aware of who is talking to whom. This greatly affects the trust factor in agreements. Imagine the game now with the assistance of the net's anonymity. Conspiracy and backroom politics are handled with secrecy. Much like the Solitaire example, this game shows how a game can be enhanced by a new medium.

A good example of existing net games is multiuser dungeons (MUDs). These let multiple people interact in one game world. Because these games are mainly text based, they appeal to individuals' imaginations, much like radio programs did before the advent of television. Unfortunately, even though the text in the game can be very appealing for imaginative purposes, the text--as an interface to move around and interact with the world--also detracts from the game because it is complex and cumbersome for beginners to use.

Interface Design


Looking at games now as a designer/programmer, the key concept is the way in which players interact with the world: the interface. A difficult interface makes a game less approachable and also can diminish the fun of play, especially if a lot of the difficulty of the game lies in the interface and not in the content of the game.

A good interface is intuitive and responsive. A good way to test whether an interface is intuitive is to have some noncomputer people play the game with minimal prior instruction, and then see whether they can figure it out. One thing that helps people figure things out is to ensure that the game is responsive to input.

Just as you saw games as a training ground for life skills, a good way to look at computer-game design is as a training ground for interface-design skills. Computers are information tools; the easier it is to use the tool to process the information into a more desirable form, the better the tool is. In the creation of a game, the programmer is free to design innovative interfaces without having the burden of conventional or real-world problems. Considering the richness of games, a revolution in interface design probably will spring from a game interface.

The Game of CopyCat


The CD-ROM included with this book contains an example of a game I developed that has a good interface. The game is called CopyCat, and it, too, has a goal, rules, and a world. The goal of the game is to copy a picture created by several patterned faces of a cube or some other shape. The one rule is that players can slide or roll the shape around only in specific ways. Here, the interface plays a central role, because the complexity of the game springs from the way players must manipulate the shape. The world consists of the picture to be copied, a blank frame where the copy of the picture will be, a shape with the patterned faces with which to make the copy, some regions where the shape can be rolled, and a duplicate shape used for reference.

Looking At Objects and Data Flow


To start off, you will look at the overall workings of the program. The crucial step in programming a game is to be able to identify the objects that are needed; it is about pattern recognition and being able to break down the program into objects by searching for commonalties. Finding the essence of the program often takes a bit of work and usually requires abstract thinking. The pieces I broke the CopyCat game into follow:

TFSolid

Pieces also include your old friends from the preceding chapter:

Omatrix

CopyCat is the applet, and it is the foreman of the program; it gets all the other objects going and then checks on them when needed. PlayArea is a Canvas where the important actions are performed. Most of your interest will be focused on this class. Board is a class that holds all the important information about the Board--the picture to be replicated. BoardBox is the Canvas that displays the desired picture. The GroupGraph holds the geometrical information about the shape/solid. TFSolid stands for Top-Face-Solid, which is a subclass of Solid with special importance to the top face of the solid or shape.

Figure 18.1 shows an overview of how those pieces fit together (after everything is instantiated by CopyCat). I suggest that you play the game before reading the rest of this chapter.

Figure 18.1. The flow of CopyCat.

Three runnable objects are in this program: CopyCat, PlayArea, and Rotator. You already saw what Rotator does, so now take a look at the other two objects. This system actually is event driven, with the threads sleeping most of the time. You can think of CopyCat as a lazy foreman who snoozes at his desk and only wakes up when PlayArea tells him that there has been some event that may be interesting--something has changed. He deals with the event and promptly goes back to sleep. Similarly, PlayArea works on this event-driven idea, except he is more like a fast food cashier because he gets a lot of events: mouse down, mouse up, and mouse drag. He receives all his events through the event handler. The separate thread in PlayArea runs animations during the play; these are used to place the top face of the shape on the game Board. I will go into detail about how this all works, but for now, get a feel for the data flow.

Notice that Rotator and BoardBox are separate from the rest of the program. Rotator is used to display a duplicate of the shape for reference. BoardBox is responsible for displaying the goal--the picture to be copied. Both BoardBox and Rotator are there only to provide static information about the game; the shape doesn't change midway through the game and neither does the goal. So after they are instantiated, they have no contact with the rest of the program. Figure 18.2 shows the visible components of CopyCat: PlayArea, Rotator, and BoardBox.


PlayArea
Rotator
BoardBox***

Figure 18.2. The visible components of CopyCat: PlayArea, Rotator, and BoardBox.

Coordinating CopyCat and PlayArea


Now look at how CopyCat and PlayArea communicate. It is a simple interaction. CopyCat wants to sleep until PlayArea has changedThings. Both CopyCat and PlayArea use Board. Board stores the current state of the Board; PlayArea changes the current state of the Board every time a top face of the shape is placed. Then CopyCat simply checks to see whether the Board is finished by seeing whether Board.finished is true. In CopyCat's run method, shown in Listing 18.1, the line PlayArea.thingsChange(); is really a question: "Have things changed?" The answer to the question is always yes, but that is because PlayArea waits for the answer to be yes before it responds to the request. After CopyCat waits for things to change, it checks to see whether something important has changed. If the number of rotations has changed from what it was before, it updates rotations and displays it to the screen by RecordScore. If the player has completed the pattern, it displays the appropriate Finished message and plays a little tune.

Listing 18.1. The run method from CopyCat.

  /* -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - */
  public void run(){
    rotations=0;
    while(true){
      /* .. wait for things to change .. */
      PlayArea.thingsChange();
      if (rotations != PlayArea.rotations){
        rotations = PlayArea.rotations;
        RecordScore(rotations);
      }
      if (Board.finished && !finished){
        finished = true;
        subtitle.setBackground(new Color(255,150,150));
        if (level1)
          subtitle.title=(String) "Finished ("+rotations+"/"+Board.minRot+")";
        else
          subtitle.title=(String) "Finished ("+rotations+"/?)";
        purrr.play();
        subtitle.centre();
subtitle.repaint();
      }
    }
  }

thingsChange works like a door at a high-school dance, where the organizers want the same number of girls and boys to enter. The bouncers ensure this by only accepting a girl, then a boy, then a girl, and so on. So if the last person let in was a boy, the next boy to come in must wait for a girl to enter. Keeping this analogy in mind, take a look at the code. Suppose that when the variable nochange equals false, it indicates "a girl was the last person let in." When nochange is true, it indicates "a boy was the last person let in." The question thingsChange is like the boy, and changedThings is like the girl. When CopyCat calls PlayArea.thingsChange();, it checks whether the last person admitted was a boy (nochange is true); if so, then it waits/sleeps. If, while CopyCat is sleeping, someone wakes the boy, he checks to see whether a girl has gone through. Eventually, a girl will go through and the boy will be let in. After going in, the nochange variable must be set to false, which shows that the last person through was a boy. Just in case a girl was waiting to come in, this variable wakes up everyone. As it turns out, the counterpart changedThings is exactly the same (see Listing 18.2).

Listing 18.2. The run method from PlayArea.java.

  /* -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - */
  public synchronized void thingsChange() {
    while (nochange == true)
      try{ wait(); } catch (InterruptedException e){}
    nochange = true;
    notifyAll();
  }
  /* -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - */
  public synchronized void changedThings() {
    while (nochange == false)
      try{ wait(); } catch (InterruptedException e){}
    nochange = false;
    notifyAll();
  }

Dealing with Events


How does PlayArea deal with its events? Now that you've finished with the interaction between PlayArea and CopyCat, you start to see that PlayArea is the component that does the real work.

First, you must identify all the events PlayArea knows how to deal with. As in Rotator, mentioned in the preceding chapter, the events dealt with are mouse down, mouse drag, and mouse up. Rotator's methods are much simpler than these, so I encourage you to revisit that chapter if this next section gets a bit too convoluted; a reasonable understanding of Rotator's event handling will be very helpful. It is important to understand the overall goal for the interface so that you can use it as a torch to light your way through some complicated code.

The goal is to have a responsive control to move the shape. The mouse down event is interpreted as an attempt to grab the object. The mouseDown will be successful if the pointer is relatively close to the shape; otherwise, nothing happens. If the shape is under the control of the mouse, the mouseDrag makes sense. This mouseDrag method is fairly intuitive. Dragging the shape simply slides the shape to where the mouse points. The tricky part occurs when the mouse drags the shape over an oriented rough surface. These rough surfaces make the shape roll in a particular direction. The rolling behaves like a ratchet. During a roll, the shape locks into the desired set positions and doesn't allow the user to go back to the previous locked position. The desired position usually has one of the faces pointing upright; this is referred to as the top face. The mouse up event is interpreted as letting go of the shape. The only tricky part to mouseUp is that if the shape is let go in a place on the Board where the top face will fit, the mouseUp method calls up a small animation that shows how the top face is placed on the Board. If this interface outline is a little fuzzy, I suggest that you play the game and pay close attention to the interface.

Now you can break down each of the three methods that handle the interface. The next section starts with the easiest: mouseDown.

mouseDown

In the mouseDown method, the fast-food cashier handles the event as long as an animation isn't running and if the pointer is close enough to the shape. As you will see later, all events are ignored if an animation is running. If the mouse down event is handled, to indicate to the player that the shape now is being held, the shape is raised; the height of the shape becomes slightly bigger. Also, the onSolid flag is set to true. This tells mouseDrag that the shape is within the control of the mouse (see Listing 18.3).

Listing 18.3. The method mouseDown from PlayArea.

../* -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - */
  public boolean mouseDown(java.awt.Event evt, int x, int y) {
    /* .. pick up the solid .. */
    if (!animation_running){
      if (Math.abs(x-solid.cenx)<solid.W && Math.abs(y-solid.ceny)<solid.H){
        onSolid = true;
         height = 0.2;
        return true;
      }
    }
    return false;
  }

mouseDrag

Unfortunately, all the handling of events isn't so simple. The next event handler for PlayArea is mouseDrag, shown in Listing 18.4. If not for the rolling part, the interface would be easy. On the other hand, the rolling is the most important part of the interface and the program.

The drag has several behaviors or modes: TRANSLATE, IN_A, and IN_B. The mode used depends on whether the mouse is over one of the rough surfaces. The TRANSLATE mode is the default mode. Being in this mode means that the object will only slide, because it is not on a rough surface. The orientation of the shape will stay fixed. Being in the IN_A and IN_B modes means that the mouse is over one of the rough surfaces. Dragging the shape over either of the rough surfaces might make the shape roll over. There are two different modes because, depending on which of the two regions the mouse is over, the shape rolls in different directions.

Because this method is very long, I'll explain it in small pieces at a time. Listing 18.4 shows the entire method, along with commentary inserted between sections of code. I'll leave a lot of the mathematical explanations to you, because a discussion of this would bog down an already convoluted method.

Listing 18.4. The method mouseDrag from PlayArea.

/* -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - */
  public boolean mouseDrag(java.awt.Event evt, int x, int y) {
    /* .. move the around the solid -- maybe roll it .. */
    if (onSolid && !animation_running){
      solid.cenx = x;
      solid.ceny = y;

First, to perform the drag, the player must be holding the shape: onSolid must be true. The other condition is that the animations aren't running, so the variable animation_running is false. The next two lines center the shape to where the mouse now is pointing.

      /* .. find the newMode--TRANSLATE, IN_A, or IN_B
            this is used to find out whether the mode has changed .. */
      newMode = TRANSLATE;
      for (int i=0;i<2;i++){
        int chX = (int)((solid.cenx-region[i][0])*vec[i][0]+
             (solid.ceny-region[i][1])*vec[i][1]);
        int chY = (int)((solid.cenx-region[i][0])*vec[i][1]-
             (solid.ceny-region[i][1])*vec[i][0]);
        if (chX>=0 && chX<region[i][2] &&
            chY>=0 && chY<region[i][3]) newMode = i;
      }

This segment of code figures out which region of PlayArea the mouse is on. TRANSLATE, IN_A, and IN_B are public static final variables or constants; they are used to make values of the mode easy to identify. The only purpose is to compute the newMode.

...
      if (mode == TRANSLATE) {
        if (mode != newMode) {
          mode = newMode;
          start_roll = true;
        }

The TRANSLATE mode is the easiest mode, because the shape already follows the mouse's pointer. The only thing it must do is prepare the shape for a roll if the newMode is different from TRANSLATE. The Boolean variable, start_roll, is used as a flag to run some code later. Actually, a better way to handle starting a roll is to construct a method called startRoll. The code that computes the newMode probably should be its own method, too.

      }else {
        /* .. mode = IN_A or IN_B .. */
        if (mode != newMode) {
          /* .. not in roll region anymore .. */
          mode = TRANSLATE;
          if (ang < sign(angle)*angle/2) {
            group.pos = oldPos;
            if (ang>SMALL_ANG) sounds[THUD].play();
          }else {
            sounds[THUD].play();
            rotations++;
            changedThings();
          }
          solid.orient = group.element[group.pos];

This section deals with being on a rough surface. If the newMode is different from the old mode (this means that the mouse is off the rough surface it was on), you must ensure that the shape is in one of the standard positions and not partially rotated. This is one of the underlying properties of the modes; a given mode assumes that the shape has a certain orientation. If the shape is not in one of the fixed positions, you take it to the closest fixed position. This is determined by how close ang is to angle, where ang is the current angle of rotation of the shape relative to the last fixed position, and angle is the amount of rotation needed to get from the last fixed position to the new fixed position. So the question is really, Is ang closer to zero or to angle? If it falls back to the old position, it will only make a THUD noise if the fall was big enough--that is, ang exceeded SMALL_ANG. Otherwise, the shape falls to the new position, the number of rotations is incremented, and CopyCat is informed of the change.

} else {
          /* .. in roll region .. */
          ang = (vec[mode][0]*(x-lastPt[0]) + vec[mode][1]*(y-lastPt[1]))*
                ROLL_VALUE/solid.W;
          if (ang<0) {
            ang = 0;
            lastPt[0] = x;
            lastPt[1] = y;
          }
          if (ang>Math.abs(angle)) { /* .. finished one rotation .. */
            rotations++;
            changedThings();
            sounds[THUD].play();
            solid.orient = group.element[group.pos];
            start_roll = true;
          } else {
            tmp.Rotation(1,2,sign(angle)*ang);
            solid.orient = mid.Times(tmp.Times(mid.Transpose().Times(group.element[oldPos])));
          }
        }
      }

Here, the mode has not changed, which means that the shape is still on a rough surface. The variable ang represents the angle at which the shape will be rotated toward the new position. It depends on how far away the mouse position is from the start of the roll, lastPt, in a certain direction. The interface works like a ratchet; when putting in a bolt, the ratchet is free to turn counterclockwise and the bolt is not turned, but the ratchet locks into the last position it passed when it is turned clockwise, and then the bolt can be turned. Taking this analogy back to the interface, the shape rolls freely when you move the mouse in one direction, analogous to the counterclockwise ratchet. Moving the mouse in the other direction locks the rolling mechanism of the shape, like the locked clockwise-turning ratchet. Here, if ang turns out to be negative, the shape does not rotate; with a positive ang, however, the shape does rotate. After ang reaches angle (the angle of the next position), you must increment the number of rotations, inform CopyCat of the change, make the THUD noise, and start a new roll. Otherwise, you must calculate the orientation of the shape in the intermediate position between the old fixed position and the new fixed position.

if (start_roll){
        oldPos = group.pos;
        group.pos = group.arrows[group.pos][mode];
        angle = mid.FindTran(solid.orient,group.element[group.pos]);
        lastPt[0] = x;
        lastPt[1] = y;
        start_roll = false;
      }

Here, you finally have the code for how to start a new roll. The old fixed position, oldPos, is saved and the new position, group.pos, is figured out. The angle to the new position is calculated, and the matrix mid is computed to help figure out the intermediate positions. Finally, the lastPt is recorded so that it can be used for the calculation of ang.

repaint();
      return true;
    }
    return false;
  }

All the changes then are thrown to the screen with repaint. return true; tells the event handler that the mouseDrag has been handled. It turns out that the only real things you affected in this whole method are the orientation and position of the shape. It is a good idea to keep the event handlers as simple as possible, because these method are called very often. Even though the code looks long, because there are really only three methods in this method, it is fairly fast. Figures 18.3 and 18.4 demonstrate two drags.

Figure 18.3. A drag in the TRANSLATE mode.

Figure 18.4. A drag in the IN_B mode.

mouseUp

The mouseUp method associates releasing the mouse button with letting the shape go. If the shape is let go close enough to a place where it fits on the Board, the top face of the shape is placed on the Board. Otherwise, the shape is placed by changing the height variable. One other case also must be handled; if the player drops the shape outside of PlayArea, it brings the shape back to the initial position. Of course, not taking care of that case would mean that the player would never be able to retrieve the shape, which would really slow down the game.

/* -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - */
  public boolean mouseUp(java.awt.Event evt, int x, int y) {
    /* .. place the pattern down .. */
    if (onSolid && !animation_running){
      /* .. See if it's close enough to a location that is the same type .. */
      onSolid = false;
      height = -0.05;
      if (Board.ClosestFace(x,y,CLOSE_ENOUGH))
        if (group.blank[Board.face[Board.closestface]]==group.blank[group.pos]){
          solid.cenx = Board.vertex[Board.closestface][0];
          solid.ceny = Board.vertex[Board.closestface][1];
          Board.faceTry[Board.closestface] = group.smallestFace[group.pos];
          Board.finished = (Board.nvert == Board.NumCorrect());
          changedThings();
          if (level1) solid.ExplodePrep(group.tilt);
          else solid.StampPrep(group.tilt);
          new Thread(this).start();
        }
      if (x<0 || x>W || y<0 || y>H) {
        goToStart(false);
        repaint();
      }
      return true;
    }
    return false;
  }

If the shape is placed on the Board, there is some bookkeeping to take care of: centering the shape over the Board where the top face will be placed, updating the Board and notifying CopyCat of the change, and starting the animation of placing the top face on the player's copy of the picture. The animations are either an explosion (level 1), where the top face lands on the Board (this takes about 10 frames); or a 20-frame animation (level 2), where the shape flips and stamps the Board, leaving a mirror image imprint of the top face. See Figures 18.5 and 18.6.

Figure 18.5. Dropping the shape where a location not desired.

Figure 18.6. Dropping the shape in a place it fits on the board--the top face drops to the board and the other faces blow away.

Interface and Communication


Computer games take on many different forms, so it is difficult to come up with useful programming tips and examples that would apply to all games. I have two principles that I follow in game design. One is to delegate the responsibilities of managing the game world to simpler objects that communicate with one other. Another is to search for a responsive, intuitive interface--some natural way that players can communicate their will to the game's world. With these ideas for designing an effective interface and an efficient internal representation of the game world, a good game concept can be turned into a good game. Without these designing ideas, however, this same concept easily could turn into one of the many "cr-applets" on the net.

I have illustrated these ideas with the game CopyCat. I broke down CopyCat into its functional parts, which communicate with each other to present and manage the game's world. In general, decomposing a game into usable objects can be very tricky. Deciding where to draw the lines between objects depends on not only the specific original intended uses of the objects, but also on possible future uses. Developing an eye for pattern recognition, along with learning good data structures, will help you develop this skill of decomposition. For beginning game programmers to learn this skill, it is good practice to constantly revise and redesign the internal workings of games to try to push the flexibility of their games. The flexibility of a game is a good measure of how well the internal workings of the game were thought out.

Also, CopyCat has a responsive intuitive interface. The essence of CopyCat's interface is that for every action the player makes in the game, there is a response so that the player knows the computer understood the attempted action. Every time the player "picks up the shape," for example, it raises slightly; the shape lowers when the player lets go of it. The control of the rolling allows for many intermediate positions. Generically, this is like a conversation between the player and the game. The player says, "blah blah blah," and the game has some response. The game may merely nod so that the player can continue talking, knowing that the last statement was understood; or, the game may do something more noticeable, depending on what the player has said. In designing the interface, the programmer is designing a limited language that the player uses to interact or converse with the game. The simpler or more familiar the language, the easier it is for the player to play the game. In CopyCat, the interface is relatively easy because it draws on people's experiences with the physical world: rolling and sliding shapes, ratchets, ink stamps, puzzles, and probably a lot more. This physicality, along with the drag-and-drop idea, makes the interface/language familiar to a lot of people.

With these principles, a sprinkle of the ideas from Chapter 12, "Network Programming with Java," some laser pistols or swords, and a lot of sleepless nights, you could be the creator of the greatest net game to ever be caught in the Web.

Summary


The Web has changed not only the way we view information, but also the way we interact with it. Web games can help us acquire the new life-skills of this information age.

The Java language provides simple but powerful tools for designing professional interfaces. The CopyCat applet shows the ease of dealing with the mouse events to communicate complex messages to the world of the game. Now it is up to you to tame the event handlers and catch some sockets and do some serious playing around.

Previous Page Page Top TOC Next Page See Page