11 Artificial Intelligence (AI)

In this chapter we are going to add some basic decision making into the mob created in chapter 09 MOB to act as a simple Artificial Intelligence (AI) in the game. In this guide the words Artificial Intelligence is a extradition. AI is an interesting area of research for the Air Force Research Lab. AI enables smart phones, autonomous vehicles, internet search, and other computer applications. AI is used in video games to control MOBs. Recently AI has been used to beat human experts at chess and the ancient game of go. This guide is going to provide you a very simple foundation on which to learn about AI. To get started we are going to implement a new class to hold logic for the mob in a separate file and then use the logic written there to move the mob.

New Class File

First, make a new file called AI_class_TVR.py (with your initials replacing TVR). If you look at the first few lines of any of the previous tutorials, take note of the import statements that tell the file to pull in information from previously defined files and libraries. In the new AI_class_TVR.py file, we only need one import command. At the top of the new file, type the following:

from __future__ import division

This helps the code be compatible with different versions of python which deal with certain inputs and operations slightly differently.

Now we need to declare the class and write a function to initialize the object. Type the following below the previous line:

class AI:
    def __init__(self):

        # mode: follow, run, or none
        self.mode = "none"

        # status: active or not
        self.status = False

        # keep track of last movement
        self.count = 15

This creates a new class called AI and defines the __init__ function. In __init__, notice the member variables declared. mode will store what type of action the AI will take. status determines whether or not the AI is active and making decisions. Finally count will track how long it has been since the AI has taken action as to prevent it from acting too often and too quickly. Recall that this function is executed whenever the frame is refreshed in the display window, which occurs 60-120 times a second depending on the frame rate. If the mob were to move at every frame update it would be unneeded computation.

Our initial goal is to make the AI follow or run away from Dr. Steve. Add two functions below __init__ on line 18 as follows:

    def follow(self, xm, zm, x, z):
        '''
            guide the AI away from given coordinates
            xm and ym are current position, x and z are player position
        '''
        move = [False, False, False, False]
        dist = [(xm - x), (zm - z)]
        if dist[0] > 1.25:
            move[2] = True
        elif dist[0] < -1.25:
            move[3] = True
        if dist[1] > 1.25:
            move[1] = True
        elif dist[1] < -1.25:
            move[0] = True

        return move
    def run_away(self, xm, zm, x, z):
        '''
             make AI run from these x z coordinates
        '''
        move = [False, False, False, False]
        dist = [(xm - x), (zm - z)]
        if dist[0] < 0 and dist[0] > -3:
            move[2] = True
        elif dist[0] > 0 and dist[0] < 3:
            move[3] = True
        if dist[1] > -3 and dist[1] < 0:
            move[1] = True
        elif dist[1] < 3 and dist[1] > 0:
            move[0] = True

        return move

These two functions are very similar in terms of their logic. The first function follow takes xm and zm, the x and z coordinates of the object controlled by the AI, and x and z, the location of what the AI is trying to follow or run away from. In both functions there is an object called move. This is a list of booleans (an object that can take on the value of either True or False) that will store what direction(s) the object should be moved to accomplish the specified task. The following if-else statements then determine which items in move should be true and returns this list of actions.

Using the New AI Class - Part 1

Now that we have defined a new class in a new file we need some way of using this file in the code we’ve already written. Copy the 10_health_part_4.py code from the previous example (replacing TVR with your initials) as follows:

cp 10_health_part_4.py 11_AI_part_1_TVR.py

We first need to let this file access and use the class from AI_class_TVR.py. To do this, go to the top of 11_AI_part_1_TVR.py and add the line import AI_class_TVR so the first few lines of the file read as follows:

from __future__ import division
import sys, os
import math
import random
import time

import AI_class

from collections import deque
from pyglet import image
from pyglet.gl import *
from pyglet.graphics import TextureGroup
from pyglet.window import key, mouse

Your code should slightly differ here in that the line that reads import AI_class above should read AI_class_TVR in your code where TVR is replaced by your initials. The basic rule when importing from another file is that if you have a file called “name_here.py”, any files that want to use code written in “name_here.py” should have a statement similar to import name_here.

Next go to the end of the __init__ function in the Model class around line 170 and add an instance of this AI object so that the end of the __init__ function reads as follows:

This creates an instance of the AI class in the model object that we can use. Next after the end of the check_mob_dist function add the following:

This function executes the moves specified by the code in the AI class. The modes must be set correctly. Go to the on_key_press method and add the following to the end:

Notice by adding self.model.mob_loaded = True for when ‘F’ is pressed, the mob will inflict damage on Dr. Steve when it is following him. It will not cause damage when the mob is running away.

Find the update function on line 675 in the window class and add a call to move_mob at the end while commenting out the call to process_mob. The function should then read as follows:

Notice by commenting out the call to process_mob we are ensuring the program uses the logic from the new AI_class instead of what was written in chapter 09 MOB.

