369 goap.py
@@ -6,250 +6,250 @@
'''
Okay, there are three levels of complexity we can choose
simplest: use boolean state
- library does this
- library does this
medium: use integer state
- we probbably want this
- requires modifying library
> actually, throwing lib away was easier
- requires different A* heuristic
> none, just be Dijkstra
bad heuristic is worse than no heuristic,
bad heuristic can also result in sub-optimal paths
- we probbably want this
- requires modifying library
> actually, throwing lib away was easier
- requires different A* heuristic
> none, just be Dijkstra
bad heuristic is worse than no heuristic,
bad heuristic can also result in sub-optimal paths
complex: use custom state with user-supplied functions and heuristic
- probbably overkill
- requires rewriting library
- requires custom functions for each action
- requires complex A* heuristic
- probbably overkill
- requires rewriting library
- requires custom functions for each action
- requires complex A* heuristic
'''


class Goal:
def __init__(self, state):
self.state = state # int dict
def __init__(self, state):
self.state = state # int dict

def met(self, teststate):
for (key, value) in self.state.iteritems():
if (teststate.get(key, 0) < value):
return False
return True
def met(self, teststate):
for (key, value) in self.state.iteritems():
if (teststate.get(key, 0) < value):
return False
return True


class Action:
def __repr__(self):
return self.name
def __repr__(self):
return self.name

def __init__(self, name, function, condition, expectation, cost=1):
self.name = name
self.function = function # function
self.condition = condition # int dict
self.expectation = expectation # int dict
self.cost = cost # int
def __init__(self, name, function, condition, expectation, cost=1):
self.name = name
self.function = function # function
self.condition = condition # int dict
self.expectation = expectation # int dict
self.cost = cost # int

def available(self, state):
for (key, value) in self.condition.iteritems():
if (state.get(key, 0) < value):
return False
return True
def available(self, state):
for (key, value) in self.condition.iteritems():
if (state.get(key, 0) < value):
return False
return True


class Node:
def __repr__(self):
return "\n(a{0}|{1})".format(self.state, self.prev)
def __repr__(self):
return "\n(a{0}|{1})".format(self.state, self.prev)

def __init__(self, state, prev, action):
self.state = state # int dict
self.prev = prev # Node
self.action = action # Action
def __init__(self, state, prev, action):
self.state = state # int dict
self.prev = prev # Node
self.action = action # Action


class Leaf:
def __repr__(self):
return "leaf {0} {1} {2}\n".format(self.prevAction, list(self.doneActions), self.node)
def __repr__(self):
return "leaf {0} {1} {2}\n".format(self.prevAction, list(self.doneActions), self.node)

def __init__(self, prevAction, node, aset):
self.prevAction = prevAction
self.node = node # Node
self.doneActions = aset # int set
def __init__(self, prevAction, node, aset):
self.prevAction = prevAction
self.node = node # Node
self.doneActions = aset # int set


def addDict(a, b):
ret = {}
for key in a:
ret[key] = a.get(key, 0) + b.get(key, 0)
for key in b:
ret[key] = a.get(key, 0) + b.get(key, 0)
return ret
ret = {}
for key in a:
ret[key] = a.get(key, 0) + b.get(key, 0)
for key in b:
ret[key] = a.get(key, 0) + b.get(key, 0)
return ret


# python 2.7 enum
class ActionReturn:
success, replanWithoutMe = range(2)
success = 0 # action was completed without issues
retry = -1 # retry action next simulation-tick
@staticmethod
def failure(secondsTimeout=10): # failure, replan and don't reconsider action until timeout
return secondsTimeout
success, replanWithoutMe = range(2)
success = 0 # action was completed without issues
retry = -1 # retry action next simulation-tick
@staticmethod
def failure(secondsTimeout=10): # failure, replan and don't reconsider action until timeout
return secondsTimeout

def findTrees(w):
ac = w["agentController"]
nav = ac.navigator
nav.findAndSet('log')
print '<Agent{}> finding trees! find find...'.format(w["id"])
return ActionReturn.success
ac = w["agentController"]
nav = ac.navigator
nav.findAndSet('log')
print '<Agent{}> finding trees! find find...'.format(w["id"])
return ActionReturn.success


def chopWood(w):
ac = w["agentController"]
nav = ac.navigator

if "chopWood" not in w:
w["chopWood"] = False
print '<Agent{}> chopping wood! chop chop...'.format(w["id"])

if not w["chopWood"]:
w["chopWood"] = True
w["foundTree"] = False
destination = nav.findAndSet('log', w["id"], w["filters"])
w["destination"] = destination
return ActionReturn.retry
elif not w["foundTree"]:
if nav.targetReached:
w["foundTree"] = True
else:
if ac.destroyBlock('log', w["destination"].location):
return ActionReturn.retry
else:
w["foundTree"] = False
w["chopWood"] = False
if w["destination"] is not None:
w["destination"].removeFlag(w["id"])
w["destination"].removeFlag("log")
ac.controller.setPitch(-10)
w["destination"] = None
return ActionReturn.success
else:
print "goap, w[destination] is None"
return ActionReturn.failure


