Skip to content

KevinRGeurts/pyGameAIFoundation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pyGameAIFoundation

Source code: GitHub

pyGameAIFoundation is a python package that provides classes for implementing decision making AI in games. It includes classes for goal-oriented behavior, action execution, and blackboard architecture. A develper can use these foundation classes to implement game goals and actions specific to their game, and to utilize those objects to create goal-based behavior in a game.

Credit where credit is due

  • The package generally implements pseudo code from the book "Artificial Intelligence for Games", Second Edtion, by Ian Millington and John Funge, Chapter 5, 2009.

Gob class

The Gob, which is an implementation of Goal-Oriented Behavior. Specifically, it implements the Simple Selection mechanisim for choosing a game action from a set of goals and actions. It implements pseudo code from Section 5.7.2 of "Artificial Intelligence for Games".

GameGoal class

GameGoal is an abstract base class. Concrete implementations of this class represent goals that can be achieved in a game. It implements pseudo code from Section 5.7.2-3 of "Artificial Intelligence for Games".

A concrete implementation must implement the method getInsistence(), which is called to get the insistence (urgency or importance) value of the goal. The higher the insistence value, the more urgent or important the goal is.

ActionManager class

The ActionManager class manages the scheduling and execution of actions in a game. It implements pseudo code from Section 5.11.3 of "Artificial Intelligence for Games".

GameAction class

GameAction is an abstract base class, GameAction. Concrete implementations of this class represent actions that can be performed in a game. It generally implements pseudo code from Section 5.11.4 of "Artificial Intelligence for Games", The possibility of actions being able to read and write data to a blackboard is additionally included, following the blackboard architecture pattern described in Section 5.9 of the same book.

Concrete implementation child classes of GameAction must:

  • Implement the method execute(), which is called to enact the specific behavior of the action.
  • Override the method isComplete(), which is called to determine if the action is completed.

Concrete implementation child classes of GameAction also:

  • May override the method canInterrupt(), which is called to determine if this action can interrupt other actions, e.g., in the active collection of an ActionManager object.
  • May override the method canDoBoth(...), which is called to determine of this action can be executed at the same time as another action, e.g., in the active collection of an ActionManager object.
  • Should override the method getGoalChange(...), which is called to determine how this action affects the insistence of a specific goal, e.g., in the goal collection maintained by a Gob object. The method should return a negative integer, indicating a reduction in the insistence of the goal when the action is executed.

GameActionCombination class (child of GameAction)

The GameActonCombination class is a child of GameAction, and it represents a combination of game actions that can be performed together (in parallel, at least in the sense of game logic).

GameActionSequence class (child of GameAction)

The GameActionSequence class is a child of GameAction, and it represents a sequence of game actions that must be performed in order in the sense of game logic.

Usage

# Local imports
from pyGameAIFoundation.gob import Gob
from pyGameAIFoundation.game_goal import GameGoal, GoalInsistence
from pyGameAIFoundation.action_manager import ActionManager
from pyGameAIFoundation.game_action import GameAction, GameActionCombination, GameActionSequence


# Create a couple of concrete GameGoal subclasses with different insistence values.

class GoalOne(GameGoal):
    def getInsistence(self):
        """
        Return the insistence value of the goal.
        :return: The insistence value of the goal, int
        """
        return GoalInsistence.LOW


class GoalTwo(GameGoal):
    def getInsistence(self):
        """
        Return the insistence value of the goal.
        :return: The insistence value of the goal, int
        """
        return GoalInsistence.MEDIUM


# Create a pair of GameAction subclasses with different implementations of getGoalChange(...)

