@@ -121,91 +121,129 @@ def distribute_food(all_tiles, chamber_tiles, trapped_food, total_food, rng=None
121121def add_wall_and_split (partition , walls , ngaps , vertical , rng = None ):
122122 rng = default_rng (rng )
123123
124- (xmin , ymin ), (xmax , ymax ) = partition
125-
126- # the size of the maze partition we work on
127- width = xmax - xmin + 1
128- height = ymax - ymin + 1
129-
130- # if the partition is too small, stop
131- if height < 3 and width < 3 :
132- return walls
133-
134- # insert a wall only if there is some space in the around it in the
135- # orthogonal direction, i.e.:
136- # if the wall is vertical, then the relevant length is the width
137- # if the wall is horizontal, then the relevant length is the height
138- partition_length = width if vertical else height
139- if partition_length < rng .randint (3 , 5 ):
140- return walls
141-
142- # the raw/column to put the horizontal/vertical wall on
143- # the position is calculated starting from the left/top of the maze partition
144- # and then a random offset is added -> the resulting raw/column must not
145- # exceed the available length
146- pos = xmin if vertical else ymin
147- pos += rng .randint (1 , partition_length - 2 )
148-
149- # the maximum length of the wall is the space we have in the same direction
150- # of the wall in the partition, i.e.
151- # if the wall is vertical, the maximum length is the height
152- # if the wall is horizontal, the maximum length is the width
153- max_length = height if vertical else width
154-
155- # We can start with a full wall, but we want to make sure that we do not
156- # block the entrances to this partition. The entrances are
157- # - the tile before the beginning of this wall [entrance] and
158- # - the tile after the end of this wall [exit]
159- # if entrance or exit are _not_ walls, then the wall must leave the neighboring
160- # tiles also empty, i.e. the wall must be shortened accordingly
161- if vertical :
162- entrance_before = (pos , ymin - 1 )
163- entrance_after = (pos , ymin + max_length )
164- begin = 0 if entrance_before in walls else 1
165- end = max_length if entrance_after in walls else max_length - 1
166- wall = {(pos , ymin + y ) for y in range (begin , end )}
167- else :
168- entrance_before = (xmin - 1 , pos )
169- entrance_after = (xmin + max_length , pos )
170- begin = 0 if entrance_before in walls else 1
171- end = max_length if entrance_after in walls else max_length - 1
172- wall = {(xmin + x , pos ) for x in range (begin , end )}
173-
174- # place the requested number of gaps in the otherwise full wall
175- # these gaps are indices in the direction of the wall, i.e.
176- # x if horizontal and y if vertical
177- # TODO: when we drop compatibility with numpy, this can be more easily done
178- # by just sampling ngaps out of the full wall set, i.e.
179- # gaps = rng.sample(wall, k=ngaps)
180- # for gap in gaps:
181- # wall.remove(gap)
182- ngaps = max (1 , ngaps )
183- wall_pos = list (range (max_length ))
184- rng .shuffle (wall_pos )
185-
186- for gap in wall_pos [:ngaps ]:
187- if vertical :
188- wall .discard ((pos , ymin + gap ))
189- else :
190- wall .discard ((xmin + gap , pos ))
124+ # store partitions in an expanding list
125+ # alongside the number of gaps in wall and its orientation
126+ partitions = [partition + (ngaps , vertical )]
127+
128+ # partition index
129+ p = 0
130+
131+ # The infinite loop is always exiting, since the position of the walls
132+ # `pos` is in `[xmin + 1, xmax - (xmin + 1)]` or
133+ # in `[ymin + 1, ymax - (ymin + 1)]`, respectively, and thus always
134+ # yielding partitions smaller than the current partition.
135+ # The checks for `height < 3`, `width < 3` and
136+ # `partition_length < rng.randint(3, 5)` ensure no further addition of
137+ # partitions, and `p += 1` in those checks and after partitioning ensure
138+ # that we always advance in the list of partitions.
139+ #
140+ # So, partitions always shrink, no new partitions are added once they
141+ # shrank below a threshold, and the loop increases the list index in
142+ # every case.
143+ while True :
144+ # get the next partition of any is available
145+ try :
146+ partition = partitions [p ]
147+ except IndexError :
148+ break
149+
150+ (xmin , ymin ), (xmax , ymax ), ngaps , vertical = partition
151+
152+ # the size of the maze partition we work on
153+ width = xmax - xmin + 1
154+ height = ymax - ymin + 1
155+
156+ # if the partition is too small, move on with the next one
157+ if height < 3 and width < 3 :
158+ p += 1
159+ continue
191160
192- # collect this wall into the global wall set
193- walls |= wall
161+ # insert a wall only if there is some space in the around it in the
162+ # orthogonal direction, i.e.:
163+ # if the wall is vertical, then the relevant length is the width
164+ # if the wall is horizontal, then the relevant length is the height,
165+ # otherwise move on with the next one
166+ partition_length = width if vertical else height
167+ if partition_length < rng .randint (3 , 5 ):
168+ p += 1
169+ continue
194170
195- # define the two new partitions of the maze generated by this wall
196- # these are the parts of the maze to the left/right of a vertical wall
197- # or the top/bottom of a horizontal wall
198- if vertical :
199- partitions = [((xmin , ymin ), (pos - 1 , ymax )),
200- ((pos + 1 , ymin ), (xmax , ymax ))]
201- else :
202- partitions = [((xmin , ymin ), (xmax , pos - 1 )),
203- ((xmin , pos + 1 ), (xmax , ymax ))]
171+ # the row/column to put the horizontal/vertical wall on
172+ # the position is calculated starting from the left/top of the maze partition
173+ # and then a random offset is added -> the resulting raw/column must not
174+ # exceed the available length
175+ pos = xmin if vertical else ymin
176+ pos += rng .randint (1 , partition_length - 2 )
177+
178+ # the maximum length of the wall is the space we have in the same direction
179+ # of the wall in the partition, i.e.
180+ # if the wall is vertical, the maximum length is the height
181+ # if the wall is horizontal, the maximum length is the width
182+ max_length = height if vertical else width
183+
184+ # We can start with a full wall, but we want to make sure that we do not
185+ # block the entrances to this partition. The entrances are
186+ # - the tile before the beginning of this wall [entrance] and
187+ # - the tile after the end of this wall [exit]
188+ # if entrance or exit are _not_ walls, then the wall must leave the neighboring
189+ # tiles also empty, i.e. the wall must be shortened accordingly
190+ if vertical :
191+ entrance_before = (pos , ymin - 1 )
192+ entrance_after = (pos , ymin + max_length )
193+ begin = 0 if entrance_before in walls else 1
194+ end = max_length if entrance_after in walls else max_length - 1
195+ wall = {(pos , ymin + y ) for y in range (begin , end )}
196+ else :
197+ entrance_before = (xmin - 1 , pos )
198+ entrance_after = (xmin + max_length , pos )
199+ begin = 0 if entrance_before in walls else 1
200+ end = max_length if entrance_after in walls else max_length - 1
201+ wall = {(xmin + x , pos ) for x in range (begin , end )}
202+
203+ # place the requested number of gaps in the otherwise full wall
204+ # these gaps are indices in the direction of the wall, i.e.
205+ # x if horizontal and y if vertical
206+ # TODO: when we drop compatibility with numpy, this can be more easily done
207+ # by just sampling ngaps out of the full wall set, i.e.
208+ # gaps = rng.sample(wall, k=ngaps)
209+ # for gap in gaps:
210+ # wall.remove(gap)
211+ ngaps = max (1 , ngaps )
212+ wall_pos = list (range (max_length ))
213+ rng .shuffle (wall_pos )
214+
215+ for gap in wall_pos [:ngaps ]:
216+ if vertical :
217+ wall .discard ((pos , ymin + gap ))
218+ else :
219+ wall .discard ((xmin + gap , pos ))
220+
221+ # collect this wall into the global wall set
222+ walls |= wall
223+
224+ # define the two new partitions of the maze generated by this wall
225+ # these are the parts of the maze to the left/right of a vertical wall
226+ # or the top/bottom of a horizontal wall
227+ ngaps = max (1 , ngaps // 2 )
204228
205- for partition in partitions :
206- walls |= add_wall_and_split (
207- partition , walls , max (1 , ngaps // 2 ), not vertical , rng = rng
208- )
229+ if vertical :
230+ new = [
231+ ((xmin , ymin ), (pos - 1 , ymax ), ngaps , not vertical ),
232+ ((pos + 1 , ymin ), (xmax , ymax ), ngaps , not vertical ),
233+ ]
234+ else :
235+ new = [
236+ ((xmin , ymin ), (xmax , pos - 1 ), ngaps , not vertical ),
237+ ((xmin , pos + 1 ), (xmax , ymax ), ngaps , not vertical ),
238+ ]
239+
240+ # queue the new partitions next;
241+ # ensures maze stability
242+ partitions .insert (p + 1 , new [1 ])
243+ partitions .insert (p + 1 , new [0 ])
244+
245+ # increase the partition index
246+ p += 1
209247
210248 return walls
211249
0 commit comments