Esempio n. 1
0
    def simplify(self, keep_comments: bool = False) -> Requirement:
        new_items = expand_items(self.items, RequirementAnd,
                                 Requirement.trivial(), keep_comments)
        if Requirement.impossible() in new_items and mergeable_array(
                self, keep_comments):
            return Requirement.impossible()

        if len(new_items) == 1 and mergeable_array(self, keep_comments):
            return new_items[0]

        return RequirementAnd(new_items, comment=self.comment)
Esempio n. 2
0
    def update_connections(self):
        current_node = self.current_node
        current_connection_node = self.current_connection_node

        assert current_node != current_connection_node or current_node is None

        if self._connections_visualizer is not None:
            self._connections_visualizer.deleteLater()
            self._connections_visualizer = None

        if current_connection_node is None or current_node is None:
            assert len(self.current_area.nodes) <= 1 or not self.edit_mode
            return

        requirement = self.current_area.connections[current_node].get(
            self.current_connection_node, Requirement.impossible())
        if self._collection_for_filtering is not None:
            requirement = requirement.patch_requirements(
                self._collection_for_filtering,
                1.0,
                self.game_description.resource_database,
            )

        self._connections_visualizer = ConnectionsVisualizer(
            self.other_node_alternatives_contents,
            self.alternatives_grid_layout, self.resource_database, requirement,
            False)
Esempio n. 3
0
    def configurable_node_assignment(
            self, configuration: DreadConfiguration, game: GameDescription,
            rng: Random) -> Iterator[NodeConfigurationAssociation]:
        result = []

        rsb = game.resource_database

        requirement_for_type = {
            "POWERBEAM": rsb.requirement_template["Shoot Beam"],
            "BOMB": rsb.requirement_template["Lay Bomb"],
            "MISSILE": rsb.requirement_template["Shoot Missile"],
            "SUPERMISSILE": rsb.requirement_template["Shoot Super Missile"],
            "POWERBOMB": rsb.requirement_template["Lay Power Bomb"],
            "SCREWATTACK": ResourceRequirement.simple(rsb.get_item("Screw")),
            "WEIGHT": Requirement.impossible(),
            "SPEEDBOOST": ResourceRequirement.simple(rsb.get_item("Speed")),
        }

        for node in game.world_list.iterate_nodes():
            if not isinstance(node, ConfigurableNode):
                continue

            result.append((game.world_list.identifier_for_node(node),
                           RequirementAnd([
                               requirement_for_type[block_type]
                               for block_type in node.extra["tile_types"]
                           ]).simplify()))

        return result
Esempio n. 4
0
def find_invalid_strongly_connected_components(game: GameDescription) -> Iterator[str]:
    import networkx
    graph = networkx.DiGraph()

    for node in game.world_list.iterate_nodes():
        if isinstance(node, DockLockNode):
            continue
        graph.add_node(node)

    context = NodeContext(
        patches=GamePatches.create_from_game(game, 0, None),
        current_resources=ResourceCollection.with_database(game.resource_database),
        database=game.resource_database,
        node_provider=game.world_list,
    )

    for node in game.world_list.iterate_nodes():
        if node not in graph:
            continue

        for other, req in game.world_list.potential_nodes_from(node, context):
            if other not in graph:
                continue

            if req != Requirement.impossible():
                graph.add_edge(node, other)

    starting_node = game.world_list.resolve_teleporter_connection(game.starting_location)

    for strong_comp in networkx.strongly_connected_components(graph):
        components: set[Node] = strong_comp

        # The starting location determines the default component
        if starting_node in components:
            continue

        if any(node.extra.get("different_strongly_connected_component", False) for node in components):
            continue

        if len(components) == 1:
            node = next(iter(components))

            # If the component is a single node which is the default node of it's area, allow it
            area = game.world_list.nodes_to_area(node)
            if area.default_node == node.name:
                continue

            # We accept nodes that have no paths out or in.
            if not graph.in_edges(node) and not graph.edges(node):
                continue

        names = sorted(
            game.world_list.node_name(node, with_world=True)
            for node in strong_comp
        )
        yield "Unknown strongly connected component detected containing {} nodes:\n{}".format(len(names), names)
 def patch_requirements(self, static_resources: ResourceCollection,
                        damage_multiplier: float,
                        database: ResourceDatabase) -> Requirement:
     if static_resources.is_resource_set(self.resource):
         if self.satisfied(static_resources, 0, database):
             return Requirement.trivial()
         else:
             return Requirement.impossible()
     else:
         return self.multiply_amount(damage_multiplier)
