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.
- The package generally implements pseudo code from the book "Artificial Intelligence for Games", Second Edtion, by Ian Millington and John Funge, Chapter 5, 2009.
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 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.
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 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.
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).
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.
# 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}')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 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.
MIT License. See the LICENSE file for details