Chapter 20. Teaching Games to Think

Creating truly engaging games is often a matter of effectively mimicking human thought within the confines of a computer. Because you no doubt want your games to be engaging, you need at least a basic understanding of how to give games some degree of brain power. This hour focuses on understanding the fundamental theories of artificial intelligence and how they can be applied to games. You will hopefully leave this hour with the fundamental knowledge required to begin implementing artificial intelligence strategies in your own games. You will definitely leave this hour with a practical example of how to incorporate simple AI into a program example.

In this hour, you’ll learn:

  • About the basics of artificial intelligence (AI)

  • About the different types of AI used in games

  • How to develop an AI strategy of your own

  • How to put AI to work in a practical example program involving sprites that interact with each other “intelligently”

Understanding Artificial Intelligence

Artificial intelligence (AI) is defined as the techniques used to emulate the human thought process in a computer. This is a pretty general definition for AI, as it should be; AI is a very broad research area—with game-related AI being a relatively small subset of the whole of AI knowledge. The goal in this hour is not to explore every facet of AI because that would easily fill an entire book, but rather to explore the fundamental concepts behind AI as they apply to games.

As you might have already guessed, human thought is no simple process to emulate, which explains why AI is such a broad area of research. Even though there are many different approaches to AI, all of them basically boil down to attempting to make human decisions within the confines of a computer “brain.” Most traditional AI systems use a variety of information-based algorithms to make decisions, just as people use a variety of previous experiences and mental rules to make a decision. In the past, the information-based AI algorithms were completely deterministic, which means that every decision could be traced back to a predictable flow of logic. Figure 20.1 shows an example of a purely logical human thought process. Obviously, human thinking doesn’t work this way at all; if we were all this predictable, it would be quite a boring planet!

A completely logical human thought process involves nothing more than reason.

Figure 20.1. A completely logical human thought process involves nothing more than reason.

Eventually, AI researchers realized that the deterministic approach to AI wasn’t sufficient to accurately model human thought. Their focus shifted from deterministic AI models to more realistic AI models that attempted to factor in the subtle complexities of human thought, such as best-guess decisions. In people, these types of decisions can result from a combination of past experience, personal bias, or the current state of emotion—in addition to the completely logical decision making process. Figure 20.2 shows an example of this type of thought process. The point is that people don’t always make scientifically predictable decisions based on analyzing their surroundings and arriving at a logical conclusion. The world would probably be a better place if we did act like this, but again, it would be awfully boring!

A more realistic human thought process adds emotion and a dash of irrationality with reason.

Figure 20.2. A more realistic human thought process adds emotion and a dash of irrationality with reason.

The logic flow in Figure 20.1 is an ideal scenario in which each decision is made based on a totally objective logical evaluation of the situation. Figure 20.2 shows a more realistic scenario, which factors in the emotional state of the person, as well as a financial angle (the question of whether the person has insurance). Examining the second scenario from a completely logical angle, it makes no sense for the person to throw the hammer because that only slows down the task at hand. However, this is a completely plausible and fairly common human response to pain and frustration. For an AI carpentry system to effectively model this situation, there would definitely have to be some hammer throwing code in there somewhere!

This hypothetical thought example is meant to give you a tiny clue as to how many seemingly unrelated things go into forming a human thought. Likewise, it only makes sense that it should take an extremely complex AI system to effectively model human thought. Most of the time, this statement is true. However, the word “effectively” allows for a certain degree of interpretation, based on the context of the application requiring AI. For your purposes, effective AI simply means AI that makes computer game objects more realistic and engaging.

More recent AI research has been focused at tackling problems similar to the ones illustrated by the hypothetical carpentry example. One particularly interesting area is fuzzy logic, which attempts to make “best-guess” decisions rather than the concrete decisions of traditional AI systems. Another interesting AI research area in relation to games is genetic algorithms, which try to model evolved thought similarly to how scientists believe nature evolves through genetics. A game using genetic algorithms would theoretically have computer opponents that learn as the game progresses, providing the human player with a seemingly never ending series of challenges.

Exploring Types of Game AI

There are many different types of AI systems and even more specific algorithms that carry out those systems. Even when you limit AI to the world of games, there is still a wide range of information and options from which to choose when it comes to adding AI to a game of your own. Many different AI solutions are geared toward particular types of games—with a plethora of different possibilities that can be applied in different situations.

