def test_possible_actions_empty(): state = MagicMock() reach = ResolverReach({}, {}, frozenset(), MagicMock()) options = list(reach.possible_actions(state)) assert options == []
def test_possible_actions_no_resources(): state = MagicMock() reach = ResolverReach([MagicMock(), MagicMock()], {}, frozenset(), MagicMock()) options = list(reach.possible_actions(state)) assert options == []
def _on_show_path_to_here(self): target: QtWidgets.QTreeWidgetItem = self.possible_locations_tree.currentItem( ) if target is None: return node: Optional[Node] = getattr(target, "node", None) if node is not None: reach = ResolverReach.calculate_reach( self.logic, self.state_for_current_configuration()) path = reach.path_to_node.get(node, []) wl = self.logic.game.world_list text = [ f"<p><span style='font-weight:600;'>Path to {node.name}</span></p><ul>" ] for p in path: text.append("<li>{}</li>".format( wl.node_name(p, with_world=True, distinguish_dark_aether=True))) text.append("</ul>") dialog = ScrollLabelDialog("".join(text), "Path to node", self) dialog.exec_() else: QtWidgets.QMessageBox.warning( self, "Invalid target", "Can't find a path to {}. Please select a node.".format( target.text(0)))
def update_locations_tree_for_reachable_nodes(self): # Calculate which nodes are in reach right now state = self.state_for_current_configuration() if state is None: nodes_in_reach = set() else: reach = ResolverReach.calculate_reach(self.logic, state) nodes_in_reach = set(reach.nodes) nodes_in_reach.add(state.node) for world in self.game_description.world_list.worlds: for area in world.areas: area_is_visible = False for node in area.nodes: is_collected = node in self._collected_nodes is_visible = node in nodes_in_reach and not ( self._hide_collected_resources and is_collected) if self._show_only_resource_nodes: is_visible = is_visible and node.is_resource_node node_item = self._node_to_item[node] node_item.setHidden(not is_visible) if node.is_resource_node: node_item.setCheckState( 0, Qt.Checked if is_collected else Qt.Unchecked) area_is_visible = area_is_visible or is_visible self._asset_id_to_item[area.area_asset_id].setHidden( not area_is_visible)
def current_nodes_in_reach(self, state: Optional[State]): if state is None: nodes_in_reach = set() else: reach = ResolverReach.calculate_reach(self.logic, state) nodes_in_reach = set(reach.nodes) nodes_in_reach.add(state.node) return nodes_in_reach
def test_possible_actions_with_event(): logic = MagicMock() state = MagicMock() event = MagicMock(spec=EventNode) state.has_resource.return_value = False # Run reach = ResolverReach([event], {}, frozenset(), logic) options = list(reach.possible_actions(state)) # Assert assert options == [event] event.resource.assert_called_once_with() state.has_resource.assert_called_once_with(event.resource.return_value) logic.get_additional_requirements.assert_called_once_with(event) logic.get_additional_requirements.return_value.satisfied.assert_called_once_with( state.resources, state.resource_database)
def test_possible_actions_no_resources(): state = MagicMock() node_a = MagicMock() node_b = MagicMock() node_b.can_collect.return_value = False type(node_a).is_resource_node = prop_a = PropertyMock(return_value=False) type(node_b).is_resource_node = prop_b = PropertyMock(return_value=True) # Run reach = ResolverReach({node_a: 1, node_b: 1}, {}, frozenset(), MagicMock()) options = list(action for action, damage in reach.possible_actions(state)) # Assert assert options == [] prop_a.assert_called_once_with() prop_b.assert_called_once_with() node_b.can_collect.assert_called_once_with(state.patches, state.resources)
def test_possible_actions_with_event(): logic = MagicMock() state = MagicMock() event = MagicMock(spec=EventNode) type(event).is_resource_node = prop = PropertyMock(return_value=True) event.can_collect.return_value = True # Run reach = ResolverReach({event: 1}, {}, frozenset(), logic) options = list(action for action, damage in reach.possible_actions(state)) # Assert assert options == [event] prop.assert_called_once_with() event.can_collect.assert_called_once_with(state.patches, state.resources) logic.get_additional_requirements.assert_called_once_with(event) logic.get_additional_requirements.return_value.satisfied.assert_called_once_with( state.resources, 1)
def test_possible_actions_with_event(): logic = MagicMock() state = MagicMock() event = MagicMock(spec=EventNode, name="event node") event.node_index = 0 type(event).is_resource_node = prop = PropertyMock(return_value=True) event.can_collect.return_value = True logic.game.world_list.all_nodes = [event] # Run reach = ResolverReach({0: 1}, {}, frozenset(), logic) options = list(action for action, damage in reach.possible_actions(state)) # Assert assert options == [event] prop.assert_called_once_with() state.node_context.assert_called_once_with() event.can_collect.assert_called_once_with(state.node_context.return_value) logic.get_additional_requirements.assert_called_once_with(event) logic.get_additional_requirements.return_value.satisfied.assert_called_once_with( state.resources, 1, state.resource_database)
def test_possible_actions_no_resources(): state = MagicMock() node_a = MagicMock(name="node_a") node_b = MagicMock(name="node_b") node_b.can_collect.return_value = False logic = MagicMock() logic.game.world_list.all_nodes = [node_a, node_b] node_a.get_index.return_value = 0 node_b.get_index.return_value = 1 type(node_a).is_resource_node = prop_a = PropertyMock(return_value=False) type(node_b).is_resource_node = prop_b = PropertyMock(return_value=True) # Run reach = ResolverReach({0: 1, 1: 1}, {}, frozenset(), logic) options = list(action for action, damage in reach.possible_actions(state)) # Assert assert options == [] prop_a.assert_called_once_with() prop_b.assert_called_once_with() state.node_context.assert_called_once_with() node_b.can_collect.assert_called_once_with(state.node_context.return_value)
def update_locations_tree_for_reachable_nodes(self): # Calculate which nodes are in reach right now state = self.state_for_current_configuration() if state is None: nodes_in_reach = set() else: reach = ResolverReach.calculate_reach(self.logic, state) nodes_in_reach = set(reach.nodes) nodes_in_reach.add(state.node) for world in self.game_description.world_list.worlds: for area in world.areas: area_is_visible = False for node in area.nodes: is_collected = node in self._collected_nodes is_visible = node in nodes_in_reach and not ( self._hide_collected_resources and is_collected) if self._show_only_resource_nodes: is_visible = is_visible and node.is_resource_node node_item = self._node_to_item[node] node_item.setHidden(not is_visible) if node.is_resource_node: resource_node: ResourceNode = node node_item.setDisabled(not resource_node.can_collect( state.patches, state.resources)) node_item.setCheckState( 0, Qt.Checked if is_collected else Qt.Unchecked) area_is_visible = area_is_visible or is_visible self._asset_id_to_item[area.area_asset_id].setHidden( not area_is_visible) # Persist the current state with self.persistence_path.joinpath("state.json").open( "w") as state_file: json.dump( { "actions": [node.index for node in self._actions], "collected_pickups": { pickup.name: quantity for pickup, quantity in self._collected_pickups.items() } }, state_file)
def _inner_advance_depth( state: State, logic: Logic, status_update: Callable[[str], None], ) -> Tuple[Optional[State], bool]: if logic.game.victory_condition.satisfied(state.resources, state.resource_database): return state, True reach = ResolverReach.calculate_reach(logic, state) debug.log_new_advance(state, reach) status_update("Resolving... {} total resources".format(len( state.resources))) has_action = False for action in reach.satisfiable_actions(state): new_result = _inner_advance_depth(state=state.act_on_node( action, path=reach.path_to_node[action]), logic=logic, status_update=status_update) # We got a positive result. Send it back up if new_result[0] is not None: return new_result else: has_action = True debug.log_rollback(state, has_action) if not has_action: logic.additional_requirements[ state. node] = _simplify_requirement_set_for_additional_requirements( reach.satisfiable_as_requirement_set, state) return None, has_action
async def _inner_advance_depth( state: State, logic: Logic, status_update: Callable[[str], None], *, reach: Optional[ResolverReach] = None, ) -> Tuple[Optional[State], bool]: """ :param state: :param logic: :param status_update: :param reach: A precalculated reach for the given state :return: """ if logic.game.victory_condition.satisfied(state.resources, state.energy): return state, True # Yield back to the asyncio runner, so cancel can do something await asyncio.sleep(0) if reach is None: reach = ResolverReach.calculate_reach(logic, state) debug.log_new_advance(state, reach) status_update("Resolving... {} total resources".format(len( state.resources))) for action, energy in reach.possible_actions(state): if _should_check_if_action_is_safe(state, action, logic.game.dangerous_resources, logic.game.world_list.all_nodes): potential_state = state.act_on_node( action, path=reach.path_to_node[action], new_energy=energy) potential_reach = ResolverReach.calculate_reach( logic, potential_state) # If we can go back to where we were, it's a simple safe node if state.node in potential_reach.nodes: new_result = await _inner_advance_depth( state=potential_state, logic=logic, status_update=status_update, reach=potential_reach, ) if not new_result[1]: debug.log_rollback(state, True, True) # If a safe node was a dead end, we're certainly a dead end as well return new_result debug.log_checking_satisfiable_actions() has_action = False for action, energy in reach.satisfiable_actions( state, logic.game.victory_condition): new_result = await _inner_advance_depth( state=state.act_on_node(action, path=reach.path_to_node[action], new_energy=energy), logic=logic, status_update=status_update, ) # We got a positive result. Send it back up if new_result[0] is not None: return new_result else: has_action = True debug.log_rollback(state, has_action, False) additional_requirements = reach.satisfiable_as_requirement_set if has_action: additional = set() for resource_node in reach.collectable_resource_nodes(state): additional |= logic.get_additional_requirements( resource_node).alternatives additional_requirements = additional_requirements.union( RequirementSet(additional)) logic.additional_requirements[ state.node] = _simplify_additional_requirement_set( additional_requirements, state, logic.game.dangerous_resources) return None, has_action