예제 #1
0
def pattern_dict_items(pattern_dict, itempool=None, exhausted=None):
    for (key, value) in pattern_dict.items():
        if hasattr(value, 'item') and isinstance(value.item, list):
            if itempool is not None:
                valid_items = [
                    item.name for item in itempool if item.name in value.item
                ]
                if exhausted is not None:
                    [
                        valid_items.remove(item) for item in exhausted
                        if item in valid_items
                    ]
            else:
                valid_items = value.item
            if not valid_items and exhausted is None:
                continue
            elif not valid_items:
                value.item = random_choices(value.item)[0]
            else:
                value.item = random_choices(valid_items)[0]
                if exhausted is not None:
                    exhausted.append(value.item)
        if is_pattern(key):
            pattern = lambda loc: pattern_matcher(key)(loc.name)
            for location in LocationIterator(pattern):
                yield (location.name, value)
        else:
            yield (key, value)
예제 #2
0
def pattern_dict_items(pattern_dict, itempool=None, used_items=None):
    for (key, value) in pattern_dict.items():
        if hasattr(value, 'item') and isinstance(value.item, list):
            if itempool is not None:
                valid_items = []
                pool_group_items = []
                for item in itempool:
                    if item.name in value.item:
                        valid_items.append(item.name)
                    else:
                        for group in item_groups:
                            if '#' + group in value.item:
                                if item.name in item_groups[group]:
                                    pool_group_items.append(item.name)
                if used_items is not None:
                    for used_item in used_items:
                        if used_item in valid_items:
                            valid_items.remove(used_item)
                        else:
                            for group in item_groups:
                                if group == "AdultTrade":
                                    # Special handling for AdultTrade item
                                    if used_item in item_groups[group]:
                                        pool_group_items = [i for i in pool_group_items if i not in item_groups[group]]
                                        continue
                                for item in item_groups[group]:
                                    if '#' + group in value.item and used_item == item and item in pool_group_items:
                                        pool_group_items.remove(item)
                                        break
                    valid_items.extend(pool_group_items)
            else:
                valid_items = value.item
            if not valid_items and used_items is None:
                continue
            elif not valid_items and used_items is not None:
                limited_items = ['Weird Egg', '#AdultTrade', '#Bottle']
                value.item = [v for v in value.item
                              if (v not in limited_items
                                  and v not in item_groups['AdultTrade']
                                  and v not in item_groups['Bottle'])]
                value.item = random_choices(value.item)[0]
            else:
                value.item = random_choices(valid_items)[0]
                if used_items is not None:
                    used_items.append(value.item)
        elif used_items is not None:
            used_items.append(value.item)
        if is_pattern(key):
            pattern = lambda loc: pattern_matcher(key)(loc.name)
            for location in LocationIterator(pattern):
                yield(location.name, value)
        else:
            yield (key, value)
예제 #3
0
    def __init__(self,
                 rand_song=True,
                 piece_size=3,
                 extra_position='none',
                 starting_range=range(0, 5),
                 activation_transform=identity,
                 playback_transform=identity,
                 activation=None):
        if activation:
            self.length = len(activation)
            self.activation = activation
            self.playback = fast_playback(self.activation)
            self.break_repeated_notes(0x03)
            self.format_playback_data()
            self.increase_duration_to(45)
            return

        if rand_song:
            self.length = random.randint(4, 8)
            self.activation = random_choices(range(0, 5), k=self.length)
            self.playback = random_playback(self.activation)
        else:
            if extra_position != 'none':
                piece_size = 3
            piece = random_piece(piece_size, starting_range)
            self.two_piece_playback(piece, extra_position,
                                    activation_transform, playback_transform)

        self.break_repeated_notes()
        self.format_activation_data()
        self.format_playback_data()
예제 #4
0
def get_junk_item(count=1, pool=None, plando_pool=None):
    if count < 1:
        raise ValueError(
            "get_junk_item argument 'count' must be greater than 0.")

    return_pool = []
    if pending_junk_pool:
        pending_count = min(len(pending_junk_pool), count)
        return_pool = [pending_junk_pool.pop() for _ in range(pending_count)]
        count -= pending_count

    if pool and plando_pool:
        jw_list = [(junk, weight) for (junk, weight) in junk_pool
                   if junk not in plando_pool
                   or pool.count(junk) < plando_pool[junk].count]
        try:
            junk_items, junk_weights = zip(*jw_list)
        except ValueError:
            raise RuntimeError(
                "Not enough junk is available in the item pool to replace removed items."
            )
    else:
        junk_items, junk_weights = zip(*junk_pool)
    return_pool.extend(
        random_choices(junk_items, weights=junk_weights, k=count))

    return return_pool
예제 #5
0
 def resolve_random_settings(self):
     for info in setting_infos:
         if 'randomize_key' in info.gui_params and self.__dict__[
                 info.gui_params['randomize_key']]:
             choices, weights = zip(*info.gui_params['distribution'])
             self.__dict__[info.name] = random_choices(choices,
                                                       weights=weights)[0]
