def test_requirement_as_set_2(): req = RequirementAnd([ Requirement.trivial(), _req("A"), ]) assert req.as_set(None) == RequirementSet([ RequirementList([_req("A")]), ])
def test_requirement_as_set_5(): req = RequirementAnd([ _req("A"), _req("B"), _req("C"), ]) assert req.as_set(None) == RequirementSet([ RequirementList([_req("A"), _req("B"), _req("C")]), ])
def _potential_nodes_from( self, node: Node) -> Iterator[Tuple[Node, RequirementSet]]: extra_requirement = _extra_requirement_for_node( self._game, self.node_context(), node) requirement_to_leave = node.requirement_to_leave( self._state.node_context()) for target_node, requirement in self._game.world_list.potential_nodes_from( node, self.node_context()): if target_node is None: continue if requirement_to_leave != Requirement.trivial(): requirement = RequirementAnd( [requirement, requirement_to_leave]) if extra_requirement is not None: requirement = RequirementAnd([requirement, extra_requirement]) yield target_node, requirement.as_set( self._state.resource_database)
def calculate_reach(cls, logic: Logic, initial_state: State) -> "ResolverReach": checked_nodes: Dict[Node, int] = {} database = initial_state.resource_database context = initial_state.node_context() # Keys: nodes to check # Value: how much energy was available when visiting that node nodes_to_check: Dict[Node, int] = { initial_state.node: initial_state.energy } reach_nodes: Dict[Node, int] = {} requirements_by_node: Dict[Node, Set[RequirementList]] = defaultdict(set) path_to_node: Dict[Node, Tuple[Node, ...]] = {} path_to_node[initial_state.node] = tuple() while nodes_to_check: node = next(iter(nodes_to_check)) energy = nodes_to_check.pop(node) if node.heal: energy = initial_state.maximum_energy checked_nodes[node] = energy if node != initial_state.node: reach_nodes[node] = energy requirement_to_leave = node.requirement_to_leave(context) for target_node, requirement in logic.game.world_list.potential_nodes_from( node, context): if target_node is None: continue if checked_nodes.get(target_node, math.inf) <= energy or nodes_to_check.get( target_node, math.inf) <= energy: continue if requirement_to_leave != Requirement.trivial(): requirement = RequirementAnd( [requirement, requirement_to_leave]) # Check if the normal requirements to reach that node is satisfied satisfied = requirement.satisfied(initial_state.resources, energy, database) if satisfied: # If it is, check if we additional requirements figured out by backtracking is satisfied satisfied = logic.get_additional_requirements( node).satisfied(initial_state.resources, energy, initial_state.resource_database) if satisfied: nodes_to_check[target_node] = energy - requirement.damage( initial_state.resources, database) path_to_node[target_node] = path_to_node[node] + (node, ) elif target_node: # If we can't go to this node, store the reason in order to build the satisfiable requirements. # Note we ignore the 'additional requirements' here because it'll be added on the end. requirements_by_node[target_node].update( requirement.as_set( initial_state.resource_database).alternatives) # Discard satisfiable requirements of nodes reachable by other means for node in set(reach_nodes.keys()).intersection( requirements_by_node.keys()): requirements_by_node.pop(node) if requirements_by_node: satisfiable_requirements = frozenset.union(*[ RequirementSet(requirements).union( logic.get_additional_requirements(node)).alternatives for node, requirements in requirements_by_node.items() ]) else: satisfiable_requirements = frozenset() return ResolverReach(reach_nodes, path_to_node, satisfiable_requirements, logic)