What I’m getting at is that there is no way to just present a bunch of AI algorithms and tell you which one goes with which particular type of game. Rather, it makes more sense to give you the theoretical background on a few of the most important types of AI, and then let you figure out how they might apply to your particular gaming needs. Having said all that, I’ve broken game-related AI down into three fundamental types:

  • Roaming AI

  • Behavioral AI

  • Strategic AI

Please understand that these three types of AI are in no way meant to encompass all the AI approaches used in games; they are simply the most common types to recognize and use. Feel free to do your own research and expand on these if you find AI to be an interesting topic worthy of further study.

Roaming AI

Roaming AI refers to AI that models the movement of game objects—that is, the decisions game objects make that determine how they roam around a virtual game world. A good example of roaming AI is in shoot-em-up space games such as the classic arcade game Galaga, where aliens often tend to track and go after the player. Similarly, aliens that fly around in a predetermined pattern are also implemented using roaming AI. Basically, roaming AI is used whenever a computer-controlled object must make a decision to alter its current path—either to achieve a desired result in the game or simply to conform to a particular movement pattern. In the Galaga example, the desired result is following a pattern while also attempting to collide with and damage the player’s ship.

Implementing roaming AI is usually fairly simple; it typically involves altering an object’s velocity or position (the alien) based on the position of another object (the player’s ship). The roaming movement of the object can also be influenced by random or predetermined patterns. Three different types of roaming AI exist: chasing, evading, and patterned. The next few sections explore these types of roaming AI in more detail.

Chasing

Chasing is a type of roaming AI in which a game object tracks and goes after another game object or objects. Chasing is the approach used in many shoot-em-up games, where an alien chases after the player’s ship. It is implemented by altering the alien’s velocity or position based on the current position of the player’s ship. Following is an example of a simple chasing algorithm involving an alien and a ship:

if (iXAlien > iXShip)
  iXAlien--;
else if (iXAlien < iXShip)
  iXAlien++;
if (iYAlien > iYShip)
  iYAlien--;
else if (iYAlien < iYShip)
  iYAlien++;

As you can see, the XY position (iXAlien and iYAlien) of the alien is altered based on where the ship is located (iXShip and iYShip). The only potential problem with this code is that it could work too well; the alien will hone in on the player with no hesitation, basically giving the player no chance to dodge it. This might be what you want, but more than likely, you want the alien to fly around a little while it chases the player. You probably also want the chasing to be a little imperfect, giving the player at least some chance of out-maneuvering the alien. In other words, you want the alien to have a tendency to chase the player without going in for an all-out blitz.

One method of smoothing out the chasing algorithm is to throw a little randomness into the equation, like this:

if ((rand() % 3) == 0) {
  if (iXAlien > iXShip)
    iXAlien--;
  else if (iXAlien < iXShip)
    iXAlien++;
}
if ((rand() % 3) == 0) {
  if (iYAlien > iYShip)
    iYAlien--;
  else if (iYAlien < iYShip)
    iYAlien++;
}

In this code, the alien has a one in three chance of tracking the player in each direction. Even with only a one in three chance, the alien will still tend to chase the player aggressively, while allowing the player a fighting chance at getting out of the way. You might think that a one in three chance doesn’t sound all that effective, but keep in mind that the alien only alters its path to chase the player. A smart player will probably figure this out and change directions frequently.

If you aren’t too fired up about the random approach to leveling off the chase, you probably need to look into patterned movement. But you’re getting a little ahead of yourself; let’s take a look at evading first.

Evading

Evading is the logical counterpart to chasing; it is another type of roaming AI in which a game object specifically tries to get away from another object or objects. Evading is implemented in a similar manner to chasing, as the following code shows:

if (iXAlien > iXShip)
  iXAlien++;
else if (iXAlien < iXShip)
  iXAlien--;
if (iYAlien > iYShip)
  iYAlien++;
else if (iYAlien < iYShip)
  iYAlien--;

This code basically does the opposite of the code used by the chasing algorithm—with the only differences being the unary operators (++, --) used to change the alien’s position so that it runs away, as opposed to chasing. Similar to chasing, evading can be softened using randomness or patterned movement. A good example of evading is the ghosts in the classic arcade game Pac Man, who run away from the player when you eat a power pellet. Of course, the Pac-Man ghosts also take advantage of chasing when you don’t have the ability to eat them.