예제 #6
0
def get_barren_hint(spoiler, world, checked):
    if not hasattr(world, 'get_barren_hint_prev'):
        world.get_barren_hint_prev = RegionRestriction.NONE

    areas = list(
        filter(
            lambda area: area not in checked and
            not (world.barren_dungeon >= world.hint_dist_user[
                'dungeons_barren_limit'] and world.empty_areas[area]['dungeon']
                 ), world.empty_areas.keys()))

    if not areas:
        return None

    # Randomly choose between overworld or dungeon
    dungeon_areas = list(
        filter(lambda area: world.empty_areas[area]['dungeon'], areas))
    overworld_areas = list(
        filter(lambda area: not world.empty_areas[area]['dungeon'], areas))
    if not dungeon_areas:
        # no dungeons left, default to overworld
        world.get_barren_hint_prev = RegionRestriction.OVERWORLD
    elif not overworld_areas:
        # no overworld left, default to dungeons
        world.get_barren_hint_prev = RegionRestriction.DUNGEON
    else:
        if world.get_barren_hint_prev == RegionRestriction.NONE:
            # 50/50 draw on the first hint
            world.get_barren_hint_prev = random.choices(
                [RegionRestriction.DUNGEON, RegionRestriction.OVERWORLD],
                [0.5, 0.5])[0]
        elif world.get_barren_hint_prev == RegionRestriction.DUNGEON:
            # weights 75% against drawing dungeon again
            world.get_barren_hint_prev = random.choices(
                [RegionRestriction.DUNGEON, RegionRestriction.OVERWORLD],
                [0.25, 0.75])[0]
        elif world.get_barren_hint_prev == RegionRestriction.OVERWORLD:
            # weights 75% against drawing overworld again
            world.get_barren_hint_prev = random.choices(
                [RegionRestriction.DUNGEON, RegionRestriction.OVERWORLD],
                [0.75, 0.25])[0]

    if world.get_barren_hint_prev == RegionRestriction.DUNGEON:
        areas = dungeon_areas
    else:
        areas = overworld_areas
    if not areas:
        return None

    area_weights = [world.empty_areas[area]['weight'] for area in areas]

    area = random_choices(areas, weights=area_weights)[0]
    if world.empty_areas[area]['dungeon']:
        world.barren_dungeon += 1

    checked.add(area)

    return (GossipText("plundering #%s# is a foolish choice." % area,
                       ['Pink']), None)
예제 #7
0
 def update_seed(self, seed):
     if seed is None or seed == '':
         # https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python
         self.seed = ''.join(random_choices(string.ascii_uppercase + string.digits, k=10))
     else:
         self.seed = seed
     self.sanitize_seed()
     self.numeric_seed = self.get_numeric_seed()
    def resolve_random_settings(self):
        sorted_infos = list(setting_infos)
        sort_key = lambda info: 0 if info.dependency is None else 1
        sorted_infos.sort(key=sort_key)

        for info in sorted_infos:
            if not self.check_dependency(info.name, check_random=False):
                continue

            if 'randomize_key' in info.gui_params and self.__dict__[
                    info.gui_params['randomize_key']]:
                choices, weights = zip(*info.gui_params['distribution'])
                self.__dict__[info.name] = random_choices(choices,
                                                          weights=weights)[0]
예제 #9
0
def get_junk_item(count=1):
    if count < 1:
        raise ValueError("get_junk_item argument 'count' must be greater than 0.")

    return_pool = []
    if pending_junk_pool:
        pending_count = min(len(pending_junk_pool), count)
        return_pool = [pending_junk_pool.pop() for _ in range(pending_count)]
        count -= pending_count

    junk_items, junk_weights = zip(*junk_pool)
    return_pool.extend(random_choices(junk_items, weights=junk_weights, k=count))

    return return_pool
예제 #10
0
def get_random_song():

    rand_song = random_choices([True, False], [1, 9])[0]
    piece_size = random_choices([3, 4], [5, 2])[0]
    extra_position = random_choices(['none', 'start', 'middle', 'end'],
                                    [12, 1, 1, 1])[0]
    activation_transform = identity
    playback_transform = identity
    weight_damage = 0
    should_transpose = random_choices([True, False], [1, 4])[0]
    starting_range = range(0, 5)
    if should_transpose:
        weight_damage = 2
        direction = random_choices(['up', 'down'], [1, 1])[0]
        if direction == 'up':
            starting_range = range(0, 4)
            activation_transform = transpose_piece(1)
        elif direction == 'down':
            starting_range = range(1, 5)
            activation_transform = transpose_piece(-1)
    should_invert = random_choices([True, False], [3 - weight_damage, 6])[0]
    if should_invert:
        weight_damage += 1
        activation_transform = compose(invert_piece, activation_transform)
    should_reflect = random_choices([True, False], [5 - weight_damage, 4])[0]
    if should_reflect:
        activation_transform = compose(reverse_piece, activation_transform)
        playback_transform = reverse_piece

    # print([rand_song, piece_size, extra_position, starting_range, should_transpose, should_invert, should_reflect])

    song = Song(rand_song, piece_size, extra_position, starting_range,
                activation_transform, playback_transform)

    # rate its difficulty
    difficulty = 0
    difficulty = piece_size * 12
    if extra_position != 'none':
        difficulty += 12
    if should_transpose:
        difficulty += 25
    if should_reflect:
        difficulty += 10
    if should_invert:
        difficulty += 20
    if rand_song:
        difficulty = 11 * len(song.activation)

    song.difficulty = difficulty
    return song
