Make sure shop slots obey accessibility rules.
The way this is checked is quite computationally expensive, should revisit later.
This commit is contained in:
parent
058436e47f
commit
0978daba69
|
@ -448,6 +448,65 @@ class World(object):
|
||||||
|
|
||||||
return False
|
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):
|
class CollectionState(object):
|
||||||
|
|
||||||
def __init__(self, parent: World):
|
def __init__(self, parent: World):
|
||||||
|
|
28
Main.py
28
Main.py
|
@ -238,22 +238,17 @@ def main(args, seed=None):
|
||||||
if shop.can_push_inventory(slot_num):
|
if shop.can_push_inventory(slot_num):
|
||||||
for c in candidates: # chosen item locations
|
for c in candidates: # chosen item locations
|
||||||
if c.item_rule(location.item): # if rule is good...
|
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)
|
swap_location_item(c, location, check_locked=False)
|
||||||
# TODO: should likely be (all_state-c.item).can_reach(shop.region)
|
candidates.remove(c)
|
||||||
# can still be can_beat_game when beatable-only for the item-owning player
|
if not world.fulfills_accessibility():
|
||||||
# appears to be the main source of "progression items unreachable Exception"
|
|
||||||
# in door-rando + multishop
|
|
||||||
if not world.can_beat_game():
|
|
||||||
# swap back
|
|
||||||
swap_location_item(c, location, check_locked=False)
|
swap_location_item(c, location, check_locked=False)
|
||||||
else:
|
continue
|
||||||
# we use this candidate
|
break
|
||||||
candidates.remove(c)
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
# This *should* never happen. But let's fail safely just in case.
|
# 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)
|
shop.region.locations.remove(location)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -381,7 +376,7 @@ def main(args, seed=None):
|
||||||
|
|
||||||
pool = concurrent.futures.ThreadPoolExecutor()
|
pool = concurrent.futures.ThreadPoolExecutor()
|
||||||
multidata_task = None
|
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:
|
if not args.suppress_rom:
|
||||||
|
|
||||||
rom_futures = []
|
rom_futures = []
|
||||||
|
@ -477,8 +472,11 @@ def main(args, seed=None):
|
||||||
f.write(multidata)
|
f.write(multidata)
|
||||||
|
|
||||||
multidata_task = pool.submit(write_multidata, rom_futures)
|
multidata_task = pool.submit(write_multidata, rom_futures)
|
||||||
if not check_beatability_task.result():
|
if not check_accessibility_task.result():
|
||||||
raise Exception("Game appears unbeatable. Aborting.")
|
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:
|
if not args.skip_playthrough:
|
||||||
logger.info('Calculating playthrough.')
|
logger.info('Calculating playthrough.')
|
||||||
create_playthrough(world)
|
create_playthrough(world)
|
||||||
|
|
|
@ -414,7 +414,7 @@ def create_shops(world, player: int):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if my_shop_slots.pop():
|
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)
|
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 = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region)
|
||||||
loc.shop_slot = True
|
loc.shop_slot = True
|
||||||
|
|
Loading…
Reference in New Issue