class ActionOne(GameAction):
    def __init__(self, expiry_time=0, priority=0, read_blackboard=None, write_blackboard=None):
        """
        :parameter expiry_time: The time in an arbitrary count-up from zero until the action expires, as int
        :parameter priority: The priority of the action. Higher numbers indicate higher priority. As int
        :parameter read_blackboard: A function to read from the blackboard, as callable
            Signature: read_blackboard(key: str) -> any
        :parameter write_blackboard: A function to write to the blackboard, as callable
            Signature: write_blackboard(key: str, value: any) -> None
        """
        super().__init__(expiry_time, priority, read_blackboard, write_blackboard)
        self._canInterrupt = False
        self._canDoBoth = False
        self._completed=False

    def canInterrupt(self):
        """
        Return whether this action can interrupt any other action.
        :return: True if the action can interrupt any other action, False otherwise, as boolean.
        """
        return self._canInterrupt

    def canDoBoth(self, other_action):
        """
        Return whether this action can be done at the same time as another action.
        :param other_action: The other action to check against, as GameAction object.
        :return: True if both actions can be done at the same time, False otherwise, as boolean.
        """
        assert(isinstance(other_action, GameAction))
        return self._canDoBoth

    def isComplete(self):
        """
        Return whether this action is complete.
        :return: True if the action is complete, False otherwise, as boolean.
        """
        return self._completed

    def execute(self):
        """
        Execute the action. In this case, by writing to the blackboard (if we have one) and
        setting the completed flag to True.
        :return: None
        """
        print("Executing ActionOne")
        if self._write_blackboard:
            self._write_blackboard('ActionOne Output', 15)
            print("Wrote to blackboard: ActionOne Output = 15")
        self._completed = True
        return None

    def getGoalChange(self, goal=None):
        """
        Return the goal insistence change associated with this action.
        :param goal: The goal to check against, as GameGoal object.
        :return: The goal insistence change associated with this action, as int.
        """
        assert(isinstance(goal, GameGoal))
        if isinstance(goal, GoalOne):
            return -GoalInsistence.MEDIUM
        else:
            return -GoalInsistence.LOW


class ActionTwo(GameAction):
    def __init__(self, expiry_time=0, priority=0, read_blackboard=None, write_blackboard=None):
        """
        :parameter expiry_time: The time in an arbitrary count-up from zero until the action expires, as int
        :parameter priority: The priority of the action. Higher numbers indicate higher priority. As int
        :parameter read_blackboard: A function to read from the blackboard, as callable
            Signature: read_blackboard(key: str) -> any
        :parameter write_blackboard: A function to write to the blackboard, as callable
            Signature: write_blackboard(key: str, value: any) -> None
        """
        super().__init__(expiry_time, priority, read_blackboard, write_blackboard)
        self._canInterrupt = False
        self._canDoBoth = False
        self._completed=False

    def canInterrupt(self):
        """
        Return whether this action can interrupt any other action.
        :return: True if the action can interrupt any other action, False otherwise, as boolean.
        """
        return self._canInterrupt

    def canDoBoth(self, other_action):
        """
        Return whether this action can be done at the same time as another action.
        :param other_action: The other action to check against, as GameAction object.
        :return: True if both actions can be done at the same time, False otherwise, as boolean.
        """
        assert(isinstance(other_action, GameAction))
        return self._canDoBoth

    def isComplete(self):
        """
        Return whether this action is complete.
        :return: True if the action is complete, False otherwise, as boolean.
        """
        return self._completed

    def execute(self):
        """
        Execute the action. In this case, by reading from the blackboard (if we have one) and
        setting the completed flag to True.
        :return: None
        """
        print("Executing ActionTwo")
        if self._read_blackboard:
            value = self._read_blackboard('ActionOne Output')
            print(f"Read from blackboard: ActionOne Output = {value}")
        self._completed = True
        return None

    def getGoalChange(self, goal=None):
        """
        Return the goal insistence change associated with this action.
        :param goal: The goal to check against, as GameGoal object.
        :return: The goal insistence change associated with this action, as int.
        """
        assert(isinstance(goal, GameGoal))
        if isinstance(goal, GoalTwo):
            return -GoalInsistence.MEDIUM
        else:
            return -GoalInsistence.LOW

        
# Create the Gob (goal-oriented behavior) object. 
gob = Gob()
# Add goals to the Gob.
gob.add_goal(GoalOne('goal_1'))
gob.add_goal(GoalTwo('goal_2'))
# Create actions and add them to the Gob.
act1 = ActionOne(expiry_time=10)
act2 = ActionTwo(expiry_time=10)
gob.add_action(act1)
gob.add_action(act2)
    