예제 #11
0
    def resolve_random_settings(self, cosmetic):
        sorted_infos = list(setting_infos)
        sort_key = lambda info: 0 if info.dependency is None else 1
        sorted_infos.sort(key=sort_key)

        for info in sorted_infos:
            # only randomize cosmetics options or non-cosmetic
            if cosmetic == info.shared:
                continue

            if self.check_dependency(info.name, check_random=True):
                continue

            if 'randomize_key' in info.gui_params and self.__dict__[info.gui_params['randomize_key']]:               
                choices, weights = zip(*info.gui_params['distribution'])
                self.__dict__[info.name] = random_choices(choices, weights=weights)[0]
예제 #12
0
 def __init__(self, settings_dict):
     self.__dict__.update(settings_dict)
     for info in setting_infos:
         if info.name not in self.__dict__:
             if info.type == bool:
                 if info.gui_params is not None and 'default' in info.gui_params:
                     self.__dict__[info.name] = True if info.gui_params[
                         'default'] == 'checked' else False
                 else:
                     self.__dict__[info.name] = False
             if info.type == str:
                 if 'default' in info.args_params:
                     self.__dict__[info.name] = info.args_params['default']
                 elif info.gui_params is not None and 'default' in info.gui_params:
                     if 'options' in info.gui_params and isinstance(
                             info.gui_params['options'], dict):
                         self.__dict__[info.name] = info.gui_params[
                             'options'][info.gui_params['default']]
                     else:
                         self.__dict__[
                             info.name] = info.gui_params['default']
                 else:
                     self.__dict__[info.name] = ""
             if info.type == int:
                 if 'default' in info.args_params:
                     self.__dict__[info.name] = info.args_params['default']
                 elif info.gui_params is not None and 'default' in info.gui_params:
                     self.__dict__[info.name] = info.gui_params['default']
                 else:
                     self.__dict__[info.name] = 1
             if info.type == list:
                 if 'default' in info.args_params:
                     self.__dict__[info.name] = list(
                         info.args_params['default'])
                 elif info.gui_params is not None and 'default' in info.gui_params:
                     self.__dict__[info.name] = list(
                         info.gui_params['default'])
                 else:
                     self.__dict__[info.name] = []
     self.settings_string = self.get_settings_string()
     if (self.seed is None):
         # https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python
         self.seed = ''.join(
             random_choices(string.ascii_uppercase + string.digits, k=10))
     self.sanitize_seed()
     self.numeric_seed = self.get_numeric_seed()
예제 #13
0
def get_barren_hint(spoiler, world, checked):
    areas = list(filter(lambda area: 
        area not in checked and \
        not (world.barren_dungeon and world.empty_areas[area]['dungeon']), 
        world.empty_areas.keys()))

    if not areas:
        return None

    area_weights = [world.empty_areas[area]['weight'] for area in areas]

    area = random_choices(areas, weights=area_weights)[0]
    if world.empty_areas[area]['dungeon']:
        world.barren_dungeon = True

    checked.append(area)

    return (GossipText("plundering #%s# is a foolish choice." % area, ['Pink']), None)
예제 #14
0
    def resolve_random_settings(self, cosmetic, randomize_key=None):
        sorted_infos = list(setting_infos)
        sort_key = lambda info: 0 if info.dependency is None else 1
        sorted_infos.sort(key=sort_key)
        randomize_keys_enabled = set()

        for info in sorted_infos:
            # only randomize cosmetics options or non-cosmetic
            if cosmetic == info.shared:
                continue

            if self.check_dependency(info.name, check_random=True):
                continue

            if 'randomize_key' not in info.gui_params:
                continue

            if randomize_key is not None and info.gui_params[
                    'randomize_key'] != randomize_key:
                continue

            # Make sure the setting is meant to be randomized and not specified in distribution
            # We that check it's not specified in the distribution so that plando can override randomized settings
            not_in_dist = '_settings' not in self.distribution.src_dict or info.name not in self.distribution.src_dict[
                '_settings'].keys()
            if self.__dict__[info.gui_params['randomize_key']] and not_in_dist:
                randomize_keys_enabled.add(info.gui_params['randomize_key'])
                choices, weights = zip(*info.gui_params['distribution'])
                self.__dict__[info.name] = random_choices(choices,
                                                          weights=weights)[0]

        # Second pass to make sure disabled settings are set properly.
        # Stupid hack: disable randomize keys, then re-enable.
        for randomize_keys in randomize_keys_enabled:
            self.__dict__[randomize_keys] = False
        for info in sorted_infos:
            if cosmetic == info.shared:
                continue
            dependency = self.get_dependency(info.name, check_random=False)
            if dependency is None:
                continue
            self.__dict__[info.name] = dependency
        for randomize_keys in randomize_keys_enabled:
            self.__dict__[randomize_keys] = True
