From e19c0ada7017af6be48f43f6763b71fe39703d3b Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 27 Jan 2018 16:21:32 -0500 Subject: [PATCH] Retry Logic for Dungeon Prizes Will help make avoid seed failure for custom pool seeds. This won't help with a seed that has a layout that is not compatible with the item pool though. --- Fill.py | 14 ++++++++------ ItemList.py | 29 ++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Fill.py b/Fill.py index 2ac444de..b663e088 100644 --- a/Fill.py +++ b/Fill.py @@ -1,6 +1,8 @@ import random import logging +class FillError(RuntimeError): + pass def distribute_items_cutoff(world, cutoffrate=0.33): # get list of locations to fill in @@ -53,7 +55,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33): logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.') progress_done = True continue - raise RuntimeError('No more progress items left to place.') + raise FillError('No more progress items left to place.') spot_to_fill = None for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations): @@ -66,7 +68,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33): if world.can_beat_game(): logging.getLogger('').warning('Not all items placed. Game beatable anyway.') break - raise RuntimeError('No more spots to place %s' % item_to_place) + raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, True) itempool.remove(item_to_place) @@ -121,7 +123,7 @@ def distribute_items_staleness(world): logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.') progress_done = True continue - raise RuntimeError('No more progress items left to place.') + raise FillError('No more progress items left to place.') spot_to_fill = None for location in fill_locations: @@ -147,7 +149,7 @@ def distribute_items_staleness(world): if world.can_beat_game(): logging.getLogger('').warning('Not all items placed. Game beatable anyway.') break - raise RuntimeError('No more spots to place %s' % item_to_place) + raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, True) itempool.remove(item_to_place) @@ -185,7 +187,7 @@ def fill_restrictive(world, base_state, locations, itempool): if not world.check_beatable_only: logging.getLogger('').warning('Not all items placed. Game beatable anyway.') break - raise RuntimeError('No more spots to place %s' % item_to_place) + raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, False) locations.remove(spot_to_fill) @@ -281,7 +283,7 @@ def flood_items(world): if candidate_item_to_place is not None: item_to_place = candidate_item_to_place else: - raise RuntimeError('No more progress items left to place.') + raise FillError('No more progress items left to place.') # find item to replace with progress item location_list = world.get_reachable_locations() diff --git a/ItemList.py b/ItemList.py index 6709fd10..065e4abc 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1,8 +1,9 @@ from collections import namedtuple +import logging import random from Items import ItemFactory -from Fill import fill_restrictive +from Fill import FillError, fill_restrictive from Dungeons import get_dungeon_item_pool #This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. @@ -248,14 +249,36 @@ def generate_itempool(world): world.required_medallions = (mm_medallion, tr_medallion) # distribute crystals + fill_prizes(world) + +def fill_prizes(world, attempts=15): crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']) crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'), world.get_location('Thieves Town - Prize'), world.get_location('Skull Woods - Prize'), world.get_location('Swamp Palace - Prize'), world.get_location('Ice Palace - Prize'), world.get_location('Misery Mire - Prize')] + placed_prizes = [loc.item.name for loc in crystal_locations if loc.item is not None] + unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes] + empty_crystal_locations = [loc for loc in crystal_locations if loc.item is None] + + while attempts: + attempts -= 1 + try: + prizepool = list(unplaced_prizes) + prize_locs = list(empty_crystal_locations) + random.shuffle(prizepool) + random.shuffle(prize_locs) + fill_restrictive(world, world.get_all_state(keys=True), prize_locs, prizepool) + except FillError: + logging.getLogger('').info("Failed to place dungeon prizes. Will retry %s more times", attempts) + for location in empty_crystal_locations: + location.item = None + continue + break + else: + raise FillError('Unable to place dungeon prizes') + - random.shuffle(crystal_locations) - fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals) def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode): pool = []