Another good example of using the evading algorithm would be a computer-controlled version of the player’s ship in a space game with a computer player. If you think about it, the player is using the evading algorithm to dodge the aliens; it’s just implemented by hitting keys rather than in a piece of computer-controlled code. If you want to provide a demo mode in a game like this where the computer plays itself, you would use an evading algorithm to control the player’s ship. Hour 23, “Showing Off Your Game with Demo Mode,” shows you exactly how to create a demo mode for your games.

Patterned Roaming

Patterned movement refers to a type of roaming AI that uses a predefined set of movements for a game object. Good examples of patterned movement are the aliens in the classic Galaga arcade game, which perform all kinds of neat aerobatics on their way down the screen. Patterns can include circles, figure eights, zigzags, or even more complex movements. An even simpler example of patterned movement is the Space Invaders game, where a herd of aliens slowly and methodically inch across and down the screen.

Note

Patterned Roaming

In truth, the aliens in Galaga use a combined approach of both patterned and chasing movement; although they certainly follow specific patterns, the aliens still make sure to come after the player whenever possible. Additionally, as the player moves into higher levels, the roaming AI starts favoring chasing over patterned movement in order to make the game harder. This is a really neat usage of combined roaming AI. This touches on the concept of behavioral AI, which you learn about in the next section.

Patterns are usually stored as an array of velocity or position offsets (or multipliers) that are applied to an object whenever patterned movement is required of it, like this:

int iZigZag[2][2] = { {3, 2}, {-3, 2} };
iXAlien += iZigZag[iPatternStep][0];
iYAlien += iZigZag[iPatternStep][1];

This code shows how to implement a very simple vertical zigzag pattern. The integer array iZigZag contains pairs of XY offsets used to apply the pattern to the alien. The iPatternStep variable is an integer representing the current step in the pattern. When this pattern is applied, the alien moves in a vertical direction at a speed of 2 pixels per game cycle, while zigzagging back and forth horizontally at a speed of 3 pixels per game cycle.

Behavioral AI

Although the types of roaming AI strategies are pretty neat in their own right, a practical gaming scenario often requires a mixture of all three. Behavioral AI is another fundamental type of gaming AI that often uses a mixture of roaming AI algorithms to give game objects specific behaviors. Using the trusted alien example again, what if you want the alien to chase some times, evade other times, follow a pattern still other times, and maybe even act totally randomly every once in a while? Another good reason for using behavioral AI is to alter the difficulty of a game. For example, you could favor a chasing algorithm more than random or patterned movement to make aliens more aggressive in higher levels of a space game.

To implement behavioral AI, you would need to establish a set of behaviors for the alien. Giving game objects behaviors isn’t too difficult, and usually involves establishing a ranking system for each type of behavior present in the system, and then applying it to each object. For example, in the alien system, you would have the following behaviors: chase, evade, fly in a pattern, and fly randomly. For each different type of alien, you would assign different percentages to the different behaviors, thereby giving them each different personalities. For example, an aggressive alien might have the following behavioral breakdown: chase 50% of the time, evade 10% of the time, fly in a pattern 30% of the time, and fly randomly 10% of the time. On the other hand, a more passive alien might act like this: chase 10% of the time, evade 50% of the time, fly in a pattern 20% of the time, and fly randomly 20% of the time.

This behavioral approach works amazingly well and yields surprising results considering how simple it is to implement. A typical implementation simply involves a switch statement or nested if-else statements to select a particular behavior. A sample implementation for the behavioral aggressive alien would look like this:

int iBehavior = abs(rand() % 100);
if (iBehavior < 50)
  // chase
else if (iBehavior < 60)
  // evade
else if (iBehavior < 90)
  // fly in a pattern
else
  // fly randomly

As you can see, creating and assigning behaviors is open to a wide range of creativity. One of the best sources of ideas for creating game object behaviors is the primal responses common in the animal world (and unfortunately all too often in the human world, too). As a matter of fact, a simple fight or flight behavioral system can work wonders when applied intelligently to a variety of game objects. Basically, just use your imagination as a guide and create as many unique behaviors as you can dream up.

Strategic AI

The final fundamental type of game AI I want to mention is strategic AI, which is basically any AI designed to play a game with a fixed set of well-defined rules. For example, a computer-controlled chess player would use strategic AI to determine each move based on trying to improve the chances of winning the game. Strategic AI tends to vary more based on the nature of the game simply because it is so tightly linked to the rules of the game. Even so, there are established and successful approaches to applying strategic AI to many general types of games, such as games played on a rectangular board with pieces. Checkers and chess immediately come to mind as fitting into this group, and likewise have a rich history of AI research devoted to them.

