def random_assumed_filler(logic: Logic, initial_state: State, patches: GamePatches, available_pickups: Tuple[PickupEntry], rng: Random, status_update: Callable[[str], None], ) -> PickupAssignment: pickup_assignment = copy.copy(patches.pickup_assignment) print("Major items: {}".format([item.name for item in available_pickups])) base_reach = advance_reach_with_possible_unsafe_resources(reach_with_all_safe_resources(logic, initial_state)) reaches_for_pickup = {} previous_reach = base_reach for pickup in reversed(available_pickups): print("** Preparing reach for {}".format(pickup.name)) new_reach = copy.deepcopy(previous_reach) add_resource_gain_to_state(new_reach.state, pickup.resource_gain()) new_reach.state.previous_state = new_reach.state new_reach.advance_to(new_reach.state) collect_all_safe_resources_in_reach(new_reach) previous_reach = advance_reach_with_possible_unsafe_resources(new_reach) reaches_for_pickup[pickup] = previous_reach for i, pickup in enumerate(available_pickups): print("\n\n\nWill place {}, have {} pickups left".format(pickup, len(available_pickups) - i - 1)) reach = reaches_for_pickup[pickup] debug.print_actions_of_reach(reach) escape_state = state_with_pickup(reach.state, pickup) total_pickup_nodes = list(_filter_pickups(filter_reachable(reach.nodes, reach))) pickup_nodes = list(filter_unassigned_pickup_nodes(total_pickup_nodes, pickup_assignment)) num_nodes = len(pickup_nodes) actions_weights = { node: len(path) for node, path in reach.shortest_path_from(initial_state.node).items() } try: pickup_node = next(pickup_nodes_that_can_reach(iterate_with_weights(pickup_nodes, actions_weights, rng), reach_with_all_safe_resources(logic, escape_state), set(reach.safe_nodes))) print("Placed {} at {}. Had {} available of {} nodes.".format(pickup.name, logic.game.node_name(pickup_node, True), num_nodes, len(total_pickup_nodes))) except StopIteration: print("\n".join(logic.game.node_name(node, True) for node in reach.safe_nodes)) raise Exception("Couldn't place {}. Had {} available of {} nodes.".format(pickup.name, num_nodes, len(total_pickup_nodes) )) pickup_assignment[pickup_node.pickup_index] = pickup return pickup_assignment
def retcon_playthrough_filler(rng: Random, player_states: Dict[int, PlayerState], status_update: Callable[[str], None], ) -> Tuple[Dict[int, GamePatches], Tuple[str, ...]]: """ Runs the retcon logic. :param rng: :param player_states: :param status_update: :return: A GamePatches for each player and a sequence of placed items. """ debug.debug_print("{}\nRetcon filler started with major items:\n{}".format( "*" * 100, "\n".join( "Player {}: {}".format( player_index, pprint.pformat({ item.name: player_state.pickups_left.count(item) for item in sorted(set(player_state.pickups_left), key=lambda item: item.name) }) ) for player_index, player_state in player_states.items() ) )) last_message = "Starting." def action_report(message: str): status_update("{} {}".format(last_message, message)) for player_state in player_states.values(): player_state.advance_pickup_index_seen_count() player_state.advance_scan_asset_seen_count() players_to_check = [] actions_log = [] while True: if not players_to_check: players_to_check = [player_index for player_index, player_state in player_states.items() if not player_state.victory_condition_satisfied()] if not players_to_check: debug.debug_print("Finished because we can win") break rng.shuffle(players_to_check) player_to_check = players_to_check.pop() current_player = player_states[player_to_check] actions_weights = current_player.calculate_potential_actions(action_report) try: action = next(iterate_with_weights(items=list(actions_weights.keys()), item_weights=actions_weights, rng=rng)) except StopIteration: if actions_weights: action = rng.choice(list(actions_weights.keys())) else: raise UnableToGenerate("Unable to generate; no actions found after placing {} items.".format( len(current_player.reach.state.patches.pickup_assignment))) if isinstance(action, PickupEntry): assert action in current_player.pickups_left all_weights = _calculate_all_pickup_indices_weight(player_states) if all_weights and (current_player.num_random_starting_items_placed >= current_player.configuration.minimum_random_starting_items): player_index, pickup_index = next(iterate_with_weights(items=iter(all_weights.keys()), item_weights=all_weights, rng=rng)) index_owner_state = player_states[player_index] index_owner_state.reach.state.patches = index_owner_state.reach.state.patches.assign_new_pickups([ (pickup_index, PickupTarget(action, player_to_check)), ]) # Place a hint for the new item hint_location = _calculate_hint_location_for_action( action, UncollectedState.from_reach(index_owner_state.reach), pickup_index, rng, index_owner_state.scan_asset_initial_pickups, ) if hint_location is not None: index_owner_state.reach.state.patches = index_owner_state.reach.state.patches.assign_hint( hint_location, Hint(HintType.LOCATION, None, pickup_index)) if pickup_index in index_owner_state.reach.state.collected_pickup_indices: current_player.reach.advance_to(current_player.reach.state.assign_pickup_resources(action)) spoiler_entry = pickup_placement_spoiler_entry(player_to_check, action, index_owner_state.game, pickup_index, hint_location, player_index, len(player_states) > 1) else: current_player.num_random_starting_items_placed += 1 if (current_player.num_random_starting_items_placed > current_player.configuration.maximum_random_starting_items): raise UnableToGenerate("Attempting to place more extra starting items than the number allowed.") spoiler_entry = f"{action.name} as starting item" if len(player_states) > 1: spoiler_entry += f" for Player {player_to_check + 1}" current_player.reach.advance_to(current_player.reach.state.assign_pickup_to_starting_items(action)) actions_log.append(spoiler_entry) debug.debug_print(f"\n>>>> {spoiler_entry}") # TODO: this item is potentially dangerous and we should remove the invalidated paths current_player.pickups_left.remove(action) count_pickups_left = sum(len(player_state.pickups_left) for player_state in player_states.values()) last_message = "{} items left.".format(count_pickups_left) status_update(last_message) else: last_message = "Triggered an event out of {} options.".format(len(actions_weights)) status_update(last_message) debug_print_collect_event(action, current_player.game) # This action is potentially dangerous. Use `act_on` to remove invalid paths current_player.reach.act_on(action) current_player.reach = advance_reach_with_possible_unsafe_resources(current_player.reach) current_player.advance_pickup_index_seen_count() current_player.advance_scan_asset_seen_count() return { index: player_state.reach.state.patches for index, player_state in player_states.items() }, tuple(actions_log)
def retcon_playthrough_filler( logic: Logic, initial_state: State, available_pickups: Tuple[PickupEntry, ...], rng: Random, status_update: Callable[[str], None], ) -> GamePatches: debug.debug_print("Major items: {}".format( [item.name for item in available_pickups])) last_message = "Starting." reach = advance_reach_with_possible_unsafe_resources( reach_with_all_safe_resources(logic, initial_state)) pickup_index_seen_count: Dict[PickupIndex, int] = collections.defaultdict(int) while True: current_uncollected = UncollectedState.from_reach(reach) pickups_left: Dict[str, PickupEntry] = { pickup.name: pickup for pickup in available_pickups if pickup not in reach.state.patches.pickup_assignment.values() } if not pickups_left: debug.debug_print( "Finished because we have nothing else to distribute") break progression_pickups = _calculate_progression_pickups( pickups_left, reach) print_retcon_loop_start(current_uncollected, logic, pickups_left, reach) for pickup_index in reach.state.collected_pickup_indices: pickup_index_seen_count[pickup_index] += 1 print_new_pickup_indices(logic, reach, pickup_index_seen_count) def action_report(message: str): status_update("{} {}".format(last_message, message)) actions_weights = _calculate_potential_actions(reach, progression_pickups, current_uncollected, action_report) try: action = next( iterate_with_weights(list(actions_weights.keys()), actions_weights, rng)) except StopIteration: if actions_weights: action = rng.choice(list(actions_weights.keys())) else: raise RuntimeError( "Unable to generate, no actions found after placing {} items." .format(len(reach.state.patches.pickup_assignment))) if isinstance(action, PickupEntry): pickup_index_weight = { pickup_index: 1 / (min(pickup_index_seen_count[pickup_index], 10)**2) for pickup_index in current_uncollected.indices } assert pickup_index_weight, "Pickups should only be added to the actions dict " \ "when there are unassigned pickups" # print(">>>>>>>>>>>>>") # world_list = logic.game.world_list # for pickup_index in sorted(current_uncollected.indices, key=lambda x: pickup_index_weight[x]): # print("{1:.6f} {2:5}: {0}".format( # world_list.node_name(find_pickup_node_with_index(pickup_index, world_list.all_nodes)), # pickup_index_weight[pickup_index], # pickup_index_seen_count[pickup_index])) pickup_index = next( iterate_with_weights(list(current_uncollected.indices), pickup_index_weight, rng)) # TODO: this item is potentially dangerous and we should remove the invalidated paths next_state = reach.state.assign_pickup_to_index( pickup_index, action) last_message = "Placed {} items so far, {} left.".format( len(next_state.patches.pickup_assignment), len(pickups_left) - 1) status_update(last_message) print_retcon_place_pickup(action, logic, pickup_index) reach.advance_to(next_state) else: last_message = "Triggered an event out of {} options.".format( len(actions_weights)) status_update(last_message) debug_print_collect_event(action, logic) # This action is potentially dangerous. Use `act_on` to remove invalid paths reach.act_on(action) reach = advance_reach_with_possible_unsafe_resources(reach) if logic.game.victory_condition.satisfied( reach.state.resources, reach.state.resource_database): debug.debug_print("Finished because we can win") break return reach.state.patches
def retcon_playthrough_filler( game: GameDescription, initial_state: State, pickups_left: List[PickupEntry], rng: Random, randomization_mode: RandomizationMode, minimum_random_starting_items: int, maximum_random_starting_items: int, status_update: Callable[[str], None], ) -> GamePatches: debug.debug_print("Major items: {}".format( [item.name for item in pickups_left])) last_message = "Starting." reach = advance_reach_with_possible_unsafe_resources( reach_with_all_safe_resources(game, initial_state)) pickup_index_seen_count: Dict[PickupIndex, int] = collections.defaultdict(int) scan_asset_seen_count: Dict[LogbookAsset, int] = collections.defaultdict(int) num_random_starting_items_placed = 0 while pickups_left: current_uncollected = UncollectedState.from_reach(reach) progression_pickups = _calculate_progression_pickups( pickups_left, reach) print_retcon_loop_start(current_uncollected, game, pickups_left, reach) for pickup_index in reach.state.collected_pickup_indices: pickup_index_seen_count[pickup_index] += 1 print_new_resources(game, reach, pickup_index_seen_count, "Pickup Index") for scan_asset in reach.state.collected_scan_assets: scan_asset_seen_count[scan_asset] += 1 print_new_resources(game, reach, scan_asset_seen_count, "Scan Asset") def action_report(message: str): status_update("{} {}".format(last_message, message)) actions_weights = _calculate_potential_actions( reach, progression_pickups, current_uncollected, maximum_random_starting_items - num_random_starting_items_placed, action_report) try: action = next( iterate_with_weights(items=list(actions_weights.keys()), item_weights=actions_weights, rng=rng)) except StopIteration: if actions_weights: action = rng.choice(list(actions_weights.keys())) else: raise UnableToGenerate( "Unable to generate; no actions found after placing {} items." .format(len(reach.state.patches.pickup_assignment))) if isinstance(action, PickupEntry): assert action in pickups_left if randomization_mode is RandomizationMode.FULL: uncollected_indices = current_uncollected.indices elif randomization_mode is RandomizationMode.MAJOR_MINOR_SPLIT: major_indices = { pickup_node.pickup_index for pickup_node in filter_pickup_nodes( reach.state.collected_resource_nodes) if pickup_node.major_location } uncollected_indices = current_uncollected.indices & major_indices if num_random_starting_items_placed >= minimum_random_starting_items and uncollected_indices: pickup_index_weight = { pickup_index: 1 / (min(pickup_index_seen_count[pickup_index], 10)**2) for pickup_index in uncollected_indices } assert pickup_index_weight, "Pickups should only be added to the actions dict " \ "when there are unassigned pickups" pickup_index = next( iterate_with_weights(items=uncollected_indices, item_weights=pickup_index_weight, rng=rng)) next_state = reach.state.assign_pickup_to_index( action, pickup_index) if current_uncollected.logbooks and _should_have_hint( action.item_category): hint_location: Optional[LogbookAsset] = rng.choice( list(current_uncollected.logbooks)) next_state.patches = next_state.patches.assign_hint( hint_location, Hint(HintType.LOCATION, None, pickup_index)) else: hint_location = None print_retcon_place_pickup(action, game, pickup_index, hint_location) else: num_random_starting_items_placed += 1 if num_random_starting_items_placed > maximum_random_starting_items: raise UnableToGenerate( "Attempting to place more extra starting items than the number allowed." ) if debug.debug_level() > 1: print(f"\n--> Adding {action.name} as a starting item") next_state = reach.state.assign_pickup_to_starting_items( action) # TODO: this item is potentially dangerous and we should remove the invalidated paths pickups_left.remove(action) last_message = "Placed {} items so far, {} left.".format( len(next_state.patches.pickup_assignment), len(pickups_left) - 1) status_update(last_message) reach.advance_to(next_state) else: last_message = "Triggered an event out of {} options.".format( len(actions_weights)) status_update(last_message) debug_print_collect_event(action, game) # This action is potentially dangerous. Use `act_on` to remove invalid paths reach.act_on(action) reach = advance_reach_with_possible_unsafe_resources(reach) if game.victory_condition.satisfied(reach.state.resources, reach.state.energy): debug.debug_print("Finished because we can win") break if not pickups_left: debug.debug_print( "Finished because we have nothing else to distribute") return reach.state.patches
def retcon_playthrough_filler( game: GameDescription, initial_state: State, pickups_left: List[PickupEntry], rng: Random, configuration: FillerConfiguration, status_update: Callable[[str], None], ) -> GamePatches: debug.debug_print("{}\nRetcon filler started with major items:\n{}".format( "*" * 100, pprint.pformat({ item.name: pickups_left.count(item) for item in sorted(set(pickups_left), key=lambda item: item.name) }))) last_message = "Starting." minimum_random_starting_items = configuration.minimum_random_starting_items maximum_random_starting_items = configuration.maximum_random_starting_items reach = advance_reach_with_possible_unsafe_resources( reach_with_all_safe_resources(game, initial_state)) pickup_index_seen_count: DefaultDict[PickupIndex, int] = collections.defaultdict(int) scan_asset_seen_count: DefaultDict[LogbookAsset, int] = collections.defaultdict(int) scan_asset_initial_pickups: Dict[LogbookAsset, FrozenSet[PickupIndex]] = {} num_random_starting_items_placed = 0 indices_groups, all_indices = build_available_indices( game.world_list, configuration) while pickups_left: current_uncollected = UncollectedState.from_reach(reach) progression_pickups = _calculate_progression_pickups( pickups_left, reach) print_retcon_loop_start(current_uncollected, game, pickups_left, reach) for pickup_index in reach.state.collected_pickup_indices: pickup_index_seen_count[pickup_index] += 1 print_new_resources(game, reach, pickup_index_seen_count, "Pickup Index") for scan_asset in reach.state.collected_scan_assets: scan_asset_seen_count[scan_asset] += 1 if scan_asset_seen_count[scan_asset] == 1: scan_asset_initial_pickups[scan_asset] = frozenset( reach.state.collected_pickup_indices) print_new_resources(game, reach, scan_asset_seen_count, "Scan Asset") def action_report(message: str): status_update("{} {}".format(last_message, message)) actions_weights = _calculate_potential_actions( reach, progression_pickups, current_uncollected, maximum_random_starting_items - num_random_starting_items_placed, action_report) try: action = next( iterate_with_weights(items=list(actions_weights.keys()), item_weights=actions_weights, rng=rng)) except StopIteration: if actions_weights: action = rng.choice(list(actions_weights.keys())) else: raise UnableToGenerate( "Unable to generate; no actions found after placing {} items." .format(len(reach.state.patches.pickup_assignment))) if isinstance(action, PickupEntry): assert action in pickups_left uncollected_indices = current_uncollected.indices & all_indices if num_random_starting_items_placed >= minimum_random_starting_items and uncollected_indices: pickup_index_weights = _calculate_uncollected_index_weights( uncollected_indices, set(reach.state.patches.pickup_assignment), pickup_index_seen_count, indices_groups, ) assert pickup_index_weights, "Pickups should only be added to the actions dict " \ "when there are unassigned pickups" pickup_index = next( iterate_with_weights(items=iter(uncollected_indices), item_weights=pickup_index_weights, rng=rng)) next_state = reach.state.assign_pickup_to_index( action, pickup_index) # Place a hint for the new item hint_location = _calculate_hint_location_for_action( action, current_uncollected, pickup_index, rng, scan_asset_initial_pickups) if hint_location is not None: next_state.patches = next_state.patches.assign_hint( hint_location, Hint(HintType.LOCATION, None, pickup_index)) print_retcon_place_pickup(action, game, pickup_index, hint_location) else: num_random_starting_items_placed += 1 if num_random_starting_items_placed > maximum_random_starting_items: raise UnableToGenerate( "Attempting to place more extra starting items than the number allowed." ) if debug.debug_level() > 1: print(f"\n--> Adding {action.name} as a starting item") next_state = reach.state.assign_pickup_to_starting_items( action) # TODO: this item is potentially dangerous and we should remove the invalidated paths pickups_left.remove(action) last_message = "Placed {} items so far, {} left.".format( len(next_state.patches.pickup_assignment), len(pickups_left) - 1) status_update(last_message) reach.advance_to(next_state) else: last_message = "Triggered an event out of {} options.".format( len(actions_weights)) status_update(last_message) debug_print_collect_event(action, game) # This action is potentially dangerous. Use `act_on` to remove invalid paths reach.act_on(action) reach = advance_reach_with_possible_unsafe_resources(reach) if game.victory_condition.satisfied(reach.state.resources, reach.state.energy): debug.debug_print("Finished because we can win") break if not pickups_left: debug.debug_print( "Finished because we have nothing else to distribute") return reach.state.patches