Esempio n. 6
0
    def replace_connection_with(self, target_node: Node,
                                requirement: Requirement):
        current_node = self.current_node

        if requirement == Requirement.impossible():
            requirement = None

        self.editor.edit_connections(self.current_area, current_node,
                                     target_node, requirement)
        self.update_connections()
        self.area_view_canvas.update()
Esempio n. 7
0
def test_requirement_as_set_3(database):
    def _req(name: str):
        id_req = ResourceRequirement.simple(database.get_item(name))
        return id_req

    req = RequirementOr([
        Requirement.impossible(),
        _req("A"),
    ])
    assert req.as_set(database) == RequirementSet([
        RequirementList([_req("A")]),
    ])
Esempio n. 8
0
    def simplify(self, keep_comments: bool = False) -> Requirement:
        new_items = expand_items(self.items, RequirementOr,
                                 Requirement.impossible(), keep_comments)
        if Requirement.trivial() in new_items and mergeable_array(
                self, keep_comments):
            return Requirement.trivial()

        num_and_requirements = 0
        common_requirements = None
        for item in new_items:
            if isinstance(item, RequirementAnd) and mergeable_array(
                    item, keep_comments):
                num_and_requirements += 1
                if common_requirements is None:
                    common_requirements = item.items
                else:
                    common_requirements = [
                        common for common in common_requirements
                        if common in item.items
                    ]

        # Only extract the common requirements if there's more than 1 requirement
        if num_and_requirements >= 2 and common_requirements:
            simplified_items = []
            common_new_or = []

            for item in new_items:
                if isinstance(item, RequirementAnd) and mergeable_array(
                        item, keep_comments):
                    assert set(common_requirements) <= set(item.items)
                    simplified_condition = [
                        it for it in item.items
                        if it not in common_requirements
                    ]
                    if simplified_condition:
                        common_new_or.append(
                            RequirementAnd(simplified_condition
                                           ) if len(simplified_condition) > 1
                            else simplified_condition[0])
                else:
                    simplified_items.append(item)

            common_requirements.append(RequirementOr(common_new_or))
            simplified_items.append(RequirementAnd(common_requirements))
            final_items = simplified_items

        else:
            final_items = new_items

        if len(final_items) == 1 and mergeable_array(self, keep_comments):
            return final_items[0]

        return RequirementOr(final_items, comment=self.comment)
Esempio n. 9
0
    async def _open_edit_connection(self):
        if self._check_for_edit_dialog():
            return

        from_node = self.current_node
        target_node = self.current_connection_node
        assert from_node is not None
        assert target_node is not None

        requirement = self.current_area.connections[from_node].get(
            target_node, Requirement.impossible())
        editor = ConnectionsEditor(self, self.resource_database, requirement)
        if await self._execute_edit_dialog(editor):
            self.editor.edit_connections(self.current_area, from_node,
                                         target_node, editor.final_requirement)
            self.update_connections()
            self.area_view_canvas.update()
Esempio n. 10
0
def pretty_print_requirement(requirement: Requirement,
                             level: int = 0) -> Iterator[Tuple[int, str]]:
    if requirement == Requirement.impossible():
        yield level, "Impossible"

    elif requirement == Requirement.trivial():
        yield level, "Trivial"

    elif isinstance(requirement, RequirementArrayBase):
        yield from pretty_print_requirement_array(requirement, level)

    elif isinstance(requirement, ResourceRequirement):
        yield level, pretty_print_resource_requirement(requirement)

    elif isinstance(requirement, RequirementTemplate):
        yield level, requirement.template_name
    else:
        raise RuntimeError(
            f"Unknown requirement type: {type(requirement)} - {requirement}")