Strategic AI, especially for board games, typically involves some form of look-ahead approach to determining the best move to make. The look-ahead is usually used in conjunction with a fixed table of predetermined moves. For a look-ahead to make sense, however, there must be a method of looking at the board at any state and calculating a score. This is known as weighting, and is often the most difficult part of implementing strategic AI in a board game. As an example of how difficult weighting can be, watch a game of chess or checkers and try to figure out who is winning after every single move. Then go a step further and think about trying to calculate a numeric score for each player at each point in the game. Obviously, near the end of the game it gets easier, but early on it is very difficult to tell who is winning simply because there are so many different things that can happen. Attempting to quantify the state of the game in a numeric score is even more difficult.

Nevertheless, there are ways to successfully calculate a weighted score for strategic games. Using a look-head approach with scoring, a strategic AI algorithm can test, for every possible move for each player, multiple moves into the future and determine which move is the best. This move is often referred to as the “least worst” move rather than the best because the goal typically is to make the move that helps the other player the least rather than the other way around. Of course, the end result is basically the same, but it is an interesting way to look at a game, nevertheless. Even though look-ahead approaches to implementing strategic AI are useful in many cases, they can require a fair amount of processing if very much depth is required (in other words, if the computer player needs to be very smart).

To better understand strategic AI, consider the case of a computer Backgammon player. The computer player has to choose two or four moves from possibly several dozen, as well as decide whether to double or resign. A practical Backgammon program might assign weights to different combinations of positions and calculate the value of each position reachable from the current position and dice roll. A scoring system would then be used to evaluate the worth of each potential position, which gets back to the often difficult proposition of scoring, even in a game such as Backgammon, with simple rules. Now apply this scenario to a hundred-unit war game—with every unit having unique characteristics, and the terrain and random factors complicating the issue still further. The optimal system of scoring simply cannot be determined in a reasonable amount of time, especially with the limited computing power of a workstation or PC.

The solution in these cases is to settle for a “good enough” move, rather than the “best” move. One of the best ways to develop the algorithm for finding the “good enough” move is to set up the computer to play both sides in a game, using a lot of variation between the algorithms and weights playing each side. Then sit back and let the two computer players battle it out and see which one wins the most. This approach typically involves a lot of tinkering and trial-and-error with the AI code, but it can result in very good computer players.

Developing an AI Strategy

Now that you understand the basic concepts behind AI in games, you can start thinking about an AI strategy for your own specific games. When deciding how to implement AI in a game, you need to do some preliminary work to assess exactly what type and level of AI you think is warranted. You need to determine what level of computer response suits your needs, abilities, resources, and project time frame.

If your main concern is developing a game that keeps human players entertained and challenged, go with the simplest AI possible. Actually, try to go with the simplest AI regardless of your goals because you can always enhance it in phases. If you think your game needs a type of AI that doesn’t quite fit into any I’ve described, do some research and see whether something out there is closer to what you need. Most importantly, budget plenty of time for implementing AI because 90% of the time, it will take longer than you ever anticipated to get it all working at a level you are happy with.

What is the best way to get started? Start in small steps, of course. Many programmers like to write code as they design, and although that approach might work in some cases, I recommend at least some degree of preliminary design on paper. Furthermore, try to keep this design limited to a subset of the game’s AI, such as a single computer opponent. Start with a small, simple map or grid and simple movement rules. Write the code to get a single opponent from point A to point B. Then add complications piece by piece, building onto a complete algorithm at each step. If you are careful to make each piece of the AI general and open enough to connect to other pieces, your final algorithms should be general enough to handle any conditions your game might encounter.

Getting back to more basic terms, a good way to build AI experience is to write a computer opponent for a simple board game, such as tic-tac-toe or checkers. Detailed AI solutions exist for many popular games, so you should be able to find them if you search around a little on the Web. Another good way to get some experience with AI is to modify an existing game in an attempt to make its computer-controlled characters a little smarter. For example, you could modify the Henway game so that the cars speed up and slow down deliberately to make it tougher on the chicken. You could also change the speed of the moving guys in the Battle Office game so that they speed up when you get close to shooting them.

Building the Roids 2 Program Example

