Пример #1
0
class AStar(object):
    def __init__(self, dimension=None, start_coords=None, end_coords=None, max_cost=config.PATHFIND_LIMIT):
        self.dimension = dimension
        self.grid = dimension.grid
        self.start_node = PathNode(start_coords)
        self.goal_node = PathNode(end_coords)
        self.gridspace = GridSpace(self.grid)
        self.max_cost = max_cost
        self.path = None
        self.closed_set = set()
        goal_state = self.gridspace.get_state_coords(end_coords)
        if goal_state.can_stand or goal_state.can_hold:
            self.open_heap = [self.start_node]
            self.open_set = set([self.start_node])
        else:
            self.open_heap = []
            self.open_set = set([])
        self.start_node.set_score(0, self.heuristic_cost_estimate(self.start_node, self.goal_node))
        self.iter_count = 0
        self.t_start = time.time()

    def reconstruct_path(self, current):
        nodes = []
        nodes.append(current)
        while current.parent is not None:
            nodes.append(current.parent)
            current = current.parent
        nodes.reverse()
        return nodes

    def get_edge_cost(self, node_from, node_to):
        return config.COST_DIRECT

    def neighbours(self, node):
        for state in self.gridspace.neighbours_of(node.coords):
            if state.coords not in self.closed_set:
                yield PathNode(state.coords)

    def heuristic_cost_estimate(self, start, goal):
        adx = abs(start.coords.x - goal.coords.x)
        adz = abs(start.coords.z - goal.coords.z)
        h_diagonal = min(adx, adz)
        h_straight = adx + adz
        h = config.COST_DIAGONAL * h_diagonal + config.COST_DIRECT * (h_straight - 2 * h_diagonal)
        return h

    def next(self):
        self.iter_count += 1
        if not self.open_set:
            log.msg("time consumed %s sec, made %d iterations" % (time.time() - self.t_start, self.iter_count))
            log.err("Did not find path between %s and %s" % (self.start_node.coords, self.goal_node.coords))
            raise StopIteration()
        x = heapq.heappop(self.open_heap)
        if x == self.goal_node:
            self.path = Path(dimension=self.dimension, nodes=self.reconstruct_path(x))
            self.gridspace = None
            log.msg(
                "finished in %s sec, length %d, made %d iterations"
                % (time.time() - self.t_start, len(self.path.nodes), self.iter_count)
            )
            log.msg("nodes %s" % self.path.nodes)
            raise StopIteration()
        self.open_set.remove(x)
        self.closed_set.add(x.coords)
        for y in self.neighbours(x):
            if y.coords in self.closed_set:
                continue
            tentative_g_core = x.g + self.get_edge_cost(x, y)
            if y not in self.open_set or tentative_g_core < y.g:
                y.set_score(tentative_g_core, self.heuristic_cost_estimate(y, self.goal_node))
                y.parent = x
                if y not in self.open_set:
                    heapq.heappush(self.open_heap, y)
                    self.open_set.add(y)
                if y.step > self.max_cost:
                    log.err(
                        "Finding path over limit between %s and %s" % (self.start_node.coords, self.goal_node.coords)
                    )
                    raise StopIteration()
Пример #2
0
class AStar(object):
#TODO: explore limiting by execution time rather than by path distance

    def __init__(self, dimension=None, start_coords=None, end_coords=None,
                 path_max=config.PATHFIND_MAX, estimate=True):
        self.t_start = time.time()
        self.dimension = dimension
        self.grid = dimension.grid
        self.start_node = PathNode(start_coords)
        self.goal_node = PathNode(end_coords)
        self.gridspace = GridSpace(self.grid)
        # keep max_cost between the configured values
        conf_max, conf_min = config.PATHFIND_MAX, config.PATHFIND_MIN
        values = [conf_min, path_max, conf_max]
        self.max_cost = list(sorted(values))[1]
        self.path = None
        self.closed_set = set()
        goal_state = self.gridspace.get_state_coords(end_coords)
        if goal_state.can_stand or goal_state.can_hold or estimate:
            self.open_heap = [self.start_node]
            self.open_set = set([self.start_node])
        else:
            self.open_heap = []
            self.open_set = set([])
        self.start_node.set_score(0, self.heuristic_cost_estimate(
                                              self.start_node, self.goal_node))
        self.iter_count = 0
        self.best = self.start_node
        self.estimate = estimate
        self.excessive = config.PATHFIND_EXEC_TIME_LIMIT

        self.distance = self.start_node.coords.distance(self.goal_node.coords)
        self.start = time.time()

    def get_edge_cost(self, node_from, node_to,
                      x_neighbors=None, y_neighbors=None):
        return config.COST_DIRECT

    def neighbours(self, node):
        for state in self.gridspace.neighbours_of(node.coords):
            if state.coords not in self.closed_set:
                yield PathNode(state.coords)

    def heuristic_cost_estimate(self, start, goal):
        """Takes a path node, and tries to estimate the cost to the goal."""
#        y = start.coords.y - goal.coords.y
#        adx = abs(start.coords.x - goal.coords.x)
#        adz = abs(start.coords.z - goal.coords.z)
#
#        fall, rise = (abs(y), 0) if y < 0 else (0, abs(y))
#        h_diagonal = min(adx, adz)
#        h_straight = adx + adz
#        h = (config.COST_DIAGONAL * h_diagonal +
#             config.COST_DIRECT * (h_straight - 2 * h_diagonal) +
#             config.COST_FALL * fall +
#             config.COST_JUMP * rise)
        vertical = start.coords.y - goal.coords.y
        if vertical < 0:
            vertical = abs(vertical) * config.COST_FALL
        else:
            vertical = vertical * config.COST_JUMP
        distance = start.coords.distance(goal.coords)
        h = distance + vertical
        return h

    def _excessive_path(self, start):
        """Cheap evalutation of whether this path is excessive, only usable on
        a scored node."""
        # Pathfinding is the most expensive operation in a tick.
        # self.excessive should ideally be less than 1/20th of a second, but
        # can realistically go higher.
        # this means the pathfinding effectiveness of the bot is affected by
        # the speed of the machine it's on -- and by it's cost.
        return time.time() - self.start > self.excessive
#        excessive = start.step > self.distance * self.excessive
#        if excessive:
#            debug and log.msg(msg % (start.h, start.g))
#        return excessive

    def report(self):
        nodes = ''
        if not self.path:
            path = '<PATH NOT FOUND>'
        else:
            estimated = '' if self.path.estimated else "(estimated)"
            path = 'path length %s %s' % (self.best.step, estimated)
            nodes = 'Nodes: %s' % self.path.nodes
        msg = "Finished in %s sec, %s iterations, %s"
        log.msg(msg % (time.time() - self.t_start, self.iter_count, path))
        debug and log.msg(nodes)

    def finish(self):
        estimated = self.best.coords != self.goal_node.coords
        if not estimated or (estimated and self.estimate):
            self.path = Path(dimension=self.dimension,
                             nodes=self.best.path, estimated=estimated)
        self.gridspace = None
        self.report()

    def next(self):
        self.iter_count += 1
        if not self.open_set:
            self.finish()
            raise StopIteration()
        x = heapq.heappop(self.open_heap)
        if x.coords == self.goal_node.coords:
            self.best = x
            self.finish()
            raise StopIteration()
        self.open_set.remove(x)
        self.closed_set.add(x.coords)
        x_neighbours = self.neighbours(x)
        for y in x_neighbours:
            if y.coords in self.closed_set:
                continue
            tentative_g_core = x.g + self.get_edge_cost(x, y, x_neighbours)
            if y not in self.open_set or tentative_g_core < y.g:
                y.set_score(tentative_g_core,
                            self.heuristic_cost_estimate(y, self.goal_node))
                if y.h < self.best.h:
                    self.best = y
                y.parent = x
                if y not in self.open_set:
                    heapq.heappush(self.open_heap, y)
                    self.open_set.add(y)
                if self._excessive_path(y):
                    msg = "Find path timed out at %s steps between %s and %s"
                    log.msg(msg % (y.step, self.start_node.coords,
                                   self.goal_node.coords))
                    self.finish()
                    raise StopIteration()
                if y.step > self.max_cost:
                    msg = "Find path over limit %s between %s and %s"
                    log.msg(msg % (self.max_cost, self.start_node.coords,
                                   self.goal_node.coords))
                    self.finish()
                    raise StopIteration()