.Netterpillars: Artificial Intelligence and Sprites, Part 2 - Final Version: Coding the Netterpillars AI
(Page 9 of 11 )
To finish your game, you need to code the NetterpillarAI class and make the final adjustments in the Main procedure, as shown in the next sections.
The Netterpillar AI Class
As you decided in the game proposal and in the game project, you only need to use a simple form of artificial intelligence. Just avoid walls and eat mushrooms if they are near, that’s all.
public class AINetterpillar : GameEngine {
private int RandomPercent = 5;
public Sprite.CompassDirections
ChooseNetterpillarDirection
(Point CurrentLocation, Sprite.CompassDirections
CurrentDirection) {…}
public Sprite.CompassDirections
RandomDirection (Point CurrentLocation,
Sprite.CompassDirections ChoosCompassDirections){…}
}
Let’s review what the game objects are.
protected enum GameObjects {
Mushroom = 0,
Empty = 1,
Branch = 2,
Netterpillar = 3
};
Not by accident, when you define this enumeration, you put the game objects in ascending order of collision preference. When you check the objects around you, the lowest value is the preferred one: A mushroom is better than empty space, and both are preferable to a collision resulting in death. You can use this to your advantage, to ease the choice of the best object by checking the lowest value (with the min function) from the positions around the current position of the netterpillar’s head.
BestObject = (GameObjects)Math.Min(Math.Min(Math.Min(
(int)GameField[CurrentLocation.X+1, CurrentLocation.Y],
(int)GameField[CurrentLocation.X-1, CurrentLocation.Y]), (int)GameField[CurrentLocation.X, CurrentLocation.Y+1]), (int)GameField[CurrentLocation.X, CurrentLocation.Y-1]);
Once the best object has been chosen, you can check it against the next object in the current direction; and if they are the same (there can be two or more optimal objects), you choose to stay in the current direction to make the netterpillar’s movement less erratic.
One last step is to add some random behavior to make the movement less predictable and less prone to getting stuck in an infinite loop; for example, the netterpillar could move in circles around the game field forever if there’s no aleatory component. In your tests, anything greater than 10 percent randomness can lead to erratic behavior (remember, you choose a new direction many times a second); a value between 0 and 5 generates good results.
public Sprite.CompassDirections ChooseNetterpillarDirection
(Point CurrentLocation, Sprite.CompassDirections
CurrentDirection) {
Sprite.CompassDirections
ChooseNetterpillarDirection_result =
(Sprite.CompassDirections)0;
GameObjects BestObject;
GameObjects NextObject = (GameObjects)0;
switch(CurrentDirection) {
case Sprite.CompassDirections.East:
NextObject = GameField[CurrentLocation.X+1,
CurrentLocation.Y];
break;
case Sprite.CompassDirections.West:
NextObject = GameField[CurrentLocation.X-1,
CurrentLocation.Y];
break;
case Sprite.CompassDirections.South:
NextObject = GameField[CurrentLocation.X,
CurrentLocation.Y+1];
break;
case Sprite.CompassDirections.North:
NextObject = GameField[CurrentLocation.X,
CurrentLocation.Y-1];
break;
}
//Pick the lowest value - Mushroom or empty.
BestObject = (GameObjects)Math.Min(Math.Min(Math.Min(
(int)GameField[CurrentLocation.X+1, CurrentLocation.Y],
(int)GameField[CurrentLocation.X-1, CurrentLocation.Y]),
(int)GameField[CurrentLocation.X, CurrentLocation.Y+1]),
(int)GameField[CurrentLocation.X, CurrentLocation.Y-1]);
// If the current direction is the best direction, stay
in current direction.
if (NextObject==BestObject) {
ChooseNetterpillarDirection_result = CurrentDirection;
}
else {
// Select the direction of the best object.
if (BestObject == GameField[CurrentLocation.X+1,
CurrentLocation.Y]) {
ChooseNetterpillarDirection_result =
Sprite.CompassDirections.East;
}
else if (BestObject == GameField[CurrentLocation.X-1,
CurrentLocation.Y]){
ChooseNetterpillarDirection_result =
Sprite.CompassDirections.West;
}
else if (BestObject == GameField[CurrentLocation.X,
CurrentLocation.Y+1]){
ChooseNetterpillarDirection_result =
Sprite.CompassDirections.South;
}
else if (BestObject == GameField[CurrentLocation.X,
CurrentLocation.Y-1]){
ChooseNetterpillarDirection_result =
Sprite.CompassDirections.North;
}
}
ChooseNetterpillarDirection_result = RandomDirection
(CurrentLocation, ChooseNetterpillarDirection_result);
return ChooseNetterpillarDirection_result;
}
To code the RandomDirection method, called in the last line of the preceding code, you’ll simply pick a random number from 0 to 100, and if it’s less than the RandomPercent property, choose a new movement direction for the netterpillar. The next code sample presents the full code for this method.
private static Random rand = new Random();
public Sprite.CompassDirections RandomDirection
(Point CurrentLocation, Sprite.CompassDirections
ChoosCompassDirections) {
Sprite.CompassDirections RandomDirection_result;
int x = rand.Next(0, 100);
RandomDirection_result = ChoosCompassDirections;
if (x<RandomPercent) {
switch(ChoosCompassDirections) {
case Sprite.CompassDirections.East:
// Try the other directions.
if (GameField[CurrentLocation.X,
CurrentLocation.Y+1]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.South;
}
else if(GameField[CurrentLocation.X,
CurrentLocation.Y-1]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.North;
}
else if(GameField[CurrentLocation.X-1,
CurrentLocation.Y]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.West;
}
break;
case Sprite.CompassDirections.West:
// Try the other directions.
if (GameField[CurrentLocation.X,
CurrentLocation.Y+1]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.South;
}
else if(GameField[CurrentLocation.X,
CurrentLocation.Y-1]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.North;
}
else if(GameField[CurrentLocation.X+1,
CurrentLocation.Y]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.East;
}
break;
case Sprite.CompassDirections.North:
// Try the other directions.
if (GameField[CurrentLocation.X,
CurrentLocation.Y+1]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.South;
}
else if(GameField[CurrentLocation.X+1,
CurrentLocation.Y]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.East;
}
else if(GameField[CurrentLocation.X-1,
CurrentLocation.Y]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.West;
}
break;
case Sprite.CompassDirections.South:
// Try the other directions.
if (GameField[CurrentLocation.X,
CurrentLocation.Y-1]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.North;
}
else if(GameField[CurrentLocation.X+1,
CurrentLocation.Y]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.East;
}
else if(GameField[CurrentLocation.X-1,
CurrentLocation.Y]<=GameObjects.Empty) {
RandomDirection_result =
Sprite.CompassDirections.West;
}
break;
}
}
return RandomDirection_result;
}Since the code in the GameEngine is intended to take care of the game’s physics (for example, it moves the netterpillars, regardless of whether one is changing direction), you’ll have to put the code for moving the netterpillars based on the AI outside the game engine object; your Main procedure is the best option.
Another valid approach would be to include the AI code inside the Netterpillar object—it’s just a matter of choice: a small number of bigger classes or many smaller ones.
This chapter is from Beginning .NET Game Programming in C# by Ellen Hatton et al. (Apress, 2004, ISBN: 1590593197). Check it out at your favorite bookstore today. Buy this book now.
|
Next: The Main Program: Final Version >>
More ASP.NET Articles
More By Apress Publishing