Compare commits

..

No commits in common. "wip-faster-paths" and "master" have entirely different histories.

2 changed files with 136 additions and 167 deletions

View File

@ -12,7 +12,6 @@
import struct
import sys
import time
class Game:
@ -27,11 +26,20 @@ class Game:
self.queue = list()
self.majorItemLocations = list()
self.minorItemLocations = list()
self.itemLocations = set()
self.itemLocations = list()
self.patcher = dict()
self.oldtime = 0.0
self.bfstime = 0.0
self.dfstime = 0.0
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()
# print('DEBUG: Opening ROM to pick stuff')
@ -88,8 +96,6 @@ 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
@ -176,25 +182,29 @@ class Game:
def add_to_majors(self, item):
self.itemLocations.add(item)
if item not in self.itemLocations:
self.itemLocations.append(item)
if item not in self.majorItemLocations:
self.majorItemLocations.append(item)
def add_list_to_majors(self, locations):
for item in locations:
self.itemLocations.add(item)
if item not in self.itemLocations:
self.itemLocations.append(item)
if item not in self.majorItemLocations:
self.majorItemLocations.append(item)
def add_to_minors(self, item):
self.itemLocations.add(item)
if item not in self.itemLocations:
self.itemLocations.append(item)
if item not in self.minorItemLocations:
self.minorItemLocations.append(item)
def add_list_to_minors(self, locations):
for item in locations:
self.itemLocations.add(item)
if item not in self.itemLocations:
self.itemLocations.append(item)
if item not in self.minorItemLocations:
self.minorItemLocations.append(item)
@ -203,15 +213,6 @@ 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()
@ -229,7 +230,7 @@ class Game:
continue
if point in path:
continue
if LimitArea and point != end:
if LimitArea:
for area in range(0, 7):
if 'S{}'.format(area) in start:
if 'S{}'.format(area) not in point:
@ -243,118 +244,12 @@ 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

View File

@ -1624,8 +1624,8 @@ def update_requirements(graph):
def find_available_areas(graph):
AreaOpen[int(StartLocation[1:2])] = StartLocation
check = int(StartLocation[1:2])
AreaOpen[check] = StartLocation
if AreaOpen[0] == None:
check = 'S0-32'
path = graph.get_path(StartLocation, check)
@ -1633,33 +1633,94 @@ def find_available_areas(graph):
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[0] = check
def check_sector(area, check, tunnel1, tunnel2):
if AreaOpen[area] == None:
path = graph.get_path(StartLocation, check)
if AreaOpen[1] == None:
check = 'S1-00'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
path = graph.get_path(check, StartLocation)
AreaOpen[1] = check
if ScrewAttack:
if AreaOpen[1] == None:
check = 'S1-6B'
path = graph.get_path(StartLocation, check)
if path != None:
AreaOpen[area] = check
if ScrewAttack:
if AreaOpen[area] == None:
path = graph.get_path(StartLocation, tunnel1)
path = graph.get_path(check, StartLocation)
if path != None:
path = graph.get_path(tunnel1, StartLocation)
if path != None:
AreaOpen[area] = tunnel1
if AreaOpen[area] == None:
path = graph.get_path(StartLocation, tunnel2)
AreaOpen[1] = check
if AreaOpen[1] == None:
check = 'S1-68'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
path = graph.get_path(tunnel2, StartLocation)
if path != None:
AreaOpen[area] = tunnel2
check_sector(1, 'S1-00', 'S1-6B', 'S1-68')
check_sector(2, 'S2-00', 'S2-7F', 'S2-82')
check_sector(3, 'S3-00', 'S3-56', 'S3-59')
check_sector(4, 'S4-00', 'S4-6A', 'S4-6C')
AreaOpen[1] = check
if AreaOpen[2] == None:
check = 'S2-00'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[2] = check
if ScrewAttack:
if AreaOpen[2] == None:
check = 'S2-7F'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[2] = check
if AreaOpen[2] == None:
check = 'S2-82'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[2] = check
if AreaOpen[3] == None:
check = 'S3-00'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[3] = check
if ScrewAttack:
if AreaOpen[3] == None:
check = 'S3-56'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[3] = check
if AreaOpen[3] == None:
check = 'S3-59'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[3] = check
if AreaOpen[4] == None:
check = 'S4-00'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[4] = check
if ScrewAttack:
if AreaOpen[4] == None:
check = 'S4-6A'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[4] = check
if AreaOpen[4] == None:
check = 'S4-6C'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[4] = check
if AreaOpen[5] == None:
check = 'S5-00'
path = graph.get_path(StartLocation, check)
@ -1675,9 +1736,28 @@ def find_available_areas(graph):
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[5] = check
check_sector(6, 'S6-00', 'S6-51', 'S6-54')
if AreaOpen[6] == None:
check = 'S6-00'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[6] = check
if ScrewAttack:
if AreaOpen[6] == None:
check = 'S6-51'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[6] = check
if AreaOpen[6] == None:
check = 'S6-54'
path = graph.get_path(StartLocation, check)
if path != None:
path = graph.get_path(check, StartLocation)
if path != None:
AreaOpen[6] = check
if BlueDoors == False:
path = graph.get_path(StartLocation, 'Security-Level-1', depth=250)
if path != None:
@ -2173,7 +2253,6 @@ def randomize_game(graph):
find_available_areas(graph)
for location in MajorLocations:
# potential find_all_available_items
path = graph.get_path(StartLocation, location)
if path != None:
AccessibleLocations.append(location)
@ -2250,9 +2329,8 @@ def randomize_game(graph):
if location not in UsedLocations:
if location not in AccessibleLocations:
if location == 'Data S0' or location == 'Item S0-05-16':
path = graph.get_path(StartLocation, location, depth=250)
path = graph.get_path(location, StartLocation, depth=250)
else:
# potential find_all_available_items
for area in range(0, 7):
if location in AreaItemLocations[area]:
if AreaOpen[area]:
@ -2324,7 +2402,6 @@ def randomize_game(graph):
tankCheck = math.ceil(healthCheck / MissileDamage / 5)
PossibleMissileTanks.clear()
if SeedSettings['MajorMinor']:
# potential find_all_available_items + could probably reuse previous result
for missileLocation in MinorLocations:
if missileLocation not in UsedLocations:
missilePath = None
@ -2851,9 +2928,9 @@ def patch_game():
itemProgression.update({ UsedLocations[x]: PlacedItems[x] })
spoilerLog.update({ 'Item order': itemProgression })
itemDict = dict()
itemLocations = sorted(World.itemLocations)
for x in range(0, len(itemLocations)):
itemDict.update({ itemLocations[x]: 0 })
World.itemLocations.sort()
for x in range(0, len(World.itemLocations)):
itemDict.update({ World.itemLocations[x]: 0 })
for x in range(0, len(PlacedItems)):
itemDict.update({ UsedLocations[x]: PlacedItems[x] })
spoilerLog.update({ 'Items': itemDict })
@ -3058,9 +3135,6 @@ 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)