def _astar(X, start, goal, approx=0): """Find a path through X from start to goal. Args: - X: a 3d array of obstacles, i.e. False -> passable, True -> not passable - start/goal: relative positions in X - approx: proximity to goal before search is complete (0 = exact) Returns: a list of relative positions, from start to goal """ start = tuple(start) goal = tuple(goal) visited = set() came_from = {} q = PriorityQueue() q.push(start, manhat_dist(start, goal)) G = np.full_like(X, np.iinfo(np.uint32).max, "uint32") G[start] = 0 while len(q) > 0: _, p = q.pop() if manhat_dist(p, goal) <= approx: path = [] while p in came_from: path.append(p) p = came_from[p] return [start] + list(reversed(path)) visited.add(p) for a in adjacent(p): if (a in visited or a[0] < 0 or a[0] >= X.shape[0] or a[1] < 0 or a[1] >= X.shape[1] or a[2] < 0 or a[2] >= X.shape[2] or X[a]): continue g = G[p] + 1 if g >= G[a]: continue came_from[a] = p G[a] = g f = g + manhat_dist(a, goal) if q.contains(a): q.replace(a, f) else: q.push(a, f) return None
def step(self, agent): super().step(agent) if self.finished: return self.interrupted = False # wait certain amount of ticks until issuing next step # while not (agent.memory.get_time() - self.last_stepped_time) > self.throttling_tick: # pass # replace blocks if possible R = self.replace.copy() self.replace.clear() for (pos, idm) in R: agent.set_held_item(idm) if agent.place_block(*pos): logging.info("Move: replaced {}".format((pos, idm))) else: # try again later self.replace.add((pos, idm)) if len(self.replace) > 0: logging.info("Replace remaining: {}".format(self.replace)) # check if finished if manhat_dist(tuple(agent.pos), self.target) <= self.approx: if len(self.replace) > 0: logging.error( "Move finished with non-empty replace set: {}".format( self.replace)) self.finished = True if self.memid is not None: locmemid = agent.memory.add_location(self.target) locmem = agent.memory.get_location_by_id(locmemid) agent.memory.update_recent_entities(mems=[locmem]) agent.memory.add_triple(subj=self.memid, pred_text="task_effect_", obj=locmemid) chat_mem_triples = agent.memory.get_triples( subj=None, pred_text="chat_effect_", obj=self.memid) if len(chat_mem_triples) > 0: chat_memid = chat_mem_triples[0][0] agent.memory.add_triple(subj=chat_memid, pred_text="chat_effect_", obj=locmemid) return # get path if self.path is None or tuple(agent.pos) != self.path[-1]: self.path = search.astar(agent, self.target, self.approx) if self.path is None: self.handle_no_path(agent) return # take a step on the path assert tuple(agent.pos) == self.path.pop() step = tuple(self.path[-1] - agent.pos) step_fn = getattr(agent, self.STEP_FNS[step]) step_fn() self.last_stepped_time = agent.memory.get_time()
def get_next_destroy_target(self, agent, xyzs): p = agent.pos for i, c in enumerate(sorted(xyzs, key=lambda c: manhat_dist(p, c))): path = search.astar(agent, c, approx=2) if path is not None: if i > 0: logging.debug("Destroy get_next_destroy_target wasted {} astars".format(i)) return c # No path to any of the blocks return None
def closest_nearby_object(get_blocks, pos): """Find the closest interesting object to pos Returns a list of ((x,y,z), (id, meta)), or None if no interesting objects are nearby """ objects = all_nearby_objects(get_blocks, pos) if len(objects) == 0: return None centroids = [np.mean([pos for (pos, idm) in obj], axis=0) for obj in objects] dists = [manhat_dist(c, pos) for c in centroids] return objects[np.argmin(dists)]
def find_nearby_new_item_stack(self, agent, id, meta): mindist = 3 near_new_item_stack = None x, y, z = agent.get_player().pos for item_stack in agent.get_item_stacks(): if item_stack.item.id == id and item_stack.item.meta == meta: dist = manhat_dist( (item_stack.pos.x, item_stack.pos.y, item_stack.pos.z), (x, y, z)) if dist < mindist: if not agent.memory.get_entity_by_eid(item_stack.entityId): mindist = dist near_new_item_stack = item_stack return mindist, near_new_item_stack
def find_closest_component(mask, relpos): """Find the connected component of nonzeros that is closest to loc Args: - mask is a 3d array - relpos is a relative position in the mask, with the same ordering Returns: a list of indices of the closest connected component, or None """ components = connected_components(mask) if len(components) == 0: return None centroids = [np.mean(cs, axis=0) for cs in components] dists = [manhat_dist(c, relpos) for c in centroids] return components[np.argmin(dists)]
def find_nearby_new_mob(self, agent): mindist = 1000000 near_new_mob = None x, y, z = self.pos y = y + 1 for mob in agent.get_mobs(): if MOBS_BY_ID[mob.mobType] == self.mobtype: dist = manhat_dist((mob.pos.x, mob.pos.y, mob.pos.z), (x, y, z)) # hope this doesn;t take so long mob gets away... if dist < mindist: # print(MOBS_BY_ID[mob.mobType], dist) if not agent.memory.get_entity_by_eid(mob.entityId): mindist = dist near_new_mob = mob return mindist, near_new_mob
def step(self, agent): super().step(agent) if self.finished: return # wait certain amount of ticks until issuing next step # while not (agent.memory.get_time() - self.last_stepped_time) > self.throttling_tick: # pass if manhat_dist(agent.pos, self.pos) > self.PLACE_REACH: task = Move(agent, { "target": self.pos, "approx": self.PLACE_REACH }) self.add_child_task(task, agent) else: agent.set_held_item(self.object_idm) if np.equal(self.pos, agent.pos).all(): agent.step_neg_z() x, y, z = self.pos y = y + 1 agent.place_block(x, y, z) time.sleep(0.1) mindist, placed_mob = self.find_nearby_new_mob(agent) if mindist < 3: memid = MobNode.create(agent.memory, placed_mob, agent_placed=True) mobmem = agent.memory.get_mem_by_id(memid) agent.memory.update_recent_entities(mems=[mobmem]) if self.memid is not None: agent.memory.add_triple(subj=self.memid, pred_text="task_effect_", obj=mobmem.memid) # the chat_effect_ triple was already made when the task is added if there was a chat... # but it points to the task memory. link the chat to the mob memory: chat_mem_triples = agent.memory.get_triples( subj=None, pred_text="chat_effect_", obj=self.memid) if len(chat_mem_triples) > 0: chat_memid = chat_mem_triples[0][0] agent.memory.add_triple(subj=chat_memid, pred_text="chat_effect_", obj=mobmem.memid) self.finished = True
def step(self): super().step() agent = self.agent if self.finished: return self.interrupted = False # replace blocks if possible R = self.replace.copy() self.replace.clear() for (pos, idm) in R: agent.set_held_item(idm) if agent.place_block(*pos): logging.debug("Move: replaced {}".format((pos, idm))) else: # try again later self.replace.add((pos, idm)) if len(self.replace) > 0: logging.debug("Replace remaining: {}".format(self.replace)) # check if finished if manhat_dist(tuple(agent.pos), self.target) <= self.approx: if len(self.replace) > 0: logging.error( "Move finished with non-empty replace set: {}".format( self.replace)) self.finished = True return # get path if self.path is None or tuple(agent.pos) != self.path[-1]: self.path = search.astar(agent, self.target, self.approx) if self.path is None: self.handle_no_path(agent) return # take a step on the path assert tuple(agent.pos) == self.path.pop() step = tuple(self.path[-1] - agent.pos) step_fn = getattr(agent, self.STEP_FNS[step]) step_fn() self.last_stepped_time = agent.memory.get_time()
def get_next_place_target(self, agent, current, diff): """Return the next block that will be targeted for placing In order: 1. don't build over your own body 2. build ground-up 3. try failed blocks again at the end 4. build closer blocks first Args: - current: yzxb-ordered current state of the region - diff: a yzx-ordered boolean mask of blocks that need addressing """ relpos_yzx = (agent.pos - self.origin)[[1, 2, 0]] diff_yzx = list(np.argwhere(diff)) diff_yzx.sort(key=lambda yzx: manhat_dist(yzx, relpos_yzx)) # 4 diff_yzx.sort(key=lambda yzx: -self.attempts[tuple(yzx)]) # 3 diff_yzx.sort(key=lambda yzx: yzx[0]) # 2 diff_yzx.sort(key=lambda yzx: tuple(yzx) in (tuple(relpos_yzx), tuple(relpos_yzx + [1, 0, 0]))) # 1 return diff_yzx[0]
def step(self, agent): super().step(agent) if self.finished: return self.interrupted = False # wait certain amount of ticks until issuing next step # while not (agent.memory.get_time() - self.last_stepped_time) > self.throttling_tick: # pass # get blocks occupying build area ox, oy, oz = self.origin sy, sz, sx, _ = self.schematic.shape current = agent.get_blocks(ox, ox + sx - 1, oy, oy + sy - 1, oz, oz + sz - 1) # are we done? # TODO: diff ignores block meta right now because placing stairs and # chests in the appropriate orientation is non-trivial diff = ( (current[:, :, :, 0] != self.schematic[:, :, :, 0]) & (self.attempts > 0) & np.isin(current[:, :, :, 0], BUILD_IGNORE_BLOCKS, invert=True)) # ignore negative blocks if there is already air there diff &= (self.schematic[:, :, :, 0] + current[:, :, :, 0]) >= 0 if self.embed: diff &= self.schematic[:, :, :, 0] != 0 # don't delete blocks if self.embed for pair in BUILD_INTERCHANGEABLE_PAIRS: diff &= np.isin(current[:, :, :, 0], pair, invert=True) | np.isin( self.schematic[:, :, :, 0], pair, invert=True) if not np.any(diff): self.finish(agent) return # blocks that would need to be removed remove_mask = diff & (current[:, :, :, 0] != 0) # destroy any blocks in the way (or any that are slated to be destroyed in schematic) # first rel_yzxs = np.argwhere(remove_mask) xyzs = set([(x + self.origin[0], y + self.origin[1], z + self.origin[2]) for (y, z, x) in rel_yzxs]) if len(xyzs) != 0: logging.info("Excavating {} blocks first".format(len(xyzs))) target = self.get_next_destroy_target(agent, xyzs) if target is None: logging.info("No path from {} to {}".format(agent.pos, xyzs)) agent.send_chat("There's no path, so I'm giving up") self.finished = True return if manhat_dist(agent.pos, target) <= self.DIG_REACH: success = agent.dig(*target) if success: agent.perception_modules[ "low_level"].maybe_remove_inst_seg(target) if self.is_destroy_schm: agent.perception_modules[ "low_level"].maybe_remove_block_from_memory( target, (0, 0)) else: agent.perception_modules[ "low_level"].maybe_add_block_to_memory( target, (0, 0), agent_placed=True) self.add_tags(agent, (target, (0, 0))) agent.get_changed_blocks() else: mv = Move(agent, {"target": target, "approx": self.DIG_REACH}) self.add_child_task(mv, agent) return # for a build task with destroy schematic, # it is done when all different blocks are removed elif self.is_destroy_schm: self.finish(agent) return # get next block to place yzx = self.get_next_place_target(agent, current, diff) idm = self.schematic[tuple(yzx)] current_idm = current[tuple(yzx)] # try placing block target = yzx[[2, 0, 1]] + self.origin logging.debug("trying to place {} @ {}".format(idm, target)) if tuple(target) in (tuple(agent.pos), tuple(agent.pos + [0, 1, 0])): # can't place block where you're standing, so step out of the way self.step_any_dir(agent) return if manhat_dist(agent.pos, target) <= self.PLACE_REACH: # block is within reach assert current_idm[0] != idm[0], "current={} idm={}".format( current_idm, idm) if current_idm[0] != 0: logging.debug("removing block {} @ {} from {}".format( current_idm, target, agent.pos)) agent.dig(*target) if idm[0] > 0: agent.set_held_item(idm) logging.debug("placing block {} @ {} from {}".format( idm, target, agent.pos)) x, y, z = target if agent.place_block(x, y, z): B = agent.get_blocks(x, x, y, y, z, z) if B[0, 0, 0, 0] == idm[0]: agent.perception_modules[ "low_level"].maybe_add_block_to_memory( (x, y, z), tuple(idm), agent_placed=True) changed_blocks = agent.get_changed_blocks() self.new_blocks.append(((x, y, z), tuple(idm))) self.add_tags(agent, ((x, y, z), tuple(idm))) else: logging.error( "failed to place block {} @ {}, but place_block returned True. \ Got {} instead.".format( idm, target, B[0, 0, 0, :])) else: logging.warn("failed to place block {} from {}".format( target, agent.pos)) if idm[0] == 6: # hacky: all saplings have id 6 agent.set_held_item([351, 15]) # use bone meal on tree saplings if len(changed_blocks) > 0: sapling_pos = changed_blocks[0][0] x, y, z = sapling_pos for _ in range( 6 ): # use at most 6 bone meal (should be enough) agent.use_item_on_block(x, y, z) changed_blocks = agent.get_changed_blocks() changed_block_poss = { block[0] for block in changed_blocks } # sapling has grown to a full tree, stop using bone meal if (x, y, z) in changed_block_poss: break self.attempts[tuple(yzx)] -= 1 if self.attempts[tuple( yzx)] == 0 and not self.giving_up_message_sent: agent.send_chat( "I'm skipping a block because I can't place it. Maybe something is in the way." ) self.giving_up_message_sent = True else: # too far to place; move first task = Move(agent, {"target": target, "approx": self.PLACE_REACH}) self.add_child_task(task, agent)