def _potential_nodes_from(self, node: Node) -> Iterator[Tuple[Node, Requirement, bool]]: extra_requirement = _extra_requirement_for_node(self._game, node) requirement_to_leave = node.requirement_to_leave(self._state.patches, self._state.resources) for target_node, requirement in self._game.world_list.potential_nodes_from(node, self.state.patches): 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]) satisfied = requirement.satisfied(self._state.resources, self._state.energy) yield target_node, requirement, satisfied
def calculate_reach(cls, logic: Logic, initial_state: State) -> "ResolverReach": checked_nodes: Dict[Node, int] = {} # 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(initial_state.patches, initial_state.resources) for target_node, requirement in logic.game.world_list.potential_nodes_from(node, initial_state.patches): 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) 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) if satisfied: nodes_to_check[target_node] = energy - requirement.damage(initial_state.resources) 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.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)