Make sure shop slots obey accessibility rules.

The way this is checked is quite computationally expensive, should revisit later.
This commit is contained in:
Fabian Dill 2021-01-11 19:56:18 +01:00
parent 058436e47f
commit 0978daba69
3 changed files with 73 additions and 16 deletions

View File

@ -448,6 +448,65 @@ class World(object):
return False
def fulfills_accessibility(self):
state = CollectionState(self)
locations = {location for location in self.get_locations() if location.item}
beatable_players = set()
items_players = set()
locations_players = set()
for player in self.player_ids:
access = self.accessibility[player]
if access == "none":
beatable_players.add(player)
elif access == "items":
items_players.add(player)
elif access == "locations":
locations_players.add(player)
else:
raise Exception(f"unknown access rule {access} for player {player}")
beatable_fulfilled = False
def location_conditition(location : Location):
"""Determine if this location has to be accessible"""
if location.player in locations_players:
return True
elif location.player in items_players:
return location.item.advancement or location.event
return False
def all_done():
"""Check if all access rules are fulfilled"""
if beatable_fulfilled:
for location in locations:
if location_conditition(location):
return False
return True
while locations:
sphere = set()
for location in locations:
if location.can_reach(state):
sphere.add(location)
if not sphere:
# ran out of places and did not finish yet, quit
logging.debug(f"Could not access required locations.")
return False
for location in sphere:
locations.remove(location)
state.collect(location.item, True, location)
if self.has_beaten_game(state):
beatable_fulfilled = True
if all_done():
return True
return False
class CollectionState(object):
def __init__(self, parent: World):

28
Main.py
View File

@ -238,22 +238,17 @@ def main(args, seed=None):
if shop.can_push_inventory(slot_num):
for c in candidates: # chosen item locations
if c.item_rule(location.item): # if rule is good...
logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
logger.debug(f'Swapping {c} into {location}:: {c.item}')
swap_location_item(c, location, check_locked=False)
# TODO: should likely be (all_state-c.item).can_reach(shop.region)
# can still be can_beat_game when beatable-only for the item-owning player
# appears to be the main source of "progression items unreachable Exception"
# in door-rando + multishop
if not world.can_beat_game():
# swap back
candidates.remove(c)
if not world.fulfills_accessibility():
swap_location_item(c, location, check_locked=False)
else:
# we use this candidate
candidates.remove(c)
break
continue
break
else:
# This *should* never happen. But let's fail safely just in case.
logging.warning("Ran out of ShopShuffle Item candidate locations.")
logger.warning("Ran out of ShopShuffle Item candidate locations.")
shop.region.locations.remove(location)
continue
@ -381,7 +376,7 @@ def main(args, seed=None):
pool = concurrent.futures.ThreadPoolExecutor()
multidata_task = None
check_beatability_task = pool.submit(world.can_beat_game)
check_accessibility_task = pool.submit(world.fulfills_accessibility)
if not args.suppress_rom:
rom_futures = []
@ -477,8 +472,11 @@ def main(args, seed=None):
f.write(multidata)
multidata_task = pool.submit(write_multidata, rom_futures)
if not check_beatability_task.result():
raise Exception("Game appears unbeatable. Aborting.")
if not check_accessibility_task.result():
if not world.can_beat_game():
raise Exception("Game appears is unbeatable. Aborting.")
else:
logger.warning("Location Accessibility requirements not fulfilled.")
if not args.skip_playthrough:
logger.info('Calculating playthrough.')
create_playthrough(world)

View File

@ -414,7 +414,7 @@ def create_shops(world, player: int):
pass
else:
if my_shop_slots.pop():
additional_item = world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
additional_item = 'Rupees (50)' # world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
slot_name = "{} Slot {}".format(shop.region.name, index + 1)
loc = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region)
loc.shop_slot = True