Rather than modify an existing game to demonstrate AI programming, I decided that it would be better to demonstrate AI within the context of a program example that doesn’t involve an objective. In other words, I wanted to create a program that you could tinker with without worrying about messing up the outcome of a game. The program I’m talking about is called Roids 2, and it’s a revamped version of the Roids program from Hour 18. If you recall, the original Roids program displayed an animated asteroid field. You’re now going to add a flying saucer to the program that is intelligent enough to dodge the asteroids, or at least do its best to dodge the asteroids.

The Roids 2 program example is very similar to the original Roids program, except for the addition of the flying saucer sprite. The remainder of the hour focuses on the development of this program, and how AI influences the flying saucer sprite.

Writing the Program Code

The Roids 2 program begins with the Roids.h header file, which declares global variables that are important to the program. More specifically, a flying saucer bitmap has been declared, along with sprites for the asteroids and the flying saucer:

Bitmap*           _pSaucerBitmap;
Sprite*           _pAsteroids[3];
Sprite*           _pSaucer;

The _pSaucerBitmap is a bitmap for the flying saucer image. The _pAsteroids and _pSaucer variables both store sprite pointers. These pointers are necessary so that you can compare the positions of the saucer and asteroids and alter the saucer’s velocity; this is how you add “intelligence” to the flying saucer.

The Roids 2 program also includes a new helper function named UpdateSaucer(), which is responsible for updating the saucer sprite. Of course, the saucer sprite is already being updated in terms of its position and velocity in the game engine. However, in this case an additional update is taking place that alters the saucer’s velocity based on its proximity to nearby asteroids. You learn exactly how this facet of the program works a little later in the hour.

The GameStart() function is similar to the previous version, except that it now contains code to initialize the flying saucer. Listing 20.1 shows the code for this function.

Example 20.1. The GameStart() Function Initializes the Flying Saucer Bitmap and Sprite

 1: void GameStart(HWND hWindow)
 2: {
 3:   // Seed the random number generator
 4:   srand(GetTickCount());
 5:
 6:   // Create the offscreen device context and bitmap
 7:   _hOffscreenDC = CreateCompatibleDC(GetDC(hWindow));
 8:   _hOffscreenBitmap = CreateCompatibleBitmap(GetDC(hWindow),
 9:     _pGame->GetWidth(), _pGame->GetHeight());
10:   SelectObject(_hOffscreenDC, _hOffscreenBitmap);
11:
12:   // Create and load the asteroid and saucer bitmaps
13:   HDC hDC = GetDC(hWindow);
14:   _pAsteroidBitmap = new Bitmap(hDC, IDB_ASTEROID, _hInstance);
15:   _pSaucerBitmap = new Bitmap(hDC, IDB_SAUCER, _hInstance);
16:
17:   // Create the starry background
18:   _pBackground = new StarryBackground(500, 400);
19:
20:   // Create the asteroid sprites
21:   RECT    rcBounds = { 0, 0, 500, 400 };
22:   _pAsteroids[0] = new Sprite(_pAsteroidBitmap, rcBounds, BA_WRAP);
23:   _pAsteroids[0]->SetNumFrames(14);
24:   _pAsteroids[0]->SetFrameDelay(1);
25:   _pAsteroids[0]->SetPosition(250, 200);
26:   _pAsteroids[0]->SetVelocity(-3, 1);
27:   _pGame->AddSprite(_pAsteroids[0]);
28:   _pAsteroids[1] = new Sprite(_pAsteroidBitmap, rcBounds, BA_WRAP);
29:   _pAsteroids[1]->SetNumFrames(14);
30:   _pAsteroids[1]->SetFrameDelay(2);
31:   _pAsteroids[1]->SetPosition(250, 200);
32:   _pAsteroids[1]->SetVelocity(3, -2);
33:   _pGame->AddSprite(_pAsteroids[1]);
34:   _pAsteroids[2] = new Sprite(_pAsteroidBitmap, rcBounds, BA_WRAP);
35:   _pAsteroids[2]->SetNumFrames(14);
36:   _pAsteroids[2]->SetFrameDelay(3);
37:   _pAsteroids[2]->SetPosition(250, 200);
38:   _pAsteroids[2]->SetVelocity(-2, -4);
39:   _pGame->AddSprite(_pAsteroids[2]);
40:
41:   // Create the saucer sprite
42:   _pSaucer = new Sprite(_pSaucerBitmap, rcBounds, BA_WRAP);
43:   _pSaucer->SetPosition(0, 0);
44:   _pSaucer->SetVelocity(3, 1);
45:   _pGame->AddSprite(_pSaucer);
46: }

