Skip to content

Commit 7802bc7

Browse files
authored
Merge pull request #91 from ManualForArchipelago/Rule-functions-Recursion
Rule functions recursion
2 parents aca96f3 + 47fe8bd commit 7802bc7

File tree

2 files changed

+33
-16
lines changed

2 files changed

+33
-16
lines changed

src/Rules.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -78,27 +78,39 @@ def checkRequireStringForArea(state: CollectionState, area: dict):
7878
if requires_list == "":
7979
return True
8080

81-
for item in re.findall(r'\{(\w+)\((.*?)\)\}', requires_list):
82-
func_name = item[0]
83-
func_args = item[1].split(",")
84-
if func_args == ['']:
85-
func_args.pop()
81+
def findAndRecursivelyExecuteFunctions(requires_list: str, recursionDepth: int = 0) -> str:
82+
found_functions = re.findall(r'\{(\w+)\((.*?)\)\}', requires_list)
83+
if found_functions:
84+
if recursionDepth >= world.rules_functions_maximum_recursion:
85+
raise RecursionError(f'One or more functions in "{area.get("name", f"An area with these parameters: {area}")}"\'s requires looped too many time (maximum recursion is {world.rules_functions_maximum_recursion}) \
86+
\n As of this Exception the following function(s) are waiting to run: {[f[0] for f in found_functions]} \
87+
\n And the currently processed requires look like this: "{requires_list}"')
88+
else:
89+
for item in found_functions:
90+
func_name = item[0]
91+
func_args = item[1].split(",")
92+
if func_args == ['']:
93+
func_args.pop()
8694

87-
func = globals().get(func_name)
95+
func = globals().get(func_name)
8896

89-
if func is None:
90-
func = getattr(Rules, func_name, None)
97+
if func is None:
98+
func = getattr(Rules, func_name, None)
9199

92-
if not callable(func):
93-
raise ValueError(f"Invalid function `{func_name}` in {area}.")
100+
if not callable(func):
101+
raise ValueError(f"Invalid function `{func_name}` in {area}.")
94102

95-
convert_req_function_args(func, func_args, area.get("name", f"An area with these parameters: {area}"))
96-
result = func(world, multiworld, state, player, *func_args)
97-
if isinstance(result, bool):
98-
requires_list = requires_list.replace("{" + func_name + "(" + item[1] + ")}", "1" if result else "0")
99-
else:
100-
requires_list = requires_list.replace("{" + func_name + "(" + item[1] + ")}", str(result))
103+
convert_req_function_args(func, func_args, area.get("name", f"An area with these parameters: {area}"))
104+
result = func(world, multiworld, state, player, *func_args)
105+
if isinstance(result, bool):
106+
requires_list = requires_list.replace("{" + func_name + "(" + item[1] + ")}", "1" if result else "0")
107+
else:
108+
requires_list = requires_list.replace("{" + func_name + "(" + item[1] + ")}", str(result))
109+
110+
requires_list = findAndRecursivelyExecuteFunctions(requires_list, recursionDepth + 1)
111+
return requires_list
101112

113+
requires_list = findAndRecursivelyExecuteFunctions(requires_list)
102114

103115
# parse user written statement into list of each item
104116
for item in re.findall(r'\|[^|]+\|', requires_list):

src/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,11 @@ def extend_hint_information(self, hint_data: dict[int, dict[int, str]]) -> None:
354354
# Non-standard AP world methods
355355
###
356356

357+
rules_functions_maximum_recursion: int = 5
358+
"""Default: 5\n
359+
The maximum time a location/region's requirement can loop to check for functions\n
360+
One thing to remember is the more you loop the longer generation will take. So probably leave it as is unless you really needs it."""
361+
357362
def add_filler_items(self, item_pool, traps):
358363
Utils.deprecate("Use adjust_filler_items instead.")
359364
return self.adjust_filler_items(item_pool, traps)

0 commit comments

Comments
 (0)