Skip to content

Commit

Permalink
Count recursions in entry rule check in client
Browse files Browse the repository at this point in the history
  • Loading branch information
Salzkorn committed Oct 11, 2024
1 parent c56d0d5 commit 4be7d07
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 30 deletions.
19 changes: 3 additions & 16 deletions worlds/sc2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1424,31 +1424,18 @@ def calc_available_nodes(ctx: SC2Context) -> typing.Tuple[typing.List[int], typi
received_items[network_item.item] += 1

accessible_rules: typing.Set[int] = set()
# Determine accessibility from top to bottom to avoid a recursion problem from
# missions trying to access layouts & campaigns appearing later than themselves
# Campaigns
for campaign_idx, campaign in enumerate(ctx.custom_mission_order):
available_layouts[campaign_idx] = []
if campaign.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, set()):
if campaign.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, []):
available_campaigns.append(campaign_idx)

# Layouts
for campaign_idx, campaign in enumerate(ctx.custom_mission_order):
if campaign_idx in available_campaigns:
for layout_idx, layout in enumerate(campaign.layouts):
if layout.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, set()):
if layout.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, []):
available_layouts[campaign_idx].append(layout_idx)

# Missions
for campaign_idx, campaign in enumerate(ctx.custom_mission_order):
if campaign_idx in available_campaigns:
for layout_idx, layout in enumerate(campaign.layouts):
if layout_idx in available_layouts[campaign_idx]:
for column in layout.missions:
for mission in column:
if mission.mission_id == -1:
continue
if mission.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, set()):
if mission.entry_rule.is_accessible(beaten_missions, received_items, ctx.mission_id_to_entry_rules, accessible_rules, []):
available_missions.append(mission.mission_id)

return available_missions, available_layouts, available_campaigns
Expand Down
39 changes: 25 additions & 14 deletions worlds/sc2/mission_order/entry_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
return False

Expand Down Expand Up @@ -121,7 +121,7 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
# Beat rules are accessible if all their missions are beaten and accessible
if not beaten_missions.issuperset(self.mission_ids):
Expand Down Expand Up @@ -182,7 +182,9 @@ def tooltip(self, indents: int, missions: Dict[int, SC2Mission]) -> str:
req = self.visual_reqs[0]
req_str = missions[req].mission_name if type(req) == int else req
if self.amount == 1:
return f"Beat {req_str}"
if type(req) == int:
return f"Beat {req_str}"
return f"Beat any mission from {req_str}"
return f"Beat {amount} missions from {req_str}"
if self.amount == 1:
tooltip = f"Beat any mission from:\n{indent}- "
Expand All @@ -198,7 +200,7 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
# Count rules are accessible if enough of their missions are beaten and accessible
return self.amount <= sum(
Expand Down Expand Up @@ -279,11 +281,16 @@ def parse_from_dict(data: Dict[str, Any]) -> SubRuleRuleData:
**{field: value for field, value in rule_data.items()}
)
sub_rules.append(rule)
return SubRuleRuleData(
rule = SubRuleRuleData(
rule_id,
sub_rules,
amount
)
# Add an accessibility buffer for top level rules
# This is an optimization to make recalculations of large mission orders feel smoother
if rule.rule_id >= 0:
rule.buffer_accessible = False
return rule

@staticmethod
def empty() -> SubRuleRuleData:
Expand All @@ -309,26 +316,30 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
# Early exit check for top-level entry rules
if self.rule_id >= 0:
if self.rule_id in accessible_rules:
if self.buffer_accessible or self.rule_id in accessible_rules:
return True
# Never consider rules discovered via recursion to be accessible
# (unless they succeeded, in which case they will be in accessible_rules)
if self.rule_id in seen_rules:
return False
seen_rules.add(self.rule_id)
seen_rules.append(self.rule_id)
# Sub-rule rules are accessible if enough of their child rules are accessible
if self.amount <= sum(
success = self.amount <= sum(
rule.is_accessible(beaten_missions, received_items, mission_id_to_entry_rules, accessible_rules, seen_rules)
for rule in self.sub_rules
):
if self.rule_id >= 0:
)
if self.rule_id >= 0:
if success:
accessible_rules.add(self.rule_id)
return True
return False
self.buffer_accessible = True
# Rules that failed in the middle of calculating another rule
# should be allowed to try again at a later point
seen_rules.remove(self.rule_id)
return success

class ItemEntryRule(EntryRule):
items_to_check: Dict[str, int]
Expand Down Expand Up @@ -375,7 +386,7 @@ def shows_single_rule(self) -> bool:
def is_accessible(
self, beaten_missions: Set[int], received_items: Dict[int, int],
mission_id_to_entry_rules: Dict[int, MissionEntryRules],
accessible_rules: Set[int], seen_rules: Set[int]
accessible_rules: Set[int], seen_rules: List[int]
) -> bool:
return all(
item in received_items and received_items[item] >= amount
Expand Down

0 comments on commit 4be7d07

Please sign in to comment.