The changes to the GameStart() function primarily involve the addition of the flying saucer sprite. The saucer bitmap is first loaded (line 15), and then the saucer sprite is created and added to the game engine (lines 42–45). It’s also worth pointing out that the asteroid sprite pointers are now being stored in the _pAsteroids array (lines 22, 28, and 34) because you need to reference them later when helping the saucer avoid hitting the asteroids.

The GameCycle() function in Roids 2 requires a slight modification to ensure that the flying saucer sprite is updated properly. This change involves the addition of a call to the UpdateSaucer() function, which is responsible for updating the velocity of the flying saucer sprite to help it dodge the asteroids.

Speaking of the UpdateSaucer() function, its code is shown in Listing 20.2.

Example 20.2. The UpdateSaucer() Function Updates the Flying Saucer’s Velocity to Help It Dodge Asteroids

 1: void UpdateSaucer()
 2: {
 3:   // Obtain the saucer's position
 4:   RECT rcSaucer, rcRoid;
 5:   rcSaucer = _pSaucer->GetPosition();
 6:
 7:   // Find out which asteroid is closest to the saucer
 8:   int iXCollision = 500, iYCollision = 400, iXYCollision = 900;
 9:   for (int i = 0; i < 3; i++)
10:   {
11:     // Get the asteroid position
12:     rcRoid = _pAsteroids[i]->GetPosition();
13:
14:     // Calculate the minimum XY collision distance
15:     int iXCollisionDist = (rcSaucer.left +
16:       (rcSaucer.right - rcSaucer.left) / 2) -
17:       (rcRoid.left +
18:       (rcRoid.right - rcRoid.left) / 2);
19:     int iYCollisionDist = (rcSaucer.top +
20:       (rcSaucer.bottom - rcSaucer.top) / 2) -
21:       (rcRoid.top +
22:       (rcRoid.bottom - rcRoid.top) / 2);
23:     if ((abs(iXCollisionDist) < abs(iXCollision)) ||
24:       (abs(iYCollisionDist) < abs(iYCollision)))
25:       if ((abs(iXCollisionDist) + abs(iYCollisionDist)) < iXYCollision)
26:       {
27:         iXYCollision = abs(iXCollision) + abs(iYCollision);
28:         iXCollision = iXCollisionDist;
29:         iYCollision = iYCollisionDist;
30:       }
31:   }
32:
33:   // Move to dodge the asteroids, if necessary
34:   POINT ptVelocity;
35:   ptVelocity = _pSaucer->GetVelocity();
36:   if (abs(iXCollision) < 60)
37:   {
38:     // Adjust the X velocity
39:     if (iXCollision < 0)
40:       ptVelocity.x = max(ptVelocity.x - 1, -8);
41:     else
42:       ptVelocity.x = min(ptVelocity.x + 1, 8);
43:   }
44:   if (abs(iYCollision) < 60)
45:   {
46:     // Adjust the Y velocity
47:     if (iYCollision < 0)
48:       ptVelocity.y = max(ptVelocity.y - 1, -8);
49:     else
50:       ptVelocity.y = min(ptVelocity.y + 1, 8);
51:   }
52:
53:   // Update the saucer to the new position
54:   _pSaucer->SetVelocity(ptVelocity);
55: }

I realize that this function contains a lot of code, but if you take it a section at a time, it’s really not too complex. First of all, let’s understand how the UpdateSaucer() function is helping the flying saucer to dodge the asteroids. The idea is to check for the closest asteroid in relation to the saucer, and then alter the saucer’s velocity so that it has a tendency to move in the opposite direction of the asteroid. I say “tendency” because you don’t want the saucer to jerk and immediately start moving away from the asteroid. Instead, you gradually alter its velocity so that it appears to steer away from the asteroid. This is a subtle difference, but the effect is dramatic because it looks as if the saucer is truly steering through the asteroid field.

The first step in the UpdateSaucer() function is to obtain the position of the flying saucer (line 5). You can then loop through the asteroids and find out the minimum X and Y collision distance (lines 8 and 9), which is the closest distance between an asteroid and the saucer. Inside the loop, the asteroid position is first obtained (line 12), which is critical for determining the collision distance. The minimum XY collision distance is then calculated (lines 15–22) and used as the basis for determining if this asteroid is currently the closest one to the saucer. This is where the function gets a little tricky because you must add the X and Y components of the collision distance to see which asteroid is closer to the saucer (lines 23–30). This technique isn’t flawless, but it helps to eliminate “false alarm” situations in which an asteroid is close to the saucer horizontally but far away vertically.

