Permalink
Browse files

add pygoap for testing

  • Loading branch information...
1 parent 5196ec0 commit cc22f968167510f91169dad82f0ddab7d7d13503 @bitcraft committed Apr 14, 2012
View
Binary file not shown.
View
@@ -0,0 +1,83 @@
+how to handle ai? multiprocessing to get around the GIL in CPython.
+why not threads? because we are CPU restricted, not IO.
+
+memory managers and blackboards should be related.
+this will allow for expectations, since a memory can be simulated in the future
+
+memory:
+should be a heap
+memory added will be wrapped with a counter
+every time a memory is fetched, the counter will be added
+eventually, memories not being used will be removed
+and the counters will be reset
+
+memories should be a tree
+if a memory is being added that is similar to an existing memory,
+then the existing memory will be updated, rather than replaced.
+
+since goals and prereqs share a common function, "valid", it makes sene to
+make them subclasses of a common class.
+
+looking at actions.csv, it is easy to see that the behaviour of the agent will
+be largely dependent on how well the action map is defined. with the little
+pirate demo, it is not difficult to model his behaviour, but with larger, more
+complex agents, it could quickly become a huge task to write and verify the
+action map.
+
+with some extra steps, i would like to make it possible that the agent can
+infer the prereq's to a goal through clues provided within the objects that the
+agent interacts with, rather than defining them within the class. i can foresee
+a performance penality for this, but that could be offset by constructing
+training environments for the agent and then storing the action map that the
+agent creates during training.
+
+the planner:
+GOAP calls for a heuristic to be used to find the optimal solution and to reduce
+the number of checks made. in a physical environment where a star is used,
+it makes sense to just find a vector from the current searched node to the
+goal, but in action planning, there is no spatial dimension where a simple
+solution like that can be used.
+
+without a heuristic, a* is just a tree search. the heuristic will increase the
+efficiency of the planner and possibly give more consistent results. it can
+also be used to guide an agents behavior by manipulating some values.
+
+for now, while testing and building the library, the h value will not be used.
+when the library is more complete, it would make sense to build a complete
+agent, then construct a set of artificial scenarios to train the agent.
+based on data from the scenarios, it could be possible to hardcode the h
+values. the planner could then be optimized for certain scenarios.
+
+This module contains the most commonly used parts of the system. Classes that
+have many related sibling classes are in other modules.
+
+
+since planning is done on the blackboard and i would like agents to be able to
+make guesses, or plans about other agents, then agents will have to somehow be
+able to be stored on and manipulated on a blackboard. this may meant that
+agents and precepts will be the same thing
+
+1/15/12:
+overhauled the concepts of goals, prereqs, and effects and rolled them into
+one class. with the new system of instanced actions, it makes design sense to
+consolidate them, since they all have complimentary functionality. from a
+performance standpoint, it may make sense to keep the separate, but this way is
+much easier to conceptualize in your mind, and i am not making an system that
+is performance sensitive....this is python.
+
+simplify holding/inventory:
+ an objects location should always be a tuple of:
+ ( holding object, position )
+
+this will make position very simple to sort. the position in the tuple should
+be a value the that holding object can make sense of, for example, an
+environment might expect a zone, and (x,y), while an agent would want an index
+number in their inventory.
+
+a side effect will be that location goals and holding goals can be consolidated
+into one function.
+
+new precepts may render a current plan invalid. to account for this, an agent
+will re-plan every time it receives a precept. a better way would be to tag a
+type of precept and then if a new one that directly relates to the plan arrives
+then re-plan.
View
@@ -0,0 +1 @@
+
View
Binary file not shown.
View
@@ -0,0 +1,193 @@
+"""
+These are the building blocks for creating pyGOAP agents that are able to
+interact with their environment in a meaningful way.
+
+When actions are updated, they can return a precept for the environment to
+process. The action can emit a sound, sight, or anything else for other
+objects to consume.
+
+These classes will be known to an agent, and chosen by the planner as a means
+to satisfy the current goal. They will be instanced and the the agent will
+execute the action in some way, one after another.
+
+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
+actions for the bb.
+"""
+
+from planning import *
+from actionstates import *
+import sys
+
+
+test_fail_msg = "some goal is returning None on a test, this is a bug."
+
+class ActionBuilder(object):
+ """
+ ActionBuilders examine a blackboard and return a list of actions
+ that can be successfully completed at the the time.
+ """
+
+ def get_actions(self, caller, bb):
+ raise NotImplementedError
+
+ def __init__(self, **kwargs):
+ self.prereqs = []
+ self.effects = []
+ self.costs = {}
+
+ self.__dict__.update(kwargs)
+
+ self.setup()
+
+ def setup(self):
+ """
+ add the prereqs, effects, and costs here
+ override this
+ """
+ pass
+
+ def __repr__(self):
+ return "<ActionBuilder: {}>".format(self.__class__.__name__)
+
+
+class CallableAction(InstancedAction):
+ """
+ callable action class.
+
+ subclass this class to implement the code side of actions.
+ for the most part, "start" and "update" will be the most
+ important methods to overload.
+ """
+
+ def __init__(self, caller, **kwargs):
+ self.caller = caller
+ self.state = ACTIONSTATE_NOT_STARTED
+
+ self.prereqs = []
+ self.effects = []
+ self.costs = {}
+
+ self.__dict__.update(kwargs)
+
+ def test(self, bb=None):
+ """
+ make sure the action is able to be started
+ return a float from 0-1 that describes how valid this action is.
+
+ validity of an action is a measurement of how effective the action
+ will be if it is completed successfully.
+
+ if any of the prereqs are not partially valid ( >0 ) then will
+ return 0
+
+ this value will be used in planning.
+
+ for many actions a simple 0 or 1 will work. for actions which
+ modify numerical values, it may be useful to return a fractional
+ value.
+ """
+
+ # NOTE: may be better written with itertools
+
+ if bb == None: raise Exception
+
+ if len(self.prereqs) == 0: return 1.0
+ total = [ i.test(bb) for i in self.prereqs ]
+ print "[goal] {} test {}".format(self, total)
+ #if 0 in total: return 0
+ try:
+ return float(sum(total)) / len(self.prereqs)
+ except TypeError:
+ print zip(total, self.prereqs)
+ print test_fail_msg
+ sys.exit(1)
+
+
+ def touch(self, bb=None):
+ """
+ call when the planning phase is complete
+ """
+ if bb == None: bb = self.caller.bb
+ [ i.touch(bb) for i in self.effects ]
+
+
+ def start(self):
+ """
+ start running the action
+ """
+ self.state = ACTIONSTATE_RUNNING
+
+
+ def update(self, time):
+ """
+ actions which occur over time should implement
+ this method.
+
+ if the action does not need more that one cycle, then
+ you should use the calledonceaction class
+ """
+ pass
+
+
+ def fail(self, reason=None):
+ """
+ maybe what we planned to do didn't work for whatever reason
+ """
+ self.state = ACTIONSTATE_FAILED
+
+
+ def abort(self):
+ """
+ stop the action without the ability to complete or continue
+ """
+ self.state = ACTIONSTATE_BAILED
+
+
+ def finish(self):
+ """
+ the planned action was completed and the result is correct
+ """
+ if self.state == ACTIONSTATE_RUNNING:
+ self.state = ACTIONSTATE_FINISHED
+
+
+ def ok_finish(self):
+ """
+ determine if the action can finish now
+ if cannot finish now, then the action
+ should bail if it is forced to finish.
+ """
+ return self.state == ACTIONSTATE_FINISHED
+
+
+ def pause(self):
+ """
+ stop the action from updating. should be able to continue.
+ """
+ self.state = ACTIONSTATE_PAUSED
+
+
+class CalledOnceAction(CallableAction):
+ """
+ Is finished immediately when started.
+ """
+
+ def start(self):
+ # valid might return a value less than 1
+ # this means that some of the prereqs are not
+ # completely satisfied.
+ # since we want everything to be completely
+ # satisfied, we require valid == 1.
+ if self.test() == 1.0:
+ CallableAction.start(self)
+ CallableAction.finish(self)
+ else:
+ self.fail()
+
+ def update(self, time):
+ pass
+
+
View
Binary file not shown.
@@ -0,0 +1,6 @@
+ACTIONSTATE_NOT_STARTED = 0
+ACTIONSTATE_FINISHED = 1
+ACTIONSTATE_RUNNING = 2
+ACTIONSTATE_PAUSED = 3
+ACTIONSTATE_ABORTED = 4
+ACTIONSTATE_FAILED = 5
Binary file not shown.
Oops, something went wrong.

0 comments on commit cc22f96

Please sign in to comment.