Core: faster prog balance (#2586)
* Core: rename world to multiworld in balance_multiworld_progression * Core: small optimization to progression balance speed
This commit is contained in:
parent
1a05bad612
commit
e8f96dabe8
41
Fill.py
41
Fill.py
|
@ -550,7 +550,7 @@ def flood_items(world: MultiWorld) -> None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def balance_multiworld_progression(world: MultiWorld) -> None:
|
def balance_multiworld_progression(multiworld: MultiWorld) -> None:
|
||||||
# A system to reduce situations where players have no checks remaining, popularly known as "BK mode."
|
# A system to reduce situations where players have no checks remaining, popularly known as "BK mode."
|
||||||
# Overall progression balancing algorithm:
|
# Overall progression balancing algorithm:
|
||||||
# Gather up all locations in a sphere.
|
# Gather up all locations in a sphere.
|
||||||
|
@ -558,28 +558,28 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
|
||||||
# If other players are below the threshold value, swap progression in this sphere into earlier spheres,
|
# If other players are below the threshold value, swap progression in this sphere into earlier spheres,
|
||||||
# which gives more locations available by this sphere.
|
# which gives more locations available by this sphere.
|
||||||
balanceable_players: typing.Dict[int, float] = {
|
balanceable_players: typing.Dict[int, float] = {
|
||||||
player: world.worlds[player].options.progression_balancing / 100
|
player: multiworld.worlds[player].options.progression_balancing / 100
|
||||||
for player in world.player_ids
|
for player in multiworld.player_ids
|
||||||
if world.worlds[player].options.progression_balancing > 0
|
if multiworld.worlds[player].options.progression_balancing > 0
|
||||||
}
|
}
|
||||||
if not balanceable_players:
|
if not balanceable_players:
|
||||||
logging.info('Skipping multiworld progression balancing.')
|
logging.info('Skipping multiworld progression balancing.')
|
||||||
else:
|
else:
|
||||||
logging.info(f'Balancing multiworld progression for {len(balanceable_players)} Players.')
|
logging.info(f'Balancing multiworld progression for {len(balanceable_players)} Players.')
|
||||||
logging.debug(balanceable_players)
|
logging.debug(balanceable_players)
|
||||||
state: CollectionState = CollectionState(world)
|
state: CollectionState = CollectionState(multiworld)
|
||||||
checked_locations: typing.Set[Location] = set()
|
checked_locations: typing.Set[Location] = set()
|
||||||
unchecked_locations: typing.Set[Location] = set(world.get_locations())
|
unchecked_locations: typing.Set[Location] = set(multiworld.get_locations())
|
||||||
|
|
||||||
total_locations_count: typing.Counter[int] = Counter(
|
total_locations_count: typing.Counter[int] = Counter(
|
||||||
location.player
|
location.player
|
||||||
for location in world.get_locations()
|
for location in multiworld.get_locations()
|
||||||
if not location.locked
|
if not location.locked
|
||||||
)
|
)
|
||||||
reachable_locations_count: typing.Dict[int, int] = {
|
reachable_locations_count: typing.Dict[int, int] = {
|
||||||
player: 0
|
player: 0
|
||||||
for player in world.player_ids
|
for player in multiworld.player_ids
|
||||||
if total_locations_count[player] and len(world.get_filled_locations(player)) != 0
|
if total_locations_count[player] and len(multiworld.get_filled_locations(player)) != 0
|
||||||
}
|
}
|
||||||
balanceable_players = {
|
balanceable_players = {
|
||||||
player: balanceable_players[player]
|
player: balanceable_players[player]
|
||||||
|
@ -658,7 +658,7 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
|
||||||
balancing_unchecked_locations.remove(location)
|
balancing_unchecked_locations.remove(location)
|
||||||
if not location.locked:
|
if not location.locked:
|
||||||
balancing_reachables[location.player] += 1
|
balancing_reachables[location.player] += 1
|
||||||
if world.has_beaten_game(balancing_state) or all(
|
if multiworld.has_beaten_game(balancing_state) or all(
|
||||||
item_percentage(player, reachables) >= threshold_percentages[player]
|
item_percentage(player, reachables) >= threshold_percentages[player]
|
||||||
for player, reachables in balancing_reachables.items()
|
for player, reachables in balancing_reachables.items()
|
||||||
if player in threshold_percentages):
|
if player in threshold_percentages):
|
||||||
|
@ -675,7 +675,7 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
|
||||||
locations_to_test = unlocked_locations[player]
|
locations_to_test = unlocked_locations[player]
|
||||||
items_to_test = list(candidate_items[player])
|
items_to_test = list(candidate_items[player])
|
||||||
items_to_test.sort()
|
items_to_test.sort()
|
||||||
world.random.shuffle(items_to_test)
|
multiworld.random.shuffle(items_to_test)
|
||||||
while items_to_test:
|
while items_to_test:
|
||||||
testing = items_to_test.pop()
|
testing = items_to_test.pop()
|
||||||
reducing_state = state.copy()
|
reducing_state = state.copy()
|
||||||
|
@ -687,8 +687,8 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
|
||||||
|
|
||||||
reducing_state.sweep_for_events(locations=locations_to_test)
|
reducing_state.sweep_for_events(locations=locations_to_test)
|
||||||
|
|
||||||
if world.has_beaten_game(balancing_state):
|
if multiworld.has_beaten_game(balancing_state):
|
||||||
if not world.has_beaten_game(reducing_state):
|
if not multiworld.has_beaten_game(reducing_state):
|
||||||
items_to_replace.append(testing)
|
items_to_replace.append(testing)
|
||||||
else:
|
else:
|
||||||
reduced_sphere = get_sphere_locations(reducing_state, locations_to_test)
|
reduced_sphere = get_sphere_locations(reducing_state, locations_to_test)
|
||||||
|
@ -696,33 +696,32 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
|
||||||
if p < threshold_percentages[player]:
|
if p < threshold_percentages[player]:
|
||||||
items_to_replace.append(testing)
|
items_to_replace.append(testing)
|
||||||
|
|
||||||
replaced_items = False
|
old_moved_item_count = moved_item_count
|
||||||
|
|
||||||
# sort then shuffle to maintain deterministic behaviour,
|
# sort then shuffle to maintain deterministic behaviour,
|
||||||
# while allowing use of set for better algorithm growth behaviour elsewhere
|
# while allowing use of set for better algorithm growth behaviour elsewhere
|
||||||
replacement_locations = sorted(l for l in checked_locations if not l.event and not l.locked)
|
replacement_locations = sorted(l for l in checked_locations if not l.event and not l.locked)
|
||||||
world.random.shuffle(replacement_locations)
|
multiworld.random.shuffle(replacement_locations)
|
||||||
items_to_replace.sort()
|
items_to_replace.sort()
|
||||||
world.random.shuffle(items_to_replace)
|
multiworld.random.shuffle(items_to_replace)
|
||||||
|
|
||||||
# Start swapping items. Since we swap into earlier spheres, no need for accessibility checks.
|
# Start swapping items. Since we swap into earlier spheres, no need for accessibility checks.
|
||||||
while replacement_locations and items_to_replace:
|
while replacement_locations and items_to_replace:
|
||||||
old_location = items_to_replace.pop()
|
old_location = items_to_replace.pop()
|
||||||
for new_location in replacement_locations:
|
for i, new_location in enumerate(replacement_locations):
|
||||||
if new_location.can_fill(state, old_location.item, False) and \
|
if new_location.can_fill(state, old_location.item, False) and \
|
||||||
old_location.can_fill(state, new_location.item, False):
|
old_location.can_fill(state, new_location.item, False):
|
||||||
replacement_locations.remove(new_location)
|
replacement_locations.pop(i)
|
||||||
swap_location_item(old_location, new_location)
|
swap_location_item(old_location, new_location)
|
||||||
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
|
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
|
||||||
f"displacing {old_location.item} into {old_location}")
|
f"displacing {old_location.item} into {old_location}")
|
||||||
moved_item_count += 1
|
moved_item_count += 1
|
||||||
state.collect(new_location.item, True, new_location)
|
state.collect(new_location.item, True, new_location)
|
||||||
replaced_items = True
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.warning(f"Could not Progression Balance {old_location.item}")
|
logging.warning(f"Could not Progression Balance {old_location.item}")
|
||||||
|
|
||||||
if replaced_items:
|
if old_moved_item_count < moved_item_count:
|
||||||
logging.debug(f"Moved {moved_item_count} items so far\n")
|
logging.debug(f"Moved {moved_item_count} items so far\n")
|
||||||
unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]}
|
unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]}
|
||||||
for location in get_sphere_locations(state, unlocked):
|
for location in get_sphere_locations(state, unlocked):
|
||||||
|
@ -736,7 +735,7 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
|
||||||
state.collect(location.item, True, location)
|
state.collect(location.item, True, location)
|
||||||
checked_locations |= sphere_locations
|
checked_locations |= sphere_locations
|
||||||
|
|
||||||
if world.has_beaten_game(state):
|
if multiworld.has_beaten_game(state):
|
||||||
break
|
break
|
||||||
elif not sphere_locations:
|
elif not sphere_locations:
|
||||||
logging.warning("Progression Balancing ran out of paths.")
|
logging.warning("Progression Balancing ran out of paths.")
|
||||||
|
|
Loading…
Reference in New Issue