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