Exemple #1
0
def flood_items(world: MultiWorld) -> None:
    # get items to distribute
    world.random.shuffle(world.itempool)
    itempool = world.itempool
    progress_done = False

    # sweep once to pick up preplaced items
    world.state.sweep_for_events()

    # fill world from top of itempool while we can
    while not progress_done:
        location_list = world.get_unfilled_locations()
        world.random.shuffle(location_list)
        spot_to_fill = None
        for location in location_list:
            if location.can_fill(world.state, itempool[0]):
                spot_to_fill = location
                break

        if spot_to_fill:
            item = itempool.pop(0)
            world.push_item(spot_to_fill, item, True)
            continue

        # ran out of spots, check if we need to step in and correct things
        if len(world.get_reachable_locations()) == len(world.get_locations()):
            progress_done = True
            continue

        # need to place a progress item instead of an already placed item, find candidate
        item_to_place = None
        candidate_item_to_place = None
        for item in itempool:
            if item.advancement:
                candidate_item_to_place = item
                if world.unlocks_new_location(item):
                    item_to_place = item
                    break

        # we might be in a situation where all new locations require multiple items to reach.
        # If that is the case, just place any advancement item we've found and continue trying
        if item_to_place is None:
            if candidate_item_to_place is not None:
                item_to_place = candidate_item_to_place
            else:
                raise FillError('No more progress items left to place.')

        # find item to replace with progress item
        location_list = world.get_reachable_locations()
        world.random.shuffle(location_list)
        for location in location_list:
            if location.item is not None and not location.item.advancement:
                # safe to replace
                replace_item = location.item
                replace_item.location = None
                itempool.append(replace_item)
                world.push_item(location, item_to_place, True)
                itempool.remove(item_to_place)
                break
Exemple #2
0
def fill_restrictive(world: MultiWorld,
                     base_state: CollectionState,
                     locations: typing.List[Location],
                     itempool: typing.List[Item],
                     single_player_placement: bool = False,
                     lock: bool = False) -> None:
    unplaced_items: typing.List[Item] = []
    placements: typing.List[Location] = []

    swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
    reachable_items: typing.Dict[int, typing.Deque[Item]] = {}
    for item in itempool:
        reachable_items.setdefault(item.player, deque()).append(item)

    while any(reachable_items.values()) and locations:
        # grab one item per player
        items_to_place = [
            items.pop() for items in reachable_items.values() if items
        ]
        for item in items_to_place:
            itempool.remove(item)
        maximum_exploration_state = sweep_from_pool(base_state,
                                                    itempool + unplaced_items)

        has_beaten_game = world.has_beaten_game(maximum_exploration_state)

        for item_to_place in items_to_place:
            spot_to_fill: typing.Optional[Location] = None
            if world.accessibility[item_to_place.player] == 'minimal':
                perform_access_check = not world.has_beaten_game(maximum_exploration_state,
                                                                 item_to_place.player) \
                    if single_player_placement else not has_beaten_game
            else:
                perform_access_check = True

            for i, location in enumerate(locations):
                if (not single_player_placement or location.player == item_to_place.player) \
                        and location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
                    # poping by index is faster than removing by content,
                    spot_to_fill = locations.pop(i)
                    # skipping a scan for the element
                    break

            else:
                # we filled all reachable spots.
                # try swapping this item with previously placed items
                for (i, location) in enumerate(placements):
                    placed_item = location.item
                    # Unplaceable items can sometimes be swapped infinitely. Limit the
                    # number of times we will swap an individual item to prevent this
                    swap_count = swapped_items[placed_item.player,
                                               placed_item.name]
                    if swap_count > 1:
                        continue

                    location.item = None
                    placed_item.location = None
                    swap_state = sweep_from_pool(base_state)
                    if (not single_player_placement or location.player == item_to_place.player) \
                            and location.can_fill(swap_state, item_to_place, perform_access_check):

                        # Verify that placing this item won't reduce available locations
                        prev_state = swap_state.copy()
                        prev_state.collect(placed_item)
                        prev_loc_count = len(
                            world.get_reachable_locations(prev_state))

                        swap_state.collect(item_to_place, True)
                        new_loc_count = len(
                            world.get_reachable_locations(swap_state))

                        if new_loc_count >= prev_loc_count:
                            # Add this item to the existing placement, and
                            # add the old item to the back of the queue
                            spot_to_fill = placements.pop(i)

                            swap_count += 1
                            swapped_items[placed_item.player,
                                          placed_item.name] = swap_count

                            reachable_items[placed_item.player].appendleft(
                                placed_item)
                            itempool.append(placed_item)

                            break

                    # Item can't be placed here, restore original item
                    location.item = placed_item
                    placed_item.location = location

                if spot_to_fill is None:
                    # Can't place this item, move on to the next
                    unplaced_items.append(item_to_place)
                    continue

            world.push_item(spot_to_fill, item_to_place, False)
            spot_to_fill.locked = lock
            placements.append(spot_to_fill)
            spot_to_fill.event = item_to_place.advancement

    if len(unplaced_items) > 0 and len(locations) > 0:
        # There are leftover unplaceable items and locations that won't accept them
        if world.can_beat_game():
            logging.warning(
                f'Not all items placed. Game beatable anyway. (Could not place {unplaced_items})'
            )
        else:
            raise FillError(
                f'No more spots to place {unplaced_items}, locations {locations} are invalid. '
                f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}'
            )

    itempool.extend(unplaced_items)