Skip to content

Commit

Permalink
misc changes to make planning more robust.
Browse files Browse the repository at this point in the history
  • Loading branch information
bitcraft committed Apr 19, 2013
1 parent 74c5857 commit 6a2c543
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 109 deletions.
25 changes: 17 additions & 8 deletions npc/pirate/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,15 @@ def get_position(entity, memory):
return pct.position


class LookAction(CalledOnceContext):
def enter(self):
print "LOOKING"
self.parent.environment.look(self.parent)


class MoveAction(ActionContext):
def update(self, time):
super(MoveAction, self).update(time)
if self.caller.position[1] == self.endpoint:
self.finish()
else:
env = self.caller.environment
path = env.pathfind(self.caller.position[1], self.endpoint)
path.pop() # this will always the the starting position
env.move(self.caller, (env, path.pop()))
print self, "moving?"

def setStartpoint(self, pos):
self.startpoint = pos
Expand Down Expand Up @@ -123,3 +122,13 @@ def get_actions(self, caller, memory):

exported_actions.append(drink_rum)



class look(ActionBuilder):
def get_actions(self, caller, memory):
action = LookAction(caller)
action.effects.append(SimpleGoal(aware=True))
yield action


exported_actions.append(look)
2 changes: 1 addition & 1 deletion pygoap/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@

from actions import ActionContext
35 changes: 22 additions & 13 deletions pygoap/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Actions need to be split into ActionInstances and ActionBuilders.
An ActionInstance's job is to work in a planner and to carry out actions.
A ActionBuilder's job is to query the caller and return a list of suitable
A ActionBuilder's job is to query the parent and return a list of suitable
actions for the memory.
"""

Expand All @@ -33,10 +33,10 @@ class ActionBuilder(object):
tested. Please make sure that the actions are valid.
"""

def __call__(self, caller, memory):
return self.get_actions(caller, memory)
def __call__(self, parent, memory):
return self.get_actions(parent, memory)

def get_actions(self, caller, memory):
def get_actions(self, parent, memory):
"""
Return a list of actions
"""
Expand All @@ -51,8 +51,8 @@ class ActionContext(object):
Context where actions take place.
"""

def __init__(self, caller, **kwargs):
self.caller = caller
def __init__(self, parent, **kwargs):
self.parent = parent
self.state = ACTIONSTATE_NOT_STARTED
self.prereqs = []
self.effects = []
Expand All @@ -71,9 +71,9 @@ def __exit__(self, *exc):
"""
Please do not override this method. Use exit instead.
"""
if self.state is ACTIONSTATE_RUNNING:
if self.state == ACTIONSTATE_RUNNING:
self.state = ACTIONSTATE_FINISHED
if self.state is not ACTIONSTATE_ABORTED:
if not self.state == ACTIONSTATE_ABORTED:
self.exit()
return False

Expand All @@ -95,15 +95,24 @@ def update(self, time):
"""
pass

def finish(self):
"""
Call this method when context is no longer needed or is finished.
Do not override. Handle cleanup in exit instead.
"""
self.state = ACTIONSTATE_FINISHED

def fail(self):
"""
Call this method if the context is not able to complete
Do not override. Handle cleanup in exit instead.
"""
self.state = ACTIONSTATE_FAILED

def abort(self):
"""
Call this method to stop this context without cleaning it up
Do not override. Handle cleanup in exit instead.
"""
self.state = ACTIONSTATE_ABORTED

Expand All @@ -122,9 +131,9 @@ def test(self, memory=None):
modify numerical values, it may be useful to return a fractional value.
"""

if memory is None: raise Exception

if not self.prereqs: return 1.0

if memory is None: raise Exception
values = ( i.test(memory) for i in self.prereqs )

try:
Expand All @@ -139,7 +148,7 @@ def touch(self, memory=None):
Call after the planning phase is complete.
"""
if memory is None:
memory = self.caller.memory
memory = self.parent.memory
[ i.touch(memory) for i in self.effects ]

def __repr__(self):
Expand All @@ -153,8 +162,8 @@ class CalledOnceContext(ActionContext):

def __enter__(self):
if self.test() == 1.0:
super(CalledOnceContext, self).enter()
super(CalledOnceContext, self).exit()
super(CalledOnceContext, self).__enter__()
super(CalledOnceContext, self).__exit__()
else:
self.fail()

65 changes: 44 additions & 21 deletions pygoap/agent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from environment import ObjectBase
from planning import plan
from actions import ActionContext
from blackboard import MemoryManager
from memory import MemoryManager
from actionstates import *
from precepts import *
import logging
Expand All @@ -11,6 +11,8 @@


NullAction = ActionContext(None)
NullAction.__enter__()
NullAction.__exit__()

# required to reduce memory usage
def time_filter(precept):
Expand All @@ -29,11 +31,12 @@ class GoapAgent(ObjectBase):
interested = []
idle_timeout = 30

def __init__(self):
def __init__(self, name=None):
super(GoapAgent, self).__init__(name)
self.memory = MemoryManager()
self.planner = plan

self.current_goal = None
self.current_goal = None

