Introduction to AI Programming for Games

Update: We have an updated version of this tutorial for iOS 9 and GameplayKit here. When you make a game, you often have enemies for the player to combat. You want these enemies to seem intelligent and present a challenge to the player to keep the game fun and engaging. You can do this through […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

An Improved Strategy

So far you actually have a fairly playable game, so if this was good enough for what you wanted, you could quit at this point!

However, I have a very easy strategy to win that works every time for me:

  • Stay on defense.
  • Wait until the AI builds something, and build 2 Zaps.
  • Build 2 more Zaps.
  • Build a Munch.
  • Switch to attack, and keep building quirks till you win!

Let's see if we can make this AI a little bit smarter.

The problem is you're basically doing a "defend and counter to gain money advantage", followed by a "mass and overwhelm" strategy. Wouldn't it be great if you could develop an AI smart enough do this as well?

You can definitely do this, but you're going to need to use a new AI concept to help you out with this - Finite State Machines!

Introducing Finite State Machines

There are many ways to implement Finite State Machines, but at the simplest form they are quite simple:

  • Keep track of what "state" you're in
  • Have conditions that say when you switch from one "state" to another
  • Then do different things based on what state you are in!

To make the AI in this game smarter you are going to develop four different states:

  1. Defending: While the base is under attack, build units to counter the human wisely.
  2. Rushing: Send everything you've got to attack, and send quirks to Support!
  3. Massing: Save up money to mass the pesky human later.
  4. Countering: Wait for the dumb human to attack your base so you can counter with overwhelming intelligence, and follow-up with a counterattack.

Whenever I work with state machines, I find it really helpful to draw a diagram that shows how the states can transition between each other. Here's the diagram for this game:

AI States in Monster Wars

Here are a few notes on the state transitions:

  • In general, this game's states are in a big circle: Defending -> Rushing -> Massing -> Countering. However, any state switches to Defending if the base is under attack.
  • As soon as the base is no longer under attack, it switches from Defending to Rushing.
  • As soon as the AI's army is destroyed, it switches from Rushing to Massing.
  • As soon as the AI saves up enough money, it switches from Massing to Countering.
  • As soon as the base is under attack, it switches from Countering to Defending.

Now that you've got the strategy in mind, let's try it out in this game! :]

Simplest Implementation

Let's try out the simplest way to get this working first - I'll later show you a better way.

Open AIPlayer.m and add an enumeration for the different states and a private instance variable as follows:

typedef enum {
    StateMassing = 0,
    StateCountering,
    StateDefending,
    StateRushing
} State;

@implementation AIPlayer  {
    State _state;
}

Then replace the update: method with the following:

- (void)update:(ccTime)delta {
    [super update:delta];
    
    if (_state == StateMassing) {        
        NSArray * enemies = [self.layer enemiesWithinRange:200 ofPlayer:self];
        if (enemies.count > 0) {
            _state = StateDefending;
            return;
        } else if (self.layer.aiTotalValue + self.coins >= COST_MUNCH + COST_ZAP*2) {
            _state = StateCountering;
            return;
        }
        [self.layer setPlayer:self attacking:NO];
    }
}

The massing state is quite simple - it just checks to see if the base is under attack (and switches to defense if so), or if it's saved up enough money (and switches to the counter state if so). Otherwise, all it does is just sit there and save money, making sure it's in defending state while it's doing so.

Add the next case to the bottom of update:

else if (_state == StateCountering) {
    NSArray * enemies = [self.layer enemiesWithinRange:200 ofPlayer:self];
    if (enemies.count > 0) {
        while (self.coins > COST_QUIRK) {
            if (self.layer.aiQuirkValue < self.layer.humanZapValue && self.coins > COST_QUIRK) {
                [self.layer spawnQuirkForPlayer:self];
            } else if (self.layer.aiZapValue < self.layer.humanMunchValue && self.coins > COST_ZAP) {
                [self.layer spawnZapForPlayer:self];
            } else if (self.layer.aiMunchValue < self.layer.humanQuirkValue && self.coins > COST_MUNCH) {
                [self.layer spawnMunchForPlayer:self];
            } else {
                [self.layer spawnQuirkForPlayer:self];
            }
        }
        _state = StateDefending;
        return;
    }
} 

In the countering case, it just sits there and waits for the base to get attacked. The AI has more patience than you! :]

When it does get attacked, it simply spends all the money its saved up to repel the enemy with overwhelming force. Note this logic is not exactly the same as the "countering" logic earlier, because it has a difference in that it spends all money no matter what.

It then switches to the defending state. Add that case to the bottom of update: next:

else if (_state == StateDefending) {
    NSArray * enemies = [self.layer enemiesWithinRange:200 ofPlayer:self];
    if (enemies.count == 0) {
        _state = StateRushing;
        return;
    }
    
    [self.layer setPlayer:self attacking:NO];
    
    if (self.layer.aiTotalValue == 0 || self.layer.humanTotalValue > self.layer.aiTotalValue * 2) {
        if (self.layer.aiQuirkValue < self.layer.humanZapValue && self.coins > COST_QUIRK) {
            [self.layer spawnQuirkForPlayer:self];
        } else if (self.layer.aiZapValue < self.layer.humanMunchValue && self.coins > COST_ZAP) {
            [self.layer spawnZapForPlayer:self];
        } else if (self.layer.aiMunchValue < self.layer.humanQuirkValue && self.coins > COST_MUNCH) {
            [self.layer spawnMunchForPlayer:self];
        } else if (self.layer.humanTotalValue == 0) {
            while (self.coins > COST_ZAP + COST_QUIRK) {
                [self.layer spawnQuirkForPlayer:self];
                [self.layer spawnZapForPlayer:self];
            }
        }
    }
} 

As long as the base is under attack, it keeps countering the enemy. When there are no more enemies within range, it switches to rushing.

Add the final rushing state next:

else if (_state == StateRushing) {
    // Check if should change state
    NSArray * enemies = [self.layer enemiesWithinRange:200 ofPlayer:self];
    if (enemies.count > 0) {
        _state = StateDefending;
        return;
    } else if (self.layer.aiTotalValue == 0) {
        _state = StateMassing;
        return;
    }
    
    [self.layer setPlayer:self attacking:YES];
    
    // Make build decision
    if (self.layer.aiTotalValue >= self.layer.humanTotalValue) {
        [self.layer spawnQuirkForPlayer:self];
    }
}

This state simply keeps sending quirks until it's losing or under attack. When it's losing, it switches back to the massing state.

It's almost time to try this out, but first let's add some test code so you can easily see what state the AI is in. You will display the AI's current state in a label on the screen.

To do this add the following method (still in AIPlayer.m):

- (NSString *)stateName {
    if (_state == StateMassing) {
        return @"Massing";
    } else if (_state == StateCountering) {
        return @"Countering";
    } else if (_state == StateDefending) {
        return @"Defending";
    } else if (_state == StateRushing) {
        return @"Rushing";
    } else {
        return @"Unknown";
    }
}

And declare it in AIPlayer.h:

- (NSString *)stateName;

And finally, in HelloWorldLayer.h add this to the bottom of the update: method:

[_stateLabel setString:_aiPlayer.stateName];

That's it! Build and run and you'll see your old strategy no longer works - it takes a bit more intelligence and tricks to beat the AI now! Can you do it? :]

Contributors

Over 300 content creators. Join our team.