@@ -76,6 +76,8 @@ class ChainingOptions:
76
76
subquests:
77
77
Whether to also return incomplete quests, which could be extended
78
78
without reaching the depth or breadth limits.
79
+ independent_chains:
80
+ Whether to allow totally independent parallel chains.
79
81
create_variables:
80
82
Whether new variables may be created during chaining.
81
83
fixed_mapping:
@@ -99,6 +101,7 @@ def __init__(self):
99
101
self .min_breadth = 1
100
102
self .max_breadth = 1
101
103
self .subquests = False
104
+ self .independent_chains = False
102
105
self .create_variables = False
103
106
self .fixed_mapping = data .get_types ().constants_mapping
104
107
self .rng = None
@@ -189,30 +192,17 @@ class _Node:
189
192
A node in a chain being generated.
190
193
191
194
Each node is aware of its position (depth, breadth) in the dependency tree
192
- induced by the chain. For generating parallel quests, the backtracks field
193
- holds actions that can be use to go up the dependency tree and start a new
194
- chain.
195
-
196
- For example, taking the action node.backtracks[i][j] will produce a new node
197
- at depth (i + 1) and breadth (self.breadth + 1). To avoid duplication, in
198
- trees like this:
199
-
200
- root
201
- / | \
202
- A B C
203
- | | |
204
- .......
205
-
206
- A.backtracks[0] will be [B, C], B.backtracks[0] will be [C], and
207
- C.backtracks[0] will be [].
195
+ induced by the chain. To avoid duplication when generating parallel chains,
196
+ each node stores the actions that have already been used at that depth.
208
197
"""
209
198
210
- def __init__ (self , parent , dep_parent , state , action , backtracks , depth , breadth ):
199
+ def __init__ (self , parent , dep_parent , state , action , rules , used , depth , breadth ):
211
200
self .parent = parent
212
201
self .dep_parent = dep_parent
213
202
self .state = state
214
203
self .action = action
215
- self .backtracks = backtracks
204
+ self .rules = rules
205
+ self .used = used
216
206
self .depth = depth
217
207
self .breadth = breadth
218
208
@@ -235,7 +225,7 @@ def __init__(self, state, options):
235
225
236
226
def root (self ) -> _Node :
237
227
"""Create the root node for chaining."""
238
- return _Node (None , None , self .state , None , [], 0 , 1 )
228
+ return _Node (None , None , self .state , None , [], set (), 0 , 1 )
239
229
240
230
def chain (self , node : _Node ) -> Iterable [_Node ]:
241
231
"""
@@ -251,30 +241,21 @@ def chain(self, node: _Node) -> Iterable[_Node]:
251
241
if self .rng :
252
242
self .rng .shuffle (assignments )
253
243
254
- partials = []
255
- actions = []
256
- states = []
244
+ used = set ()
257
245
for partial in assignments :
258
246
action = self .try_instantiate (node .state , partial )
259
247
if not action :
260
248
continue
261
249
262
- if not self .check_action (node , action ):
250
+ if not self .check_action (node , node . state , action ):
263
251
continue
264
252
265
253
state = self .apply (node , action )
266
254
if not state :
267
255
continue
268
256
269
- partials .append (partial )
270
- actions .append (action )
271
- states .append (state )
272
-
273
- for i , action in enumerate (actions ):
274
- # Only allow backtracking into later actions, to avoid duplication
275
- remaining = partials [i + 1 :]
276
- backtracks = node .backtracks + [remaining ]
277
- yield _Node (node , node , states [i ], action , backtracks , node .depth + 1 , node .breadth )
257
+ used = used | {action }
258
+ yield _Node (node , node , state , action , rules , used , node .depth + 1 , node .breadth )
278
259
279
260
def backtrack (self , node : _Node ) -> Iterable [_Node ]:
280
261
"""
@@ -284,21 +265,39 @@ def backtrack(self, node: _Node) -> Iterable[_Node]:
284
265
if node .breadth >= self .max_breadth :
285
266
return
286
267
287
- for i , partials in enumerate (node .backtracks ):
288
- backtracks = node .backtracks [:i ]
289
-
290
- for j , partial in enumerate (partials ):
268
+ parent = node
269
+ parents = []
270
+ while parent .dep_parent :
271
+ if parent .depth == 1 and not self .options .independent_chains :
272
+ break
273
+ parents .append (parent )
274
+ parent = parent .dep_parent
275
+ parents = parents [::- 1 ]
276
+
277
+ for sibling in parents :
278
+ parent = sibling .dep_parent
279
+ rules = self .options .get_rules (parent .depth )
280
+ assignments = self .all_assignments (node , rules )
281
+ if self .rng :
282
+ self .rng .shuffle (assignments )
283
+
284
+ for partial in assignments :
291
285
action = self .try_instantiate (node .state , partial )
292
286
if not action :
293
287
continue
294
288
289
+ if action in sibling .used :
290
+ continue
291
+
292
+ if not self .check_action (parent , node .state , action ):
293
+ continue
294
+
295
295
state = self .apply (node , action )
296
296
if not state :
297
297
continue
298
298
299
- remaining = partials [j + 1 :]
300
- new_backtracks = backtracks + [remaining ]
301
- yield _Node (node , partial .node , state , action , new_backtracks , i + 1 , node .breadth + 1 )
299
+ used = sibling .used | {action }
300
+ yield _Node (node , parent , state , action , rules , used , sibling .depth , node .breadth + 1 )
302
301
303
302
def all_assignments (self , node : _Node , rules : Iterable [Rule ]) -> Iterable [_PartialAction ]:
304
303
"""
@@ -359,7 +358,7 @@ def create_variable(self, state, ph, type_counts):
359
358
type_counts [ph .type ] += 1
360
359
return var
361
360
362
- def check_action (self , node : _Node , action : Action ) -> bool :
361
+ def check_action (self , node : _Node , state : State , action : Action ) -> bool :
363
362
# Find the last action before a navigation action
364
363
# TODO: Fold this behaviour into ChainingOptions.check_action()
365
364
nav_parent = node
@@ -387,7 +386,7 @@ def check_action(self, node: _Node, action: Action) -> bool:
387
386
if len (recent .added & relevant ) == 0 or len (pre_navigation .added & relevant ) == 0 :
388
387
return False
389
388
390
- return self .options .check_action (node . state , action )
389
+ return self .options .check_action (state , action )
391
390
392
391
def _is_navigation (self , action ):
393
392
return action .name .startswith ("go/" )
@@ -405,8 +404,8 @@ def apply(self, node: _Node, action: Action) -> Optional[State]:
405
404
406
405
new_state .apply (action )
407
406
408
- # Some debug checks
409
- assert self . check_state ( new_state )
407
+ if not self . check_state ( new_state ):
408
+ return None
410
409
411
410
# Detect cycles
412
411
state = new_state .copy ()
0 commit comments