Execute the code, you should be able to press ‘L’ to load the mob and then either ‘F’ or ‘R’ to make the mob follow Dr. Steve or run away from him. Don’t forget that when the mob is following Dr. Steve, it can reduce his health if it gets too close. Be careful! You don’t want to get game over.

It is worth discussing that even though we are describing this behavior as AI, it is simply the application of an algorithm on some input that results in a direction that points toward Dr. Steve. We can see the mob as following Dr. Steve, but it is really just following a set mathematical pattern that we implemented. Thus this behavior can be perceived as a Artificial Intelligence that follows Dr. Steve by someone who doesn’t know how the code works. Most Artificial Intelligence software implemented today follow simple mathematical patterns.

No More Walking Through Walls - Part 2

Let’s perform a scientific experiment.

Run the program from the previous section above and press “L” to launch the mob. Now place some blocks between yourself and the mob. Press “F” so the mob starts to follow you. Wait, that’s not right. The mob walked straight through the blocks and made them disappear! Let’s fix this bug and prevent the mob from walking through walls.

As is the guide custom copy the 11_AI_part_1.py code from the previous example (replacing TVR with your initials) like below:

cp 11_AI_part_1.py 11_AI_part_2_TVR.py

First, we need to add a variable to keep track of the mob’s height. In the Model class’ __init__ function, recall we already have two variables self.mob_x_position and self.mob_z_position to track the mob at ground level. Add another variable just after these two so it reads as follows:

Now jump down to the check_mob_dist function. Let’s change it to account for differences in height by changing the function definition to read as below:

The main difference is that now when we calculate the distance between the mob and Dr. Steve, we also consider any difference in height. If we didn’t do this, the game would consider two objects at the same x and z coordinates as being at the same position, regardless of what their y coordinates were. This would be equivalent to saying two people on different floors of a building were standing next to each other.

Now find where we defined the functions mob_move_* where * is a direction. Previously we ignored the height at which the mob was located so we just had it at a constant y position of -1 which is ground level. Now that we are tracking the height of the mob, we need to change this. Throughout these functions, you will notice calls to self.add_block and self.remove_block. In each call there is a -1. Replace each -1 with self.mob_y_position. For example, mob_move_right should now read as follows:

Next we need to create methods similar to those we just edited that move the mob vertically. Just after mob_move_backward add the following two functions:

These functions are almost identical to those defined previously except that now we can move the mob in any direction.

Now go to where the different textures were defined (where we defined TEXTURE_PATH) and add the following line:

ALL_TEXTURE = [GRASS, SAND, BRICK, STONE, MOB_STATE1, MOB_STATE2]

Go to the move_mob function (just after check_mob_dist). We need to make many changes here, so just replace move_mob with the following two functions:

The first function checks to see whether there is already an object at the given position. If there is nothing there, it returns True. If something is already there it returns False. Note that when we say a function “returns” something, this means that when we use the function, not only can we change values passed to the function, but we get some extra value(s) back.

Let’s walk through the changes to move_mob. First, we declare a new variable moved_up. This will help us track whether the mob just moved vertically and help us determine if the mob can move down. The next part of the code is identical to what we had previously: check what mode the mob is in and determine what direction it should move. Next we have the statements that actually move the mob in the specified direction(s). Each direction has almost identical logic so let’s only examine one of them closely (the section under the if statements reading if out[0]).

First we get the position, called pos, of the space in front of the mob. We check if it is available with a call to check_avail. If there is already something there, we want to try and go over it. So we redefine pos to be the place one in front and one up from the mob’s current location and make another call to check_avail. If the space directly in front of the mob is available, move there. If it isn’t and the space above is available, the mob moves there. Otherwise, the mob shouldn’t move up or forward at this time.

We use this same logic for each direction, checking one square in the specified direction and one above and then moving to the available spot. At the end of the function, notice the below few lines:

                self.model.mob_move_down()
                pos = (self.model.mob_x_position,
                       self.model.mob_y_position-1,
                       self.model.mob_z_position)

This says that while the space just below the mob is available, the mob is above ground level, and the mob did not just move up, the mob should move down a space. This prevents the mob from floating around while it’s chasing Dr. Steve or running away from him. That would just be spooky!

Try running the code again. Now if you build a barrier one block high, the mob can get over it. Notice though that the mob now follows the same rules you do, it can’t get over more than one block so if you build a wall two or more blocks high it gets stuck.

If you go back to the section containing the build hills code (in the model’s _initialize function), you can uncomment this section and watch the mob run up and down the hills. If it is easier you can copy/paste the code from the file called 11_AI_part_2.py around line 198 - 217.

Limitations

This is a very basic computer controlled character. While it will try and follow Dr. Steve wherever he goes, the mob cannot determine if there is a possible path to him. For example, if you build some steps and go up them, the mob may not follow Dr. Steve up the steps unless it was running toward the steps from the correct direction. It may get stuck on the sides. To fix this, the game would require a much more complicated algorithm or AI that can look at potential moves it could find a route to get to Dr. Steve.