When the asteroid loop is exited, you have two pieces of important information to work with: the X collision distance and the Y collision distance. It’s now possible to check and see if these distances are below a certain minimum distance that is required in order for the saucer to be in danger of colliding with an asteroid. My own trial-and-error testing led to a value of 60, but you might decide on a slightly different value in your own testing. In order to steer the saucer to safety horizontally, the X collision distance is checked to see if it is below the minimum distance of 60; in which case, the saucer’s velocity is adjusted (lines 36–43). The same process is then repeated for the saucer’s Y collision distance (lines 44–51). Finally, the new velocity of the saucer is set by calling the SetVelocity() method on the saucer sprite (line 54).

Testing the Finished Product

The premise behind the Roids 2 program is to show how a certain degree of AI can be injected into a sprite object so that it can avoid other sprite objects. In this context, the evading sprite object is a flying saucer that is desperately trying to keep from colliding with asteroids in an asteroid field. Although you have to actually run the program yourself to see the flying saucer evade the asteroids, Figure 20.3 shows the saucer in the process of steering away from a close call.

The flying saucer in the Roids 2 program does its best to dodge the asteroids that are floating around the game screen.

Figure 20.3. The flying saucer in the Roids 2 program does its best to dodge the asteroids that are floating around the game screen.

This is one of those rare program examples that is actually fun to just sit back and watch because the program appears to have a mind of its own. In other words, you’ve created the logic for the flying saucer, and now you get to see how it responds in different situations. This is the part of the process of developing AI in games that is often frustrating because objects can do strange things that are quite unexpected at times. Even so, the flying saucer does a pretty good job of avoiding asteroids in the Roids 2 program.

Summary

If you find artificial intelligence to be a fascinating area of computer research, you hopefully enjoyed this hour. You learned about the three fundamental types of game AI (roaming, behavioral, and strategic), along with how they are used in typical gaming scenarios. As a game programmer with at least a passing interest in AI, your AI knowledge will likely grow a great deal as you encounter situations in which you can apply AI techniques. After you get comfortable with implementing the basics, you can move on to more advanced AI solutions based upon prior experience and research on the Web. I hope this hour at least provided you with a roadmap to begin your journey into the world of the computer mind.

The next hour applies your newfound AI knowledge to the most ambitious game in the book. The game is called Space Out, and it’s a space shoot-em-up with a quirky cast of characters.

Q&A

Q1:

Everyone acts like computers are so smart, but you’ve made it sound like they’re dumb. What gives?

A1:

Computers, in fact, are very “dumb” when it comes to what we humans refer to as thought. However, computers are very “smart” when it comes to mathematical calculations and algorithms. The trick with AI is to model the subtleties of human thought in such a way that the computer can do what it’s good at, executing mathematical calculations and algorithms. The point is that thoughts we take for granted are often very difficult for computers simply because human thought takes into account an incredibly large number of subtle variables when arriving at even the simplest decision.

Q2:

If my game is designed to have only human players, do I even need to worry with AI?

A2:

Even though games with all human players might appear to not require any AI at first, it is often useful to control many of the background aspects of the game using simple AI. For example, consider a two player head-to-head space battle game. Even though you might not have any plans for computer ships, consider adding some AI to determine how the environment responds to the players’ actions. For example, add a black hole near the more aggressive player from time to time, providing that player with more hassles than the other player. Although the intelligence required of a black hole is pretty weak by most AI standards, it could still use a simple chase algorithm to follow the player around.

Workshop

The Workshop is designed to help you anticipate possible questions, review what you’ve learned, and begin learning how to put your knowledge into practice. The answers to the quiz can be found in Appendix A, “Quiz Answers.”

Quiz

1:

What are the three types of roaming AI?

2:

How does behavioral AI work?

3:

In regard to strategic AI, what do the terms look-ahead and weighting mean?

Exercises

  1. Play some video games with computer opponents and see whether you can tell which type of AI approach is being used.

  2. Tinker with the AI code in the Roids 2 program example to see how changes to the minimum collision distance affect the movement of the flying saucer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.228