Esempio n. 11
0
    def state_for_current_configuration(self) -> Optional[State]:
        state = self._initial_state.copy()
        if self._actions:
            state.node = self._actions[-1]

        state.patches = state.patches.assign_elevators(
            (state.world_list.get_teleporter_node(teleporter),
             combo.currentData())
            for teleporter, combo in self._elevator_id_to_combo.items())

        for gate, item in self._translator_gate_to_combo.items():
            scan_visor = self.game_description.resource_database.get_item(
                "Scan")

            requirement: Optional[
                LayoutTranslatorRequirement] = item.currentData()
            if requirement is None:
                translator_req = Requirement.impossible()
            else:
                translator = self.game_description.resource_database.get_item(
                    requirement.item_name)
                translator_req = ResourceRequirement.simple(translator)

            state.patches.configurable_nodes[gate] = RequirementAnd([
                ResourceRequirement.simple(scan_visor),
                translator_req,
            ])

        for pickup, quantity in self._collected_pickups.items():
            for _ in range(quantity):
                add_pickup_to_state(state, pickup)

        for node in self._collected_nodes:
            state.resources.add_resource_gain(
                node.resource_gain_on_collect(state.node_context()))

        return state
Esempio n. 12
0
def base_dock_name_raw(dock_type: DockType, weakness: DockWeakness, connection: AreaIdentifier) -> str:
    expected_connector = "to"
    if weakness.requirement == Requirement.impossible() and weakness.name != "Not Determined":
        expected_connector = "from"
    return f"{dock_type.long_name} {expected_connector} {connection.area_name}"
Esempio n. 13
0
def test_impossible_requirement_as_set():
    assert Requirement.impossible().as_set(None) == RequirementSet.impossible()
Esempio n. 14
0
def test_impossible_requirement_satisfied():
    assert not Requirement.impossible().satisfied(_empty_col(), 99, None)
Esempio n. 15
0
def test_impossible_requirement_damage():
    assert Requirement.impossible().damage(_empty_col(), None) == MAX_DAMAGE
Esempio n. 16
0
def test_impossible_requirement_str():
    assert str(Requirement.impossible()) == "Impossible"
Esempio n. 17
0
    def contextMenuEvent(self, event: QtGui.QContextMenuEvent) -> None:
        local_pos = QPointF(self.mapFromGlobal(event.globalPos()))
        local_pos -= self.get_area_canvas_offset()
        self._next_node_location = self.qt_local_to_game_loc(local_pos)

        menu = QtWidgets.QMenu(self)
        if self.state is None:
            menu.addAction(self._show_all_connections_action)
        if self.edit_mode:
            menu.addAction(self._create_node_action)
            menu.addAction(self._move_node_action)
            self._move_node_action.setEnabled(
                self.highlighted_node is not None)
            if self.highlighted_node is not None:
                self._move_node_action.setText(
                    f"Move {self.highlighted_node.name} here")

        # Areas Menu
        menu.addSeparator()
        areas_at_mouse = self._other_areas_at_position(local_pos)

        for area in areas_at_mouse:
            sub_menu = QtWidgets.QMenu(f"Area: {area.name}", self)
            sub_menu.addAction("View area").triggered.connect(
                functools.partial(self.SelectAreaRequest.emit, area))
            if self.edit_mode:
                sub_menu.addAction(
                    "Create dock here to this area").triggered.connect(
                        functools.partial(self.CreateDockRequest.emit,
                                          self._next_node_location, area))
            menu.addMenu(sub_menu)

        if not areas_at_mouse:
            sub_menu = QtGui.QAction("No areas here", self)
            sub_menu.setEnabled(False)
            menu.addAction(sub_menu)

        # Nodes Menu
        menu.addSeparator()
        nodes_at_mouse = self._nodes_at_position(local_pos)
        if self.highlighted_node in nodes_at_mouse:
            nodes_at_mouse.remove(self.highlighted_node)

        for node in nodes_at_mouse:
            if len(nodes_at_mouse) == 1:
                menu.addAction(node.name).setEnabled(False)
                sub_menu = menu
            else:
                sub_menu = QtWidgets.QMenu(node.name, self)

            sub_menu.addAction("Highlight this").triggered.connect(
                functools.partial(self.SelectNodeRequest.emit, node))
            view_connections = sub_menu.addAction("View connections to this")
            view_connections.setEnabled(
                (self.edit_mode and self.highlighted_node != node) or
                (node in self.area.connections.get(self.highlighted_node, {})))
            view_connections.triggered.connect(
                functools.partial(self.SelectConnectionsRequest.emit, node))

            if self.edit_mode:
                sub_menu.addSeparator()
                sub_menu.addAction(
                    "Replace connection with Trivial").triggered.connect(
                        functools.partial(
                            self.ReplaceConnectionsRequest.emit,
                            node,
                            Requirement.trivial(),
                        ))
                sub_menu.addAction("Remove connection").triggered.connect(
                    functools.partial(
                        self.ReplaceConnectionsRequest.emit,
                        node,
                        Requirement.impossible(),
                    ))
                if areas_at_mouse:
                    move_menu = QtWidgets.QMenu("Move to...", self)
                    for area in areas_at_mouse:
                        move_menu.addAction(area.name).triggered.connect(
                            functools.partial(
                                self.MoveNodeToAreaRequest.emit,
                                node,
                                area,
                            ))
                    sub_menu.addMenu(move_menu)

            if sub_menu != menu:
                menu.addMenu(sub_menu)

        if not nodes_at_mouse:
            sub_menu = QtGui.QAction("No other nodes here", self)
            sub_menu.setEnabled(False)
            menu.addAction(sub_menu)

        # Done

        menu.exec_(event.globalPos())
