def print_retcon_loop_start(game: GameDescription, pickups_left: Iterator[PickupEntry], reach: GeneratorReach, player_index: int, ): if debug.debug_level() > 0: current_uncollected = UncollectedState.from_reach(reach) if debug.debug_level() > 1: extra = ", pickups_left: {}".format(sorted(set(pickup.name for pickup in pickups_left))) else: extra = "" print("\n\n===============================") print("\n>>> Player {}: From {}, {} open pickup indices, {} open resources{}".format( player_index, game.world_list.node_name(reach.state.node, with_world=True), len(current_uncollected.indices), len(current_uncollected.resources), extra )) if debug.debug_level() > 2: print("\nCurrent reach:") for node in reach.nodes: print("[{!s:>5}, {!s:>5}] {}".format(reach.is_reachable_node(node), reach.is_safe_node(node), game.world_list.node_name(node)))
def get_pickups_that_solves_unreachable( pickups_left: List[PickupEntry], reach: GeneratorReach, uncollected_resource_nodes: List[ResourceNode], ) -> PickupCombinations: """New logic. Given pickup list and a reach, checks the combination of pickups that satisfies on unreachable nodes""" state = reach.state possible_sets = list(reach.unreachable_nodes_with_requirements().values()) context = reach.node_context() uncollected_resources = [ node.resource(context) for node in uncollected_resource_nodes ] all_lists = _requirement_lists_without_satisfied_resources( state, possible_sets, uncollected_resources) result = [] for requirement_list in sorted(all_lists, key=lambda it: it.as_stable_sort_tuple): pickups = pickups_to_solve_list(pickups_left, requirement_list, state) if pickups is not None and pickups: # FIXME: avoid duplicates in result result.append(tuple(pickups)) if debug.debug_level() > 2: print(">> All pickup combinations alternatives:") for items in sorted(result): print("* {}".format(", ".join(p.name for p in items))) return tuple(result)
def collect_all_safe_resources_in_reach(reach: GeneratorReach) -> None: """ :param reach: :return: """ while True: actions = list(_get_safe_resources(reach)) if not actions: break for action in actions: if action.can_collect(reach.node_context()): # assert reach.is_safe_node(action) reach.advance_to(reach.state.act_on_node(action), is_safe=True)
def test_reach_size_from_start(echoes_game_description, default_layout_configuration, minimal_logic, nodes, safe_nodes): # Setup specific_levels = { trick.short_name: LayoutTrickLevel.HYPERMODE for trick in echoes_game_description.resource_database.trick } layout_configuration = dataclasses.replace( default_layout_configuration, trick_level_configuration=TrickLevelConfiguration( minimal_logic=minimal_logic, specific_levels=specific_levels if not minimal_logic else {}), ) player_pool = generator.create_player_pool(Random(15000), layout_configuration, 0) game, state = logic_bootstrap(layout_configuration, player_pool.game, player_pool.patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) >= nodes assert len(list(reach.safe_nodes)) >= safe_nodes
def interesting_resources_for_reach( reach: GeneratorReach) -> FrozenSet[ResourceInfo]: satisfiable_requirements: FrozenSet[RequirementList] = frozenset( itertools.chain.from_iterable( requirements.alternatives for requirements in reach.unreachable_nodes_with_requirements().values())) return game_description.calculate_interesting_resources( satisfiable_requirements, reach.state.resources, reach.state.energy, reach.state.resource_database)
def print_actions_of_reach(reach: GeneratorReach): if _DEBUG_LEVEL <= 1: return game = reach.game actions = get_collectable_resource_nodes_of_reach(reach) for action in actions: print("++ Safe? {1} -- {0} -- Dangerous? {2}".format( game.world_list.node_name(action), reach.is_safe_node(action), action.resource() in game.dangerous_resources))
def print_new_resources( game: GameDescription, reach: GeneratorReach, seen_count: dict[ResourceInfo, int], label: str, ): if debug.debug_level() > 1: world_list = game.world_list for index, count in seen_count.items(): if count == 1: node = find_node_with_resource(index, reach.node_context(), world_list.iterate_nodes()) print("-> New {}: {}".format( label, world_list.node_name(node, with_world=True)))
def _calculate_weights_for( potential_reach: GeneratorReach, current_uncollected: UncollectedState, ) -> float: if potential_reach.victory_condition_satisfied(): return _VICTORY_WEIGHT potential_uncollected = UncollectedState.from_reach( potential_reach) - current_uncollected return sum(( _EVENTS_WEIGHT_MULTIPLIER * int(bool(potential_uncollected.events)), _INDICES_WEIGHT_MULTIPLIER * int(bool(potential_uncollected.indices)), _LOGBOOKS_WEIGHT_MULTIPLIER * int(bool(potential_uncollected.logbooks)), ))
def print_new_resources(game: GameDescription, reach: GeneratorReach, seen_count: Dict[ResourceInfo, int], label: str, ): world_list = game.world_list if debug.debug_level() > 1: for index, count in seen_count.items(): if count == 1: node = find_node_with_resource(index, world_list.all_nodes) print("-> New {}: {}".format(label, world_list.node_name(node, with_world=True))) if debug.debug_level() > 2: paths = reach.shortest_path_from(node) path = paths.get(reach.state.node, []) print([node.name for node in path]) print("")
def test_reach_size_from_start(echoes_game_description): # Setup configuration = LayoutConfiguration.from_params( trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HYPERMODE), ) patches = GamePatches.with_game(echoes_game_description) patches = patches.assign_gate_assignment( base_patches_factory.gate_assignment_for_configuration( configuration, echoes_game_description.resource_database, Random(15000))) game, state = logic_bootstrap(configuration, echoes_game_description, patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) == 26 assert len(list(reach.safe_nodes)) == 4
def test_reach_size_from_start(echoes_game_description, default_layout_configuration): # Setup layout_configuration = dataclasses.replace( default_layout_configuration, trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HYPERMODE), ) player_pool = generator.create_player_pool(Random(15000), layout_configuration, 0) game, state = logic_bootstrap(layout_configuration, player_pool.game, player_pool.patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) == 44 assert len(list(reach.safe_nodes)) == 5
def _calculate_progression_pickups(pickups_left: Iterator[PickupEntry], reach: GeneratorReach, ) -> Tuple[PickupEntry, ...]: satisfiable_requirements: FrozenSet[RequirementList] = frozenset(itertools.chain.from_iterable( requirements.alternatives for requirements in reach.unreachable_nodes_with_requirements().values() )) interesting_resources = calculate_interesting_resources( satisfiable_requirements, reach.state.resources, reach.state.energy, reach.state.resource_database ) progression_pickups = [] for pickup in pickups_left: if pickup in progression_pickups: continue if _resources_in_pickup(pickup, reach.state.resources).intersection(interesting_resources): progression_pickups.append(pickup) return tuple(progression_pickups)
def test_reach_size_from_start(echoes_game_description, default_layout_configuration): # Setup configuration = dataclasses.replace( default_layout_configuration, trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HYPERMODE), ) patches = echoes_game_description.create_game_patches() patches = patches.assign_gate_assignment( base_patches_factory.gate_assignment_for_configuration( configuration, echoes_game_description.resource_database, Random(15000))) game, state = logic_bootstrap(configuration, echoes_game_description, patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) == 25 assert len(list(reach.safe_nodes)) == 4
def test_reach_size_from_start_echoes(small_echoes_game_description, default_layout_configuration): # Setup game = small_echoes_game_description specific_levels = { trick.short_name: LayoutTrickLevel.HYPERMODE for trick in game.resource_database.trick } def item(name: str): return find_resource_info_with_long_name(game.resource_database.item, name) def nodes(*names): result = [game.world_list.node_from_name(name) for name in names] result.sort(key=lambda it: it.index) return result layout_configuration = dataclasses.replace( default_layout_configuration, trick_level=TrickLevelConfiguration( minimal_logic=False, specific_levels=specific_levels, game=default_layout_configuration.game), starting_location=LocationList.with_elements( [game.starting_location], game=RandovaniaGame.PRIME2, )) patches = base_patches_factory.create_base_patches(layout_configuration, Random(15000), game, False, player_index=0) state = bootstrap.calculate_starting_state(game, patches) state.resources[item("Combat Visor")] = 1 state.resources[item("Amber Translator")] = 1 state.resources[item("Scan Visor")] = 1 state.resources[item("Morph Ball")] = 1 state.resources[item("Power Beam")] = 1 state.resources[item("Charge Beam")] = 1 state.resources[item("Grapple Beam")] = 1 state.resources[item("Dark Beam")] = 1 state.resources[item("Dark Ammo")] = 50 state.resources[item("Missile")] = 5 # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert list(reach.nodes) == nodes( "Temple Grounds/Path of Eyes/Front of Translator Gate", "Temple Grounds/Path of Eyes/Lore Scan", "Temple Grounds/Path of Eyes/Translator Gate", "Temple Grounds/Path of Eyes/Door to Torvus Transport Access", "Temple Grounds/Torvus Transport Access/Door to Path of Eyes", "Temple Grounds/Torvus Transport Access/Door to Transport to Torvus Bog", "Temple Grounds/Transport to Torvus Bog/Door to Torvus Transport Access", "Temple Grounds/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Temple Grounds", "Torvus Bog/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Torvus Bog", "Torvus Bog/Transport to Temple Grounds/Door to Temple Transport Access", "Torvus Bog/Temple Transport Access/Door to Transport to Temple Grounds", "Torvus Bog/Temple Transport Access/Door to Torvus Lagoon", "Torvus Bog/Torvus Lagoon/Door to Temple Transport Access", "Torvus Bog/Torvus Lagoon/Door to Path of Roots", "Torvus Bog/Torvus Lagoon/Keybearer Corpse (S-Dly)", "Torvus Bog/Path of Roots/Door to Torvus Lagoon", "Torvus Bog/Path of Roots/Door to Great Bridge", "Torvus Bog/Path of Roots/Pickup (Missile)", "Torvus Bog/Path of Roots/Next to Pickup", "Torvus Bog/Path of Roots/Under Lore Scan", "Torvus Bog/Path of Roots/Lore Scan", "Torvus Bog/Great Bridge/Door to Path of Roots", ) assert len(list(reach.safe_nodes)) == 20
def _filter_collectable(resource_nodes: Iterator[ResourceNode], reach: GeneratorReach) -> Iterator[ResourceNode]: for resource_node in resource_nodes: if resource_node.can_collect(reach.node_context()): yield resource_node
def _filter_reachable(nodes: Iterator[Node], reach: GeneratorReach) -> Iterator[Node]: for node in nodes: if reach.is_reachable_node(node): yield node
def _get_safe_resources(reach: GeneratorReach) -> Iterator[ResourceNode]: yield from _filter_reachable( _filter_out_dangerous_actions( collectable_resource_nodes(reach.safe_nodes, reach), reach.game, reach.node_context()), reach)