-
Notifications
You must be signed in to change notification settings - Fork 6
Writing your own actions
Rather than go into boring details about the structure, I'm going to build a simple agent with you here, step-by-step. Along the way I'll describe what we're doing and why we're doing it. The completed tutorial is available in the source distribution. It is called 'tutorial.py'
Let's imagine that we want to build a lonely agent for our simulation. In defining the agent, we'll start by defining what it actually does. As it turns out, our lonely agent just wants to say hello, so let's start by making a class that does just that: say hello.
class PrintActionContext(CalledOnceContext):
def enter(self):
print "hello world"
I'm using the CalledOnceContext because this action is simple and doesn't require complex setup. I'll describe other ActionContext types later.
Now we have the Action. This action is generic and can be used by any other agent. The planner, however, cannot use ActionContexts, so we must create another class that defines how this particular ActionContext (PrintActionContext) is used. We use the ActionBuilder class for that.
ActionBuilder classes get queried by the planner for a list of actions that can be run at any given state. In this simple example, we are just going to return a single action. This essentially means that the Print ActionContext class can be run at any time.
class PrintAction(ActionBuilder):
def get_actions(self, caller, memory):
yield PrintActionContext(caller)
Next we'll have to add this action to the agent's planner. Since we don't have an agent yet, we'll create on first, then add the ActionBuilder (PrintAction).
agent = GoapAgent()
agent.add_action(PrintAction())
And there you have it! Unfortunately, although the planner has the ability to introduce itself to you via the console (hello world!), it never will. The purpose of GOAP is real-time determination of plans without relying on explicitly defining every possible situation the agent will encounter.
This formulation of plans relies on 'goals' and 'effects'. The agent will determine the most appropriate goal for any given situation and will make a plan from that. Lets give our agent something to strive for: a goal.
friendly_goal = SimpleGoal(introduced_self=True)
agent.add_goal(friendly_goal)
Ok, so we've created a goal using the SimpleGoal class. It relies on simple keyword value pairs. In this case, the goal will be 'satisfied' when the keyword 'introduced_self' is True. The agent will choose unsatisfied goals and attempt to form a plan that will satisfy them.
Even now, the agent will never print to the console. Can you guess why--we need to give the planner a reason to use our PrintAction. To do this, we will add an effect to it.
class PrintAction(ActionBuilder):
def get_actions(self, caller, memory):
action = PrintActionContext(caller)
action.effects.append(SimpleGoal(introduced_self=True))
yield action
Does that SimpleGoal look familiar to you? It is the same as we used to create our goal for the agent! Now, when the planner attempts to make a plan that satisfies the goal we added to it (friendly_goal), it will be able to choose PrintAction as a possible way to do it.
Let's get our agent working to test it out.
PyGOAP is an abstraction from some other type of context. PyGOAP agents only understand the world through fragments of data that represent the environment it is a part of. Those data are called "precepts" and PyGOAP includes some basic precepts to just get the environment simulation working.
I'm going to back track a little here and introduce the "Environment". The environment's basic tasks are to pass out precepts, model how precepts 'move' in the environment, and to give each agent the opportunity to plan and carry out actions.
env = Environment()
env.add(agent)
Our agent now has a home. Environments carry out simulation in timesteps.
env.update(1)
In this example, we are using 1 as an arbitrary measurement of time that has passed since last update. Agents in the simulation will get a TimePrecept so that they are aware time has passed and the actions that they are currently running will be given that value as an argument, then allowed to update.
After executing the time step, you should see 'hello world' displayed on your console. It worked!
At this point, if you have any difficulties, please check 'tutorial.py' found in the source distribution.