Esempio n. 18
0
 def final_requirement(self) -> Optional[Requirement]:
     result = self.build_requirement()
     if result == Requirement.impossible():
         return None
     return result
def test_basic_search_with_translator_gate(has_translator: bool,
                                           echoes_resource_database,
                                           echoes_game_patches):
    # Setup
    scan_visor = echoes_resource_database.get_item("DarkVisor")
    nc = functools.partial(NodeIdentifier.create, "Test World", "Test Area A")

    node_a = GenericNode(nc("Node A"), 0, True, None, "", ("default", ), {})
    node_b = GenericNode(nc("Node B"), 1, True, None, "", ("default", ), {})
    node_c = GenericNode(nc("Node C"), 2, True, None, "", ("default", ), {})
    translator_node = ConfigurableNode(
        translator_identif := nc("Translator Gate"), 3, True, None, "",
        ("default", ), {})

    world_list = WorldList([
        World("Test World", [
            Area(
                "Test Area A", None, True,
                [node_a, node_b, node_c, translator_node], {
                    node_a: {
                        node_b: Requirement.trivial(),
                        translator_node: Requirement.trivial(),
                    },
                    node_b: {
                        node_a: Requirement.trivial(),
                    },
                    node_c: {
                        translator_node: Requirement.trivial(),
                    },
                    translator_node: {
                        node_a: Requirement.trivial(),
                        node_c: Requirement.trivial(),
                    },
                }, {})
        ], {})
    ])
    game = GameDescription(RandovaniaGame.METROID_PRIME_ECHOES,
                           DockWeaknessDatabase([], {}, {}, (None, None)),
                           echoes_resource_database, ("default", ),
                           Requirement.impossible(), None, {}, None,
                           world_list)

    patches = echoes_game_patches.assign_node_configuration([
        (translator_identif, ResourceRequirement.simple(scan_visor)),
    ])
    initial_state = State(
        ResourceCollection.from_dict(echoes_resource_database,
                                     {scan_visor: 1 if has_translator else 0}),
        (),
        99,
        node_a,
        patches,
        None,
        StateGameData(echoes_resource_database, game.world_list, 100, 99),
    )

    # Run
    reach = reach_lib.reach_with_all_safe_resources(game, initial_state)

    # Assert
    if has_translator:
        assert set(
            reach.safe_nodes) == {node_a, node_b, translator_node, node_c}
    else:
        assert set(reach.safe_nodes) == {node_a, node_b}