# Create the ActionManager.
mgr = ActionManager()

# Choose the best action to execute.
(bestAct, topGoal) = gob.chooseAction()
bestAct.expiry_time = mgr.currentTime + bestAct.expiry_time  # Set the expiry time for the action.
print(f'Chose action with priority {bestAct.priority} to fulfill goal "{topGoal.name}" with insistence {topGoal.getInsistence()}')

# Schedule the best action in the ActionManager.
mgr.scheduleAction(bestAct)

# Output the action manager state.
print(f'Action Manager State before execution:\n{mgr}')

# Execute the action manager, which will activate the scheduled action and execute it
mgr.execute()
# Output the action manager state.
print(f'Action Manager State after first execution:\n{mgr}')
# Execute the action manager again, which will remove the completed action from the active list.
mgr.execute()
# Output the action manager state.
print(f'Action Manager State after second execution:\n{mgr}')

# Create a GameActionCombination and demonstrate its use in the Gob and ActionManager.
combo = GameActionCombination(expiry_time=10, combo_acts=[act1, act2])
gob.add_action(combo)
(bestAct, topGoal) = gob.chooseAction()
bestAct.expiry_time = mgr.currentTime + bestAct.expiry_time  # Set the expiry time for the action.
print(f'\nChose action with priority {bestAct.priority} to fulfill goal "{topGoal.name}" with insistence {topGoal.getInsistence()}')
mgr.scheduleAction(bestAct)
print(f'Action Manager State before execution:\n{mgr}')
# Activate the scheduled combination action, and execute both actions in the combo.
mgr.execute()
print(f'Action Manager State after first execution:\n{mgr}')
# Remove the completed combination action from the active list.
mgr.execute()
print(f'Action Manager State after second execution:\n{mgr}')
# Remove the combination action from the Gob's action list, so it doesn't interfere with the rest of the demo,
# since it's insistence change is the same as the sequence action created next.
gob.remove_action(combo)

# Create a GameActionSequence, demonstrate its use in the Gob and ActionManager, and use of the
# blackboard to pass information between actions in a sequence.
seq = GameActionSequence(expiry_time=10)
act1 = ActionOne(expiry_time=10, read_blackboard=seq.readFromBlackBoard, write_blackboard=seq.writeToBlackBoard)
act2 = ActionTwo(expiry_time=10, read_blackboard=seq.readFromBlackBoard, write_blackboard=seq.writeToBlackBoard)
seq.addAction(act1)
seq.addAction(act2)
gob.add_action(seq)
(bestAct, topGoal) = gob.chooseAction()
bestAct.expiry_time = mgr.currentTime + bestAct.expiry_time  # Set the expiry time for the action.
print(f'\nChose action with priority {bestAct.priority} to fulfill goal "{topGoal.name}" with insistence {topGoal.getInsistence()}')
mgr.scheduleAction(bestAct)
print(f'Action Manager State before execution:\n{mgr}')
# Activate the scheduled sequence action, and execute the first action in the sequence.
mgr.execute()
print(f'Action Manager State after first execution:\n{mgr}')
# Execute the second action in the sequence.
mgr.execute()
print(f'Action Manager State after second execution:\n{mgr}')
# Remove the completed sequence action from the active list.
mgr.execute()
print(f'Action Manager State after third execution:\n{mgr}')

Demonstration

To run the demonstration, type python -m pyGameAIFoundation.main in a terminal window. Note, that this assumes that the pyGameAIFoundation package has been installed in your Python environment. The demonstration is essentially the same as the usage example above.

Unittests

Unittests for the pyGameAIFoundation are in the tests directory, with filenames starting with test_. To run the unittests, type python -m unittest discover -s ..\..\tests -v in a terminal window in the src\pyGameAIFoundation directory.

License

MIT License. See the LICENSE file for details

About

pyGameAIFoundation is a python package that provides classes for implementing decision making AI in games.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages