diff --git a/Fusion_Graph.py b/Fusion_Graph.py index 13bcf31..3c53a27 100644 --- a/Fusion_Graph.py +++ b/Fusion_Graph.py @@ -12,6 +12,7 @@ import struct import sys +import time class Game: @@ -28,18 +29,9 @@ class Game: self.minorItemLocations = list() self.itemLocations = set() self.patcher = dict() - self.graph.clear() - self.areaConnections.clear() - self.areaConnectionOffsets.clear() - self.doorConnections.clear() - self.rooms.clear() - self.requirements.clear() - self.visited.clear() - self.queue.clear() - self.majorItemLocations.clear() - self.minorItemLocations.clear() - self.itemLocations.clear() - self.patcher.clear() + self.oldtime = 0.0 + self.bfstime = 0.0 + self.dfstime = 0.0 # print('DEBUG: Opening ROM to pick stuff') @@ -96,6 +88,8 @@ class Game: sys.exit(1) + def print_stats(self): + print("Path search time: old search = {}s, bfs time = {}s, dfs time = {}s".format(self.oldtime, self.bfstime, self.dfstime)) def set_setting(self, setting, value): self.settings[setting] = value @@ -209,6 +203,15 @@ class Game: return self.requirements.get(checkRequirement) def get_path(self, start, end, LimitArea=False, path=None, depth=100): + #return [start,end] if self.has_path(start,end,LimitArea, depth) else None + + t = time.perf_counter() + path = self._get_path(start, end, LimitArea, path, depth) + self.oldtime += time.perf_counter() - t + + return path + + def _get_path(self, start, end, LimitArea, path, depth): if path == None: self.visited.clear() self.queue.clear() @@ -240,12 +243,118 @@ class Game: node = edge[1] pathReqs = self.get_requirements(start, node) if pathReqs == None: - newpath = self.get_path(node, end, LimitArea, path, depth) + newpath = self._get_path(node, end, LimitArea, path, depth) if newpath: path = path + [node] return newpath elif pathReqs == True: - newpath = self.get_path(node, end, LimitArea, path, depth) + newpath = self._get_path(node, end, LimitArea, path, depth) if newpath: path = path + [node] return newpath + + def has_path(self, start, end, LimitArea=False, depth=100): + if LimitArea: + area = self.itemArea.get(start) + if area == None: + for n in range(0, 7): + if 'S{}'.format(n) in start: + area = n + if area == None: + for n in range(0, 7): + if 'S{}'.format(n) in end: + area = n + else: + area = None + t = time.perf_counter() + result = self.has_path_bfs(start, end, area=area, max_depth=depth) + self.bfstime += time.perf_counter() - t + t = time.perf_counter() + r2 =self.has_path_dfs(start, end, area=area) + assert result == r2, (start, end, result, r2, area) + self.dfstime += time.perf_counter() - t + return result + + # NOTE: both the start and end node need to be excluded from + # area checks because they can be things like bosses or the Water Pump + # that aren't associated with an area. all the intermediate nodes + # should be doors, and doors all have their sector number in their names. + + def has_path_bfs(self, start, end, area=None, max_depth=100): + if start not in self.graph: + return False + if start == end: + return True + if area != None: + areaStr = 'S{}'.format(area) + seen = {start} + frontier = [start] + next_frontier = [] + depth = 0 + while frontier: + depth += 1 + #if depth > max_depth: break + for node in frontier: + for neighbor in self.graph[node]: + if neighbor in seen: + continue + if neighbor in self.itemLocations: + if neighbor == end: + pathReqs = self.get_requirements(node, neighbor) + if pathReqs == None or pathReqs == True: + return True + #seen.add(neighbor) + continue + if area is not None: + if areaStr not in neighbor and neighbor != end: + continue + pathReqs = self.get_requirements(node, neighbor) + if pathReqs != None and pathReqs != True: + continue + if neighbor == end: + return True + + next_frontier.append(neighbor) + + # since this is a breadth-first search on an unweighted graph, + # we know that no other paths can be shorter than this one, + # so we can immediately mark the node as seen to prevent + # adding it to the frontier again + seen.add(neighbor) + + frontier.clear() + frontier, next_frontier = next_frontier, frontier + + return False + + def has_path_dfs(self, start, end, area=None): + if start not in self.graph: + return False + if start == end: + return True + visited = set() + stack = [start] + if area != None: + areaStr = 'S{}'.format(area) + while stack: + node = stack.pop() + if node in visited: + continue + visited.add(node) + if node == end: + return True + if node is not start: + if node in self.itemLocations: + continue + if area is not None: + if areaStr not in node: + continue + + for neighbor in reversed(self.graph[node]): + if neighbor in visited: + continue + pathReqs = self.get_requirements(node, neighbor) + if pathReqs != None and pathReqs != True: + continue + stack.append(neighbor) + return False diff --git a/Randomizer.py b/Randomizer.py index 2b6083f..71fce93 100644 --- a/Randomizer.py +++ b/Randomizer.py @@ -3135,6 +3135,9 @@ def start_randomizer(rom, settings): seedTime = time.time() - startTime print(str(FileName)) print('Randomized in:', seedTime) + World.print_stats() + print("water lowered:", WaterLowered) + print("len(world.graph) = ", len(World.graph)) totalRandoTime = time.time() - totalRandoTime print('All seeds took:', totalRandoTime)