예제 #15
0
def get_barren_hint(spoiler, world, checked):
    areas = list(filter(lambda area:
        area not in checked and \
        not (world.barren_dungeon and world.empty_areas[area]['dungeon']),
        world.empty_areas.keys()))

    if not areas:
        return None

    area_weights = [world.empty_areas[area]['weight'] for area in areas]

    area = random_choices(areas, weights=area_weights)[0]
    if world.empty_areas[area]['dungeon']:
        world.barren_dungeon = True

    checked.append(area)

    return (
        buildHintString(colorText(area, 'Pink') + " is barren of treasure."),
        None)
예제 #16
0
    def pool_add_item(self, pool, item_name, count):
        if item_name == '#Junk':
            added_items = get_junk_item(count, pool=pool, plando_pool=self.item_pool)
        elif is_pattern(item_name):
            add_matcher = lambda item: pattern_matcher(item_name)(item.name)
            candidates = [
                item.name for item in ItemIterator(predicate=add_matcher)
                if item.name not in self.item_pool or self.item_pool[item.name].count != 0
            ]  # Only allow items to be candidates if they haven't been set to 0
            if len(candidates) == 0:
                raise RuntimeError("Unknown item, or item set to 0 in the item pool could not be added: " + item_name)
            added_items = random_choices(candidates, k=count)
        else:
            if not IsItem(item_name):
                raise RuntimeError("Unknown item could not be added: " + item_name)
            added_items = [item_name] * count

        for item in added_items:
            pool.append(item)

        return added_items
예제 #17
0
    def pool_add_item(self, pool, item_name, count):
        added_items = []
        if item_name == '#Junk':
            added_items = get_junk_item(count)
        elif is_pattern(item_name):
            add_matcher = lambda item: pattern_matcher(item_name)(item.name)
            candidates = [
                item.name for item in ItemIterator(predicate=add_matcher)
            ]
            if len(candidates) == 0:
                raise RuntimeError("Unknown item could not be added: " +
                                   item_name)
            added_items = random_choices(candidates, k=count)
        else:
            if not IsItem(item_name):
                raise RuntimeError("Unknown item could not be added: " +
                                   item_name)
            added_items = [item_name] * count

        for item in added_items:
            pool.append(item)

        return added_items
