mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
First Iteration (can handle no glitches open/standard logic), WIP
This commit is contained in:
375
BaseClasses.py
Normal file
375
BaseClasses.py
Normal file
@@ -0,0 +1,375 @@
|
||||
import copy
|
||||
|
||||
|
||||
class World(object):
|
||||
|
||||
def __init__(self):
|
||||
self.regions = []
|
||||
self.itempool = []
|
||||
self.state = CollectionState(self)
|
||||
self.required_medallions = ['Ether', 'Quake']
|
||||
self._cached_locations = None
|
||||
self._entrance_cache = {}
|
||||
self._region_cache = {}
|
||||
self._entrance_cache = {}
|
||||
self._location_cache = {}
|
||||
self._item_cache = {}
|
||||
|
||||
def get_region(self, regionname):
|
||||
if isinstance(regionname, Region):
|
||||
return regionname
|
||||
try:
|
||||
return self._region_cache[regionname]
|
||||
except KeyError:
|
||||
for region in self.regions:
|
||||
if region.name == regionname:
|
||||
self._region_cache[regionname] = region
|
||||
return region
|
||||
raise RuntimeError('No such region %s' % regionname)
|
||||
|
||||
def get_entrance(self, entrance):
|
||||
if isinstance(entrance, Entrance):
|
||||
return entrance
|
||||
try:
|
||||
return self._entrance_cache[entrance]
|
||||
except KeyError:
|
||||
for region in self.regions:
|
||||
for exit in region.exits:
|
||||
if exit.name == entrance:
|
||||
self._entrance_cache[entrance] = exit
|
||||
return exit
|
||||
raise RuntimeError('No such entrance %s' % entrance)
|
||||
|
||||
def get_location(self, location):
|
||||
if isinstance(location, Location):
|
||||
return location
|
||||
try:
|
||||
return self._location_cache[location]
|
||||
except KeyError:
|
||||
for region in self.regions:
|
||||
for r_location in region.locations:
|
||||
if r_location.name == location:
|
||||
self._location_cache[location] = r_location
|
||||
return r_location
|
||||
raise RuntimeError('No such entrance %s' % location)
|
||||
|
||||
def find_items(self, item):
|
||||
return [location for location in self.get_locations() if location.item is not None and location.item.name == item]
|
||||
|
||||
def push_item(self, location, item, collect=True):
|
||||
if not isinstance(location, Location):
|
||||
location = self.get_location(location)
|
||||
|
||||
if location.item_rule(item):
|
||||
location.item = item
|
||||
item.location = location
|
||||
if collect:
|
||||
self.state.collect(item)
|
||||
|
||||
print('Placed %s at %s' % (item, location))
|
||||
else:
|
||||
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
|
||||
|
||||
def get_locations(self):
|
||||
if self._cached_locations is None:
|
||||
self._cached_locations = []
|
||||
for region in self.regions:
|
||||
self._cached_locations.extend(region.locations)
|
||||
return self._cached_locations
|
||||
|
||||
def get_unfilled_locations(self):
|
||||
return [location for location in self.get_locations() if location.item is None]
|
||||
|
||||
def get_reachable_locations(self, state=None):
|
||||
if state is None:
|
||||
state = self.state
|
||||
return [location for location in self.get_locations() if state.can_reach(location)]
|
||||
|
||||
def get_placeable_locations(self, state=None):
|
||||
if state is None:
|
||||
state = self.state
|
||||
return [location for location in self.get_locations() if location.item is None and state.can_reach(location)]
|
||||
|
||||
def unlocks_new_location(self, item):
|
||||
temp_state = self.state.copy()
|
||||
temp_state._clear_cache()
|
||||
temp_state.collect(item)
|
||||
return len(self.get_placeable_locations()) < len(self.get_placeable_locations(temp_state))
|
||||
|
||||
|
||||
class CollectionState(object):
|
||||
|
||||
def __init__(self, parent, has_everything=False):
|
||||
self.prog_items = set()
|
||||
self.world = parent
|
||||
self.has_everything = has_everything
|
||||
self.changed = False
|
||||
self.region_cache = {}
|
||||
self.location_cache = {}
|
||||
self.entrance_cache = {}
|
||||
# use to avoid cycle dependencies during resolution
|
||||
self.recursion_cache = []
|
||||
|
||||
def _clear_cache(self):
|
||||
# we only need to invalidate results which were False, places we could reach before we can still reach after adding more items
|
||||
self.region_cache = {k: v for k, v in self.region_cache.items() if v}
|
||||
self.location_cache = {k: v for k, v in self.location_cache.items() if v}
|
||||
self.entrance_cache = {k: v for k, v in self.entrance_cache.items() if v}
|
||||
self.recursion_cache = []
|
||||
self.changed = False
|
||||
|
||||
def copy(self):
|
||||
ret = CollectionState(self.world, self.has_everything)
|
||||
ret.prog_items = copy.copy(self.prog_items)
|
||||
ret.changed = self.changed
|
||||
ret.region_cache = copy.copy(self.region_cache)
|
||||
ret.location_cache = copy.copy(self.location_cache)
|
||||
ret.entrance_cache = copy.copy(self.entrance_cache)
|
||||
ret.recursion_cache = copy.copy(self.recursion_cache)
|
||||
return ret
|
||||
|
||||
def can_reach(self, spot, resolution_hint=None):
|
||||
if self.changed:
|
||||
self._clear_cache()
|
||||
|
||||
if spot in self.recursion_cache:
|
||||
return False
|
||||
|
||||
if isinstance(spot, Region):
|
||||
correct_cache = self.region_cache
|
||||
elif isinstance(spot, Location):
|
||||
correct_cache = self.location_cache
|
||||
elif isinstance(spot, Entrance):
|
||||
correct_cache = self.entrance_cache
|
||||
else:
|
||||
# try to resolve a name
|
||||
if resolution_hint == 'Location':
|
||||
spot = self.world.get_location(spot)
|
||||
correct_cache = self.location_cache
|
||||
elif resolution_hint == 'Entrance':
|
||||
spot = self.world.get_entrance(spot)
|
||||
correct_cache = self.entrance_cache
|
||||
else:
|
||||
# default to Region
|
||||
spot = self.world.get_region(spot)
|
||||
correct_cache = self.region_cache
|
||||
|
||||
if spot not in correct_cache:
|
||||
# for the purpose of evaluating results, recursion is resolved by always denying recursive access (as that ia what we are trying to figure out right now in the first place
|
||||
self.recursion_cache.append(spot)
|
||||
can_reach = spot.can_reach(self)
|
||||
self.recursion_cache.pop()
|
||||
|
||||
# we only store qualified false results (i.e. ones not inside a hypothetical)
|
||||
if not can_reach:
|
||||
if not self.recursion_cache:
|
||||
correct_cache[spot] = can_reach
|
||||
else:
|
||||
correct_cache[spot] = can_reach
|
||||
return can_reach
|
||||
return correct_cache[spot]
|
||||
|
||||
def can_collect(self, item, count=None):
|
||||
if isinstance(item, Item):
|
||||
item = item.name
|
||||
if count is None:
|
||||
cached = self.world._item_cache.get(item, None)
|
||||
if cached is None:
|
||||
candidates = self.world.find_items(item)
|
||||
if not candidates:
|
||||
return False
|
||||
elif len(candidates) == 1:
|
||||
cached = candidates[0]
|
||||
self.world._item_cache[item] = cached
|
||||
else:
|
||||
# this should probably not happen, wonky item distribution?
|
||||
len([location for location in candidates if self.can_reach(location)]) >= 1
|
||||
return self.can_reach(cached)
|
||||
|
||||
return len([location for location in self.world.find_items(item) if self.can_reach(location)]) >= count
|
||||
|
||||
def has(self, item):
|
||||
if self.has_everything:
|
||||
return True
|
||||
else:
|
||||
return item in self.prog_items
|
||||
|
||||
def can_lift_rocks(self):
|
||||
return self.has('Power Glove') or self.has('Titans Mitts')
|
||||
|
||||
def can_lift_heavy_rocks(self):
|
||||
return self.has('Titans Mitts')
|
||||
|
||||
def has_sword(self):
|
||||
return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
|
||||
|
||||
def has_beam_sword(self):
|
||||
return self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
|
||||
|
||||
def has_blunt_weapon(self):
|
||||
return self.has_sword() or self.has('Hammer')
|
||||
|
||||
def has_Mirror(self):
|
||||
return self.has('Magic Mirror')
|
||||
|
||||
def has_Boots(self):
|
||||
return self.has('Pegasus Boots')
|
||||
|
||||
def has_Pearl(self):
|
||||
return self.has('Moon Pearl')
|
||||
|
||||
def has_fire_source(self):
|
||||
return self.has('Fire Rod') or self.has('Lamp')
|
||||
|
||||
def has_misery_mire_medallion(self):
|
||||
return self.has(self.world.required_medallions[0])
|
||||
|
||||
def has_turtle_rock_medallion(self):
|
||||
return self.has(self.world.required_medallions[1])
|
||||
|
||||
def collect(self, item):
|
||||
if item.name.startswith('Progressive '):
|
||||
if 'Sword' in item.name:
|
||||
if self.has('Golden Sword'):
|
||||
return
|
||||
elif self.has('Tempered Sword'):
|
||||
self.prog_items.add('Golden Sword')
|
||||
self.changed = True
|
||||
elif self.has('Master Sword'):
|
||||
self.prog_items.add('Tempered Sword')
|
||||
self.changed = True
|
||||
elif self.has('Fighter Sword'):
|
||||
self.prog_items.add('Master Sword')
|
||||
self.changed = True
|
||||
else:
|
||||
self.prog_items.add('Fighter Sword')
|
||||
self.changed = True
|
||||
elif 'Glove' in item.name:
|
||||
if self.has('Titans Mitts'):
|
||||
return
|
||||
elif self.has('Power Glove'):
|
||||
self.prog_items.add('Titans Mitts')
|
||||
self.changed = True
|
||||
else:
|
||||
self.prog_items.add('Power Glove')
|
||||
self.changed = True
|
||||
return
|
||||
|
||||
if item.advancement:
|
||||
self.prog_items.add(item.name)
|
||||
self.changed = True
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('can_reach_'):
|
||||
return self.can_reach(item[10])
|
||||
elif item.startswith('has_'):
|
||||
return self.has(item[4])
|
||||
|
||||
raise RuntimeError('Cannot parse %s.' % item)
|
||||
|
||||
|
||||
class Region(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.entrances = []
|
||||
self.exits = []
|
||||
self.locations = []
|
||||
|
||||
def can_reach(self, state):
|
||||
for entrance in self.entrances:
|
||||
if state.can_reach(entrance):
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_locations(self, *locations):
|
||||
for location in locations:
|
||||
self.locations.append(Location(location, self))
|
||||
|
||||
def add_exits(self, *exits):
|
||||
for exit in exits:
|
||||
self.exits.append(Entrance(exit, self))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
|
||||
class Entrance(object):
|
||||
|
||||
def __init__(self, name='', parent=None):
|
||||
self.name = name
|
||||
self.parent_region = parent
|
||||
self.connected_region = None
|
||||
|
||||
def access_rule(self, state):
|
||||
return True
|
||||
|
||||
def can_reach(self, state):
|
||||
if self.parent_region:
|
||||
if not state.can_reach(self.parent_region):
|
||||
return False
|
||||
|
||||
return self.access_rule(state)
|
||||
|
||||
def connect(self, region):
|
||||
self.connected_region = region
|
||||
region.entrances.append(self)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
|
||||
class Location(object):
|
||||
|
||||
def __init__(self, name='', parent=None, access=None, item_rule=None):
|
||||
self.name = name
|
||||
self.parent_region = parent
|
||||
self.item = None
|
||||
if access is not None:
|
||||
self.access_rule = access
|
||||
if item_rule is not None:
|
||||
self.item_rule = item_rule
|
||||
|
||||
def access_rule(self, state):
|
||||
return True
|
||||
|
||||
def item_rule(self, item):
|
||||
if item.name != 'Triforce':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def can_reach(self, state):
|
||||
if self.parent_region:
|
||||
if not state.can_reach(self.parent_region):
|
||||
return False
|
||||
|
||||
return self.access_rule(state)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
|
||||
class Item(object):
|
||||
|
||||
def __init__(self, name='', advancement=False, key=False):
|
||||
self.name = name
|
||||
self.advancement = advancement
|
||||
self.key = key
|
||||
self.location = None
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
Reference in New Issue
Block a user