return ActionReturn.success
ac = w["agentController"]
nav = ac.navigator

if "chopWood" not in w:
w["chopWood"] = False
print '<Agent{}> chopping wood! chop chop...'.format(w["id"])

if not w["chopWood"]:
w["chopWood"] = True
w["foundTree"] = False
destination = nav.findAndSet('log', w["id"], w["filters"])
w["destination"] = destination
return ActionReturn.retry
elif not w["foundTree"]:
if nav.targetReached:
w["foundTree"] = True
else:
if ac.destroyBlock('log', w["destination"].location):
return ActionReturn.retry
else:
w["foundTree"] = False
w["chopWood"] = False
if w["destination"] is not None:
w["destination"].removeFlag(w["id"])
w["destination"].removeFlag("log")
ac.controller.setPitch(-10)
w["destination"] = None
return ActionReturn.success
else:
print "goap, w[destination] is None"
return ActionReturn.failure


return ActionReturn.success


def craftTable(w):
w["agentController"].craft("crafting_table")
print '<Agent{}> crafting crafting table! table...'.format(w["id"])
return ActionReturn.success
w["agentController"].craft("crafting_table")
print '<Agent{}> crafting crafting table! table...'.format(w["id"])
return ActionReturn.success


def craftPlank(w):
w["agentController"].craft("planks")
print '<Agent{}> crafting planks! plank plank...'.format(w["id"])
return ActionReturn.success
w["agentController"].craft("planks")
print '<Agent{}> crafting planks! plank plank...'.format(w["id"])
return ActionReturn.success


def craftSticks(w):
w["agentController"].craft("stick")
print '<Agent{}> crafting sticks! stick stick...'.format(w["id"])
return ActionReturn.success
w["agentController"].craft("stick")
print '<Agent{}> crafting sticks! stick stick...'.format(w["id"])
return ActionReturn.success


def craftHoe(w):
w["agentController"].craft("wooden_hoe")
print '<Agent{}> crafting hoe! hoe hoe...'.format(w["id"])
return ActionReturn.success
w["agentController"].craft("wooden_hoe")
print '<Agent{}> crafting hoe! hoe hoe...'.format(w["id"])
return ActionReturn.success


def harvestGrain(w):
print '<Agent{}> harvesting grain! oh no! there are is no grain, so I will plant some and check on them later'.format(w["id"])
return ActionReturn.failure(5)
print '<Agent{}> harvesting grain! oh no! there are is no grain, so I will plant some and check on them later'.format(w["id"])
return ActionReturn.failure(5)


def bakeBread(w):
print '<Agent{}> baking bread! bake bake...'.format(w["id"])
return ActionReturn.success
print '<Agent{}> baking bread! bake bake...'.format(w["id"])
return ActionReturn.success


# dijkstra's algorithm using priority queues
def pathfind(goals, actions, startstate, bannedset):
root = Node(startstate, None, None)

leafs = [] # priority queue of leafs
heapq.heappush(leafs, (0, Leaf(None, root, bannedset)))

debug_node_expansions = 0

while leafs: # while not empty
if debug_node_expansions>=10000:
print 'reached max node expantions: %d' % debug_node_expansions
sys.exit()
debug_node_expansions += 1
(cost, leaf) = heapq.heappop(leafs)
for goal in goals:
if (goal.met(leaf.node.state)):
print 'node expansions %d' % debug_node_expansions
print leaf
return leaf
for action in actions:
if action.available(leaf.node.state) and (action == leaf.prevAction or action not in leaf.doneActions):
aset = leaf.doneActions.copy()
aset.add(action)
node = Node(addDict(leaf.node.state, action.expectation), leaf.node, action)
heapq.heappush(leafs, (cost + action.cost, Leaf(action, node, aset)))
return Leaf(0, root, bannedset)
root = Node(startstate, None, None)

leafs = [] # priority queue of leafs
heapq.heappush(leafs, (0, Leaf(None, root, bannedset)))

debug_node_expansions = 0

while leafs: # while not empty
if debug_node_expansions>=10000:
print 'reached max node expantions: %d' % debug_node_expansions
sys.exit()
debug_node_expansions += 1
(cost, leaf) = heapq.heappop(leafs)
for goal in goals:
if (goal.met(leaf.node.state)):
print 'node expansions %d' % debug_node_expansions
print leaf
return leaf
for action in actions:
if action.available(leaf.node.state) and (action == leaf.prevAction or action not in leaf.doneActions):
aset = leaf.doneActions.copy()
aset.add(action)
node = Node(addDict(leaf.node.state, action.expectation), leaf.node, action)
heapq.heappush(leafs, (cost + action.cost, Leaf(action, node, aset)))
return Leaf(0, root, bannedset)