예제 #18
0
def buildGossipHints(spoiler, world):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = False

    max_states = State.get_states_with_items([w.state for w in spoiler.worlds], [])
    for stone in gossipLocations.values():
        stone.reachable = \
            max_states[world.id].can_reach(stone.location, resolution_hint='Location') and \
            max_states[world.id].guarantee_hint()

    checkedLocations = []

    stoneIDs = list(gossipLocations.keys())

    world.distribution.configure_gossip(spoiler, stoneIDs)

    random.shuffle(stoneIDs)

    hint_dist = hint_dist_sets[world.hint_dist]
    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, _ = zip(*hint_prob)

    # Add required location hints
    alwaysLocations = getHintGroup('always', world)
    for hint in alwaysLocations:
        location = world.get_location(hint.name)
        checkedLocations.append(hint.name)

        location_text = getHint(location.name, world.clearer_hints).text
        if '#' not in location_text:
            location_text = '#%s#' % location_text
        item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
        add_hint(spoiler, world, stoneIDs, GossipText('%s #%s#.' % (location_text, item_text), ['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True)

    # Add trial hints
    if world.trials_random and world.trials == 6:
        add_hint(spoiler, world, stoneIDs, GossipText("#Ganon's Tower# is protected by a powerful barrier.", ['Pink']), hint_dist['trial'][1], force_reachable=True)
    elif world.trials_random and world.trials == 0:
        add_hint(spoiler, world, stoneIDs, GossipText("Sheik dispelled the barrier around #Ganon's Tower#.", ['Yellow']), hint_dist['trial'][1], force_reachable=True)
    elif world.trials < 6 and world.trials > 3:
        for trial,skipped in world.skipped_trials.items():
            if skipped:
                add_hint(spoiler, world, stoneIDs,GossipText("the #%s Trial#  was dispelled by Sheik." % trial, ['Yellow']), hint_dist['trial'][1], force_reachable=True)
    elif world.trials <= 3 and world.trials > 0:
        for trial,skipped in world.skipped_trials.items():
            if not skipped:
                add_hint(spoiler, world, stoneIDs, GossipText("the #%s Trial# protects Ganon's Tower." % trial, ['Pink']), hint_dist['trial'][1], force_reachable=True)

    hint_types = list(hint_types)
    hint_prob  = list(hint_prob)
    if world.hint_dist == "tournament":
        fixed_hint_types = []
        for hint_type in hint_types:
            fixed_hint_types.extend([hint_type] * int(hint_dist[hint_type][0]))

    while stoneIDs:
        if world.hint_dist == "tournament":
            if fixed_hint_types:
                hint_type = fixed_hint_types.pop(0)
            else:
                hint_type = 'random'
        else:
            try:
                hint_type = random_choices(hint_types, weights=hint_prob)[0]
            except IndexError:
                raise Exception('Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
        else:
            gossip_text, location = hint
            place_ok = add_hint(spoiler, world, stoneIDs, gossip_text, hint_dist[hint_type][1], location)
            if not place_ok and world.hint_dist == "tournament":
                fixed_hint_types.insert(0, hint_type)
예제 #19
0
def buildWorldGossipHints(spoiler, world, checkedLocations=None):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = 0
    world.woth_dungeon = 0

    search = Search.max_explore([w.state for w in spoiler.worlds])
    for stone in gossipLocations.values():
        stone.reachable = (
            search.spot_access(world.get_location(stone.location))
            and search.state_list[world.id].guarantee_hint())

    if checkedLocations is None:
        checkedLocations = set()

    stoneIDs = list(gossipLocations.keys())

    world.distribution.configure_gossip(spoiler, stoneIDs)

    if 'disabled' in world.hint_dist_user:
        for stone_name in world.hint_dist_user['disabled']:
            try:
                stone_id = gossipLocations_reversemap[stone_name]
            except KeyError:
                raise ValueError(f'Gossip stone location "{stone_name}" is not valid')
            stoneIDs.remove(stone_id)
            (gossip_text, _) = get_junk_hint(spoiler, world, checkedLocations)
            spoiler.hints[world.id][stone_id] = gossip_text

    stoneGroups = []
    if 'groups' in world.hint_dist_user:
        for group_names in world.hint_dist_user['groups']:
            group = []
            for stone_name in group_names:
                try:
                    stone_id = gossipLocations_reversemap[stone_name]
                except KeyError:
                    raise ValueError(f'Gossip stone location "{stone_name}" is not valid')

                stoneIDs.remove(stone_id)
                group.append(stone_id)
            stoneGroups.append(group)
    # put the remaining locations into singleton groups
    stoneGroups.extend([[id] for id in stoneIDs])

    random.shuffle(stoneGroups)

    # Create list of items for which we want hints. If Bingosync URL is supplied, include items specific to that bingo.
    # If not (or if the URL is invalid), use generic bingo hints
    if world.hint_dist == "bingo":
        bingoDefaults = read_json(data_path('Bingo/generic_bingo_hints.json'))
        if world.bingosync_url is not None and world.bingosync_url.startswith("https://bingosync.com/"): # Verify that user actually entered a bingosync URL
            logger = logging.getLogger('')
            logger.info("Got Bingosync URL. Building board-specific goals.")
            world.item_hints = buildBingoHintList(world.bingosync_url)
        else:
            world.item_hints = bingoDefaults['settings']['item_hints']

        if world.tokensanity in ("overworld", "all") and "Suns Song" not in world.item_hints:
            world.item_hints.append("Suns Song")

        if world.shopsanity != "off" and "Progressive Wallet" not in world.item_hints:
            world.item_hints.append("Progressive Wallet")


    # Load hint distro from distribution file or pre-defined settings
    #
    # 'fixed' key is used to mimic the tournament distribution, creating a list of fixed hint types to fill
    # Once the fixed hint type list is exhausted, weighted random choices are taken like all non-tournament sets
    # This diverges from the tournament distribution where leftover stones are filled with sometimes hints (or random if no sometimes locations remain to be hinted)
    sorted_dist = {}
    type_count = 1
    hint_dist = OrderedDict({})
    fixed_hint_types = []
    max_order = 0
    for hint_type in world.hint_dist_user['distribution']:
        if world.hint_dist_user['distribution'][hint_type]['order'] > 0:
            hint_order = int(world.hint_dist_user['distribution'][hint_type]['order'])
            sorted_dist[hint_order] = hint_type
            if max_order < hint_order:
                max_order = hint_order
            type_count = type_count + 1
    if (type_count - 1) < max_order:
        raise Exception("There are gaps in the custom hint orders. Please revise your plando file to remove them.")
    for i in range(1, type_count):
        hint_type = sorted_dist[i]
        if world.hint_dist_user['distribution'][hint_type]['copies'] > 0:
            fixed_num = world.hint_dist_user['distribution'][hint_type]['fixed']
            hint_weight = world.hint_dist_user['distribution'][hint_type]['weight']
        else:
            logging.getLogger('').warning("Hint copies is zero for type %s. Assuming this hint type should be disabled.", hint_type)
            fixed_num = 0
            hint_weight = 0
        hint_dist[hint_type] = (hint_weight, world.hint_dist_user['distribution'][hint_type]['copies'])
        hint_dist.move_to_end(hint_type)
        fixed_hint_types.extend([hint_type] * int(fixed_num))

    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, _ = zip(*hint_prob)

    # Add required location hints, only if hint copies > 0
    if hint_dist['always'][1] > 0:
        alwaysLocations = getHintGroup('always', world)
        for hint in alwaysLocations:
            location = world.get_location(hint.name)
            checkedLocations.add(hint.name)
            if location.item.name in bingoBottlesForHints and world.hint_dist == 'bingo':
                always_item = 'Bottle'
            else:
                always_item = location.item.name
            if always_item in world.item_hints:
                world.item_hints.remove(always_item)

            if location.name in world.hint_text_overrides:
                location_text = world.hint_text_overrides[location.name]
            else:
                location_text = getHint(location.name, world.clearer_hints).text
            if '#' not in location_text:
                location_text = '#%s#' % location_text
            item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
            add_hint(spoiler, world, stoneGroups, GossipText('%s #%s#.' % (location_text, item_text), ['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True)
            logging.getLogger('').debug('Placed always hint for %s.', location.name)

    # Add trial hints, only if hint copies > 0
    if hint_dist['trial'][1] > 0:
        if world.trials_random and world.trials == 6:
            add_hint(spoiler, world, stoneGroups, GossipText("#Ganon's Tower# is protected by a powerful barrier.", ['Pink']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials_random and world.trials == 0:
            add_hint(spoiler, world, stoneGroups, GossipText("Sheik dispelled the barrier around #Ganon's Tower#.", ['Yellow']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials < 6 and world.trials > 3:
            for trial,skipped in world.skipped_trials.items():
                if skipped:
                    add_hint(spoiler, world, stoneGroups,GossipText("the #%s Trial# was dispelled by Sheik." % trial, ['Yellow']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials <= 3 and world.trials > 0:
            for trial,skipped in world.skipped_trials.items():
                if not skipped:
                    add_hint(spoiler, world, stoneGroups, GossipText("the #%s Trial# protects Ganon's Tower." % trial, ['Pink']), hint_dist['trial'][1], force_reachable=True)

    # Add user-specified hinted item locations if using a built-in hint distribution
    # Raise error if hint copies is zero
    if len(world.item_hints) > 0 and world.hint_dist_user['named_items_required']:
        if hint_dist['named-item'][1] == 0:
            raise Exception('User-provided item hints were requested, but copies per named-item hint is zero')
        else:
            for i in range(0, len(world.item_hints)):
                hint = get_specific_item_hint(spoiler, world, checkedLocations)
                if hint == None:
                    raise Exception('No valid hints for user-provided item')
                else:
                    gossip_text, location = hint
                    place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, hint_dist['named-item'][1], location)
                    if not place_ok:
                        raise Exception('Not enough gossip stones for user-provided item hints')

    hint_types = list(hint_types)
    hint_prob  = list(hint_prob)
    hint_counts = {}

    custom_fixed = True
    while stoneGroups:
        if fixed_hint_types:
            hint_type = fixed_hint_types.pop(0)
            copies = hint_dist[hint_type][1]
            if copies > len(stoneGroups):
                # Quiet to avoid leaking information.
                logging.getLogger('').debug(f'Not enough gossip stone locations ({len(stoneGroups)} groups) for fixed hint type {hint_type} with {copies} copies, proceeding with available stones.')
                copies = len(stoneGroups)
        else:
            custom_fixed = False
            # Make sure there are enough stones left for each hint type
            num_types = len(hint_types)
            hint_types = list(filter(lambda htype: hint_dist[htype][1] <= len(stoneGroups), hint_types))
            new_num_types = len(hint_types)
            if new_num_types == 0:
                raise Exception('Not enough gossip stone locations for remaining weighted hint types.')
            elif new_num_types < num_types:
                hint_prob = []
                for htype in hint_types:
                    hint_prob.append(hint_dist[htype][0])
            try:
                # Weight the probabilities such that hints that are over the expected proportion
                # will be drawn less, and hints that are under will be drawn more.
                # This tightens the variance quite a bit. The variance can be adjusted via the power
                weighted_hint_prob = []
                for w1_type, w1_prob in zip(hint_types, hint_prob):
                    p = w1_prob
                    if p != 0: # If the base prob is 0, then it's 0
                        for w2_type, w2_prob in zip(hint_types, hint_prob):
                            if w2_prob != 0: # If the other prob is 0, then it has no effect
                                # Raising this term to a power greater than 1 will decrease variance
                                # Conversely, a power less than 1 will increase variance
                                p = p * (((hint_counts.get(w2_type, 0) / w2_prob) + 1) / ((hint_counts.get(w1_type, 0) / w1_prob) + 1))
                    weighted_hint_prob.append(p)

                hint_type = random_choices(hint_types, weights=weighted_hint_prob)[0]
                copies = hint_dist[hint_type][1]
            except IndexError:
                raise Exception('Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
            # Zero out the probability in the base distribution in case the probability list is modified
            # to fit hint types in remaining gossip stones
            hint_dist[hint_type] = (0.0, copies)
        else:
            gossip_text, location = hint
            place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, copies, location)
            if place_ok:
                hint_counts[hint_type] = hint_counts.get(hint_type, 0) + 1
                if location is None:
                    logging.getLogger('').debug('Placed %s hint.', hint_type)
                else:
                    logging.getLogger('').debug('Placed %s hint for %s.', hint_type, location.name)
            if not place_ok and custom_fixed:
                logging.getLogger('').debug('Failed to place %s fixed hint for %s.', hint_type, location.name)
                fixed_hint_types.insert(0, hint_type)
예제 #20
0
def get_junk_item(count=1):
    junk_items, junk_weights = zip(*junk_pool)
    return random_choices(junk_items, weights=junk_weights, k=count)
예제 #21
0
def buildGossipHints(spoiler, world):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = False

    max_states = State.get_states_with_items([w.state for w in spoiler.worlds],
                                             [])
    for id, stone in gossipLocations.items():
        stone.reachable = \
            max_states[world.id].can_reach(stone.location, resolution_hint='Location') and \
            max_states[world.id].guarantee_hint()

    checkedLocations = []

    stoneIDs = list(gossipLocations.keys())
    random.shuffle(stoneIDs)

    hint_dist = hint_dist_sets[world.hint_dist]
    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, hint_count = zip(*hint_prob)

    # Add required location hints
    alwaysLocations = getHintGroup('alwaysLocation', world)
    for hint in alwaysLocations:
        location = world.get_location(hint.name)
        checkedLocations.append(hint.name)
        add_hint(spoiler, world, stoneIDs, buildHintString(colorText(getHint(location.name, world.clearer_hints).text, 'Green') + " " + \
            colorText(getHint(getItemGenericName(location.item), world.clearer_hints).text, 'Red') + "."), hint_dist['always'][1], location, force_reachable=True)

    # Add trial hints
    if world.trials_random and world.trials == 6:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 buildHintString(
                     colorText("Ganon's Tower", 'Pink') +
                     " is protected by a powerful barrier."),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials_random and world.trials == 0:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 buildHintString("Sheik dispelled the barrier around " +
                                 colorText("Ganon's Tower", 'Yellow')),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials < 6 and world.trials > 3:
        for trial, skipped in world.skipped_trials.items():
            if skipped:
                add_hint(
                    spoiler,
                    world,
                    stoneIDs,
                    buildHintString("the " +
                                    colorText(trial + " Trial", 'Yellow') +
                                    " was dispelled by Sheik."),
                    hint_dist['trial'][1],
                    force_reachable=True)
    elif world.trials <= 3 and world.trials > 0:
        for trial, skipped in world.skipped_trials.items():
            if not skipped:
                add_hint(spoiler,
                         world,
                         stoneIDs,
                         buildHintString("the " +
                                         colorText(trial + " Trial", 'Pink') +
                                         " protects Ganon's Tower."),
                         hint_dist['trial'][1],
                         force_reachable=True)

    hint_types = list(hint_types)
    hint_prob = list(hint_prob)
    if world.hint_dist == "tournament":
        fixed_hint_types = []
        for hint_type in hint_types:
            fixed_hint_types.extend([hint_type] * int(hint_dist[hint_type][0]))

    while stoneIDs:
        if world.hint_dist == "tournament":
            if fixed_hint_types:
                hint_type = fixed_hint_types.pop(0)
            else:
                hint_type = 'loc'
        else:
            try:
                [hint_type] = random_choices(hint_types, weights=hint_prob)
            except IndexError:
                raise Exception(
                    'Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
        else:
            text, location = hint
            place_ok = add_hint(spoiler, world, stoneIDs, text,
                                hint_dist[hint_type][1], location)
            if not place_ok and world.hint_dist == "tournament":
                fixed_hint_types.insert(0, hint_type)
예제 #22
0
def buildWorldGossipHints(spoiler, world, checkedLocations=None):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = False
    world.woth_dungeon = 0

    search = Search.max_explore([w.state for w in spoiler.worlds])
    for stone in gossipLocations.values():
        stone.reachable = (search.spot_access(
            world.get_location(stone.location))
                           and search.state_list[world.id].guarantee_hint())

    if checkedLocations is None:
        checkedLocations = set()

    stoneIDs = list(gossipLocations.keys())

    world.distribution.configure_gossip(spoiler, stoneIDs)

    random.shuffle(stoneIDs)

    hint_dist = hint_dist_sets[world.hint_dist]
    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, _ = zip(*hint_prob)

    # Add required location hints
    alwaysLocations = getHintGroup('always', world)
    for hint in alwaysLocations:
        location = world.get_location(hint.name)
        checkedLocations.add(hint.name)

        location_text = getHint(location.name, world.clearer_hints).text
        if '#' not in location_text:
            location_text = '#%s#' % location_text
        item_text = getHint(getItemGenericName(location.item),
                            world.clearer_hints).text
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 GossipText('%s #%s#.' % (location_text, item_text),
                            ['Green', 'Red']),
                 hint_dist['always'][1],
                 location,
                 force_reachable=True)

    # Add trial hints
    if world.trials_random and world.trials == 6:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 GossipText(
                     "#Ganon's Tower# is protected by a powerful barrier.",
                     ['Pink']),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials_random and world.trials == 0:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 GossipText(
                     "Sheik dispelled the barrier around #Ganon's Tower#.",
                     ['Yellow']),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials < 6 and world.trials > 3:
        for trial, skipped in world.skipped_trials.items():
            if skipped:
                add_hint(spoiler,
                         world,
                         stoneIDs,
                         GossipText(
                             "the #%s Trial# was dispelled by Sheik." % trial,
                             ['Yellow']),
                         hint_dist['trial'][1],
                         force_reachable=True)
    elif world.trials <= 3 and world.trials > 0:
        for trial, skipped in world.skipped_trials.items():
            if not skipped:
                add_hint(spoiler,
                         world,
                         stoneIDs,
                         GossipText(
                             "the #%s Trial# protects Ganon's Tower." % trial,
                             ['Pink']),
                         hint_dist['trial'][1],
                         force_reachable=True)

    hint_types = list(hint_types)
    hint_prob = list(hint_prob)
    hint_counts = {}

    if world.hint_dist == "tournament":
        fixed_hint_types = []
        for hint_type in hint_types:
            fixed_hint_types.extend([hint_type] * int(hint_dist[hint_type][0]))
        fill_hint_types = ['sometimes', 'random']
        current_fill_type = fill_hint_types.pop(0)

    while stoneIDs:
        if world.hint_dist == "tournament":
            if fixed_hint_types:
                hint_type = fixed_hint_types.pop(0)
            else:
                hint_type = current_fill_type
        else:
            try:
                # Weight the probabilities such that hints that are over the expected proportion
                # will be drawn less, and hints that are under will be drawn more.
                # This tightens the variance quite a bit. The variance can be adjusted via the power
                weighted_hint_prob = []
                for w1_type, w1_prob in zip(hint_types, hint_prob):
                    p = w1_prob
                    if p != 0:  # If the base prob is 0, then it's 0
                        for w2_type, w2_prob in zip(hint_types, hint_prob):
                            if w2_prob != 0:  # If the other prob is 0, then it has no effect
                                # Raising this term to a power greater than 1 will decrease variance
                                # Conversely, a power less than 1 will increase variance
                                p = p * (
                                    ((hint_counts.get(w2_type, 0) / w2_prob) +
                                     1) /
                                    ((hint_counts.get(w1_type, 0) / w1_prob) +
                                     1))
                    weighted_hint_prob.append(p)

                hint_type = random_choices(hint_types,
                                           weights=weighted_hint_prob)[0]
            except IndexError:
                raise Exception(
                    'Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
            if world.hint_dist == "tournament" and hint_type == current_fill_type:
                logging.getLogger('').info(
                    'Not enough valid %s hints for tournament distribution.',
                    hint_type)
                if fill_hint_types:
                    current_fill_type = fill_hint_types.pop(0)
                    logging.getLogger('').info(
                        'Switching to %s hints to fill remaining gossip stone locations.',
                        current_fill_type)
                else:
                    raise Exception(
                        'Not enough valid hints for tournament distribution.')
        else:
            gossip_text, location = hint
            place_ok = add_hint(spoiler, world, stoneIDs, gossip_text,
                                hint_dist[hint_type][1], location)
            if place_ok:
                hint_counts[hint_type] = hint_counts.get(hint_type, 0) + 1
            if not place_ok and world.hint_dist == "tournament":
                logging.getLogger('').debug('Failed to place %s hint for %s.',
                                            hint_type, location.name)
                fixed_hint_types.insert(0, hint_type)
예제 #23
0
def random_piece(count, allowed=range(0,5)):
    return random_choices(allowed, k=count)