self.goals = [] # all goals this instance can use
self.filters = [] # list of methods to use as a filter
Expand Down Expand Up @@ -61,7 +64,6 @@ def filter_precept(self, precept):
precepts can be put through filters to change them.
this can be used to simulate errors in judgement by the agent.
"""

for f in self.filters:
precept = f(precept)
if precept is None:
Expand All @@ -80,7 +82,10 @@ def process(self, precept):
debug("[agent] %s recv'd precept %s", self, precept)
self.memory.add(precept)

return self.next_action()
if self.next_action is NullAction:
self.replan()
return self.next_action


def replan(self):
"""
Expand All @@ -94,49 +99,67 @@ def replan(self):

debug("[agent] goals %s", s)

start_action = NullAction

# starting for the most relevant goal, attempt to make a plan
plan = []
self.plan = []
for score, goal in s:
tentative_plan = self.planner(self, self.actions,
self.current_action, self.memory, goal)
start_action, self.memory, goal)

if tentative_plan:
tentative_plan.pop()
pretty = list(reversed(tentative_plan[:]))
debug("[agent] %s has planned to %s", self, goal)
debug("[agent] %s has plan %s", self, pretty)
plan = tentative_plan
self.plan = tentative_plan
self.current_goal = goal
break

return plan

# we only support one concurrent action (i'm lazy)
def running_actions(self):
return self.current_action

@property
def current_action(self):
"""
get the current action of the current plan
"""

try:
return self.plan[-1]
except IndexError:
return NullAction

def running_actions(self):
return self.current_action

@property
def next_action(self):
"""
get the next action of the current plan
if the current action is finished, return the next
otherwise, return the current action
"""

if self.plan == []:
self.plan = self.replan()

# this action is done, so return the next one
# this action is done
if self.current_action.state == ACTIONSTATE_FINISHED:
return self.plan.pop() if self.plan else None

# there are more actions in the queue, so just return the next one
if self.plan:
return self.plan.pop()

# no more actions, so the plan worked!
else:

# let the goal do its magic to the memory manager
if self.current_goal:
self.current_goal.touch(self.memory)
self.current_goal = None

return NullAction

# this action failed somehow
elif self.current_action.state == ACTIONSTATE_FAILED:
raise Exception, "action failed, don't know what to do now!"

# our action is still running, just run that
elif self.current_action.state == ACTIONSTATE_RUNNING:
return current_action


return self.current_action
33 changes: 25 additions & 8 deletions pygoap/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(self, entities=[], agents=[], time=0):
self.time = time
self._agents = []
self._entities = []
self._positions = []
self._positions = {}

[ self.add(i) for i in entities ]
[ self.add(i) for i in agents ]
Expand All @@ -67,6 +67,14 @@ def entities(self):
def get_position(self, entity):
raise NotImplementedError


# this is a placeholder hack. proper handling will go through
# model_precept()
def look(self, caller):
for i in chain(self._entities, self._agents):
caller.process(LocationPrecept(i, self._positions[i]))


def run(self, steps=1000):
"""
Run the Environment for given number of time steps.
Expand All @@ -81,9 +89,19 @@ def add(self, entity, position=None):

from agent import GoapAgent


debug("[env] adding %s", entity)


# hackish way to force agents to re-evaulate their environment
for a in self._agents:
to_remove = []

for p in a.memory.of_class(DatumPrecept):
if p.name == 'aware':
to_remove.append(p)

[ a.memory.remove(p) for p in to_remove]

# add the agent
if isinstance(entity, GoapAgent):
self._agents.append(entity)
Expand Down Expand Up @@ -112,18 +130,17 @@ def update(self, time_passed):
p = TimePrecept(self.time)
[ a.process(p) for a in self.agents ]

[ self.look(a) for a in self.agents ]

# get all the running actions for the agents
self.action_que = [ a.running_actions() for a in self.agents ]

# start any actions that are not started
[ action.__enter__() for action in self.action_que
if action.state == ACTIONSTATE_NOT_STARTED ]

# update all the actions that may be running
precepts = [ a.update(time_passed) for a in self.action_que ]
precepts = [ p for p in precepts if not p == None ]

# start any actions that are not started
[ action.enter() for action in self.action_que
if action.state == ACTIONSTATE_NOT_STARTED ]


def broadcast_precepts(self, precepts, agents=None):
"""
Expand Down
8 changes: 4 additions & 4 deletions pygoap/environment2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,19 @@ def model_vision(self, precept, origin, terminus):
def model_sound(self, precept, origin, terminus):
return precept

def look(self, caller, direction=None, distance=None):
def look(self, parent, direction=None, distance=None):
"""
Simulate vision by sending precepts to the caller.
Simulate vision by sending precepts to the parent.
"""

model = self.model_precept

for entity in self.entities:
caller.process(
parent.process(
model(
PositionPrecept(
entity, self.get_position(entity)),
caller
parent
)
)

Expand Down
2 changes: 1 addition & 1 deletion pygoap/goals.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
finished successfully.
"""

from blackboard import MemoryManager
from memory import MemoryManager
from precepts import *
import sys, logging

Expand Down
Loading

0 comments on commit 6a2c543

Please sign in to comment.