# simple wrapper around pathfind to make it easier to use
def plan(startstate, bannedSet):
goals = np.array([
Goal({'bread':1}),
])
actions = np.array([
Action("craftTable", craftTable, {'planks': 4}, {'tables': 1, 'planks': -4}),
Action("craftPlank", craftPlank, {'logs': 1}, {'planks': 4, 'logs': -1}),
Action("chopWood", chopWood, {}, {'logs': 1}),
Action("craftHoe", craftHoe, {'tables': 1, 'planks': 2, 'sticks': 2}, {'hoes': 1, 'planks': -2, 'sticks': -2}),
Action("craftSticks", craftSticks, {'planks': 2}, {'sticks': 4, 'planks': -1}),
Action("harvestGrain", harvestGrain, {'hoes':1}, {'grain': 1}),
Action("bakeBread", bakeBread, {'tables': 1, 'grain': 3}, {'bread':1, 'grain':-3}),
])
print 'starting goap'
starttime = time.time()
leaf = pathfind(goals, actions, startstate, bannedSet)
endtime = time.time()
print 'done in %0.3f seconds' % (endtime - starttime)
node = leaf.node
path = []
while node != None:
if (node.action != None):
path.append(node.action)
node = node.prev
# reversed(list) does not reverse the list, but give you a special iterator
# so I'm using this funky syntax to _actually_ reverse the list
return path[::-1]
goals = np.array([
Goal({'bread':1}),
])
actions = np.array([
Action("craftTable", craftTable, {'planks': 4}, {'tables': 1, 'planks': -4}),
Action("craftPlank", craftPlank, {'logs': 1}, {'planks': 4, 'logs': -1}),
Action("chopWood", chopWood, {}, {'logs': 1}),
Action("craftHoe", craftHoe, {'tables': 1, 'planks': 2, 'sticks': 2}, {'hoes': 1, 'planks': -2, 'sticks': -2}),
Action("craftSticks", craftSticks, {'planks': 2}, {'sticks': 4, 'planks': -1}),
Action("harvestGrain", harvestGrain, {'hoes':1}, {'grain': 1}),
Action("bakeBread", bakeBread, {'tables': 1, 'grain': 3}, {'bread':1, 'grain':-3}),
])
print 'starting goap'
starttime = time.time()
leaf = pathfind(goals, actions, startstate, bannedSet)
endtime = time.time()
print 'done in %0.3f seconds' % (endtime - starttime)
node = leaf.node
path = []
while node != None:
if (node.action != None):
path.append(node.action)
node = node.prev
# reversed(list) does not reverse the list, but give you a special iterator
# so I'm using this funky syntax to _actually_ reverse the list
return path[::-1]

class ActionTimeout:
def __init__(self, action, timeout):
self.action = action
self.timeout = timeout
def __init__(self, action, timeout):
self.action = action
self.timeout = timeout

goap_gid = 0

class Goap:
def __init__(self, agentController, agentCount):
global goap_gid
self.meta = {}
self.meta["agentController"] = agentController
self.meta["id"] = goap_gid
self.meta["filters"] = [i+1 if i >= goap_gid else i for i in range(agentCount - 1)]
self.state = {} # dictionary of ints
self.timeouts = [] # array of ActionTimeouts
self.plan = [] # array of Actions
goap_gid += 1
self.state = {}

def updateState(self):
self.state = self.meta["agentController"].inventory

def execute(self):
def __init__(self, agentController, agentCount):
global goap_gid
self.meta = {}
self.meta["agentController"] = agentController
self.meta["id"] = goap_gid
self.meta["filters"] = [i+1 if i >= goap_gid else i for i in range(agentCount - 1)]
self.state = {} # dictionary of ints
self.timeouts = [] # array of ActionTimeouts
self.plan = [] # array of Actions
goap_gid += 1
self.state = {}

def updateState(self):
self.state = self.meta["agentController"].inventoryHandler.getCombinedDict()

def execute(self):
# first we check out which items are currently banned
currentTime = time.time()
self.timeouts = [timeout for timeout in self.timeouts if not timeout.timeout<currentTime]
@@ -259,6 +259,9 @@ def execute(self):
self.plan = plan(self.state, banned)
# then perform the action if there is a goal
if self.plan != []:
print "plan = {}".format(self.plan)
print "state = {}".format(self.state)

action = self.plan[0]
result = action.function(self.meta)
if result == ActionReturn.retry:
@@ -274,6 +277,6 @@ def execute(self):


if __name__ == '__main__':
goapInstance = Goap(None)
goapInstance.execute()
goapInstance = Goap(None)
goapInstance.execute()

@@ -125,6 +125,20 @@ def getItemAmount(self, itemName):



def getCombinedDict(self):
"""
Returns the combined dictionaries of both inventory and hotbar amounts,
where the final dict has name/amount key/value pairs.
"""
combined = {}
combined.update(self.inventoryAmount)

for itemName in self.hotbarAmount:
combined[itemName] = combined.get(itemName, 0) + self.hotbarAmount[itemName]

return combined


def getInventorySlot(self, itemName):
"""
Returns the first slot for the inventory in which the given item is, or
@@ -10,7 +10,7 @@
################################################################################

CUBE_OBS = "vision_cube" # Name that will be used in XML/JSON
CUBE_SIZE = 1 # Number of visible blocks in 1 direction
CUBE_SIZE = 6 # Number of visible blocks in 1 direction

ENTITIES_OBS = "entities" # Name that will be used in XML/JSON
ENTITIES_RANGE = 25 # Number of blocks in 1 direction that we can