我正在教授编程(在这种情况下,1对1辅导对编程感兴趣的青少年),这段代码将是进展程序生成迷宫的最后阶段。
欢迎对此代码进行改进的任何评论!但是,不明确的代码违反标准实践的问题尤其受欢迎,性能问题在这里不太重要。
请注意,在我教初学者的时候,我更喜欢避免更复杂或更特定于Python的构造,例如if __name__ == '__main__'、生成器、迭代工具,并专注于更通用的构造--程序的结构、调试策略、循环或类。
"""
maze generator
"""
import random
from PIL import Image
def main():
WIDTH = 100
HEIGHT = 100
TILE_SIZE_PX = 4
WHITE = (255, 255, 255)
PASSAGE_COLOR = WHITE
BLACK = (0, 0, 0)
WALL_COLOR = BLACK
maze = Maze(width=WIDTH, height=HEIGHT)
maze.output_maze("maze.png", passage_color=PASSAGE_COLOR, wall_color=WALL_COLOR, tile_size_in_pixels=TILE_SIZE_PX)
maze = MazeWithWideCorridors(width=WIDTH, height=HEIGHT)
maze.output_maze("maze_alternative.png", passage_color=PASSAGE_COLOR, wall_color=WALL_COLOR, tile_size_in_pixels=TILE_SIZE_PX)
class Maze:
"""
generates maze using DFS based algorithm
"""
def __init__(self, width, height):
self.WIDTH = width
self.HEIGHT = height
self.PASSAGE_COLOR = (255, 255, 255)
self.WALL_COLOR = (0, 0, 0)
self.image = Image.new("RGB", (self.WIDTH, self.HEIGHT), self.WALL_COLOR)
self.pixels = self.image.load()
self.generate()
def generate(self):
"""
expands maze starting from (0, 0) as a seed location,
as long as eligible places to carve new tunnels exist
"""
candidates_list = []
candidates_list.append((0, 0))
while len(candidates_list) > 0:
processed = candidates_list.pop()
x = processed[0]
y = processed[1]
self.pixels[x, y] = self.PASSAGE_COLOR
new_candidates = self.children(x, y)
if len(new_candidates) > 0:
candidates_list.append(processed)
candidates_list.append(random.choice(new_candidates))
def output_maze(self, image_output_filepath, tile_size_in_pixels=1, passage_color=(255, 255, 255), wall_color=(0, 0, 0)):
"""
shows maze image at the screen and
outputs maze to specified location in image_output_filepath
using file format implied by extensions
"""
output = Image.new("RGB", (self.WIDTH, self.HEIGHT))
output_pixels = output.load()
for x in range(self.WIDTH):
for y in range(self.HEIGHT):
if self.pixels[x, y] == self.PASSAGE_COLOR:
output_pixels[x, y] = passage_color
else:
output_pixels[x, y] = wall_color
output = output.resize((self.WIDTH*tile_size_in_pixels, self.HEIGHT*tile_size_in_pixels))
output.show()
output.save(image_output_filepath)
def children(self, parent_x, parent_y):
"""
returns list of all currently eligible locations to expand from (parent_x, parent_y)
list contains tuples of integers
"""
up = (parent_x, parent_y - 1)
left = (parent_x - 1, parent_y)
right = (parent_x + 1, parent_y)
down = (parent_x, parent_y + 1)
returned = []
if self.is_safe_to_tunnel(parent_x, parent_y, up[0], up[1]):
returned.append(up)
if self.is_safe_to_tunnel(parent_x, parent_y, left[0], left[1]):
returned.append(left)
if self.is_safe_to_tunnel(parent_x, parent_y, down[0], down[1]):
returned.append(down)
if self.is_safe_to_tunnel(parent_x, parent_y, right[0], right[1]):
returned.append(right)
return returned
def is_safe_to_tunnel(self, parent_x, parent_y, x, y):
"""
returns true if location (x, y) can be turned into a passage
false otherwise
protects agains going outside image or making
loop or passage wider than 1 tile
returns false if (x, y) is not inside the image
returns false if (x, y) is already a passage
returns false if there are passages around (x, y) that are
not on (parent_x, parent_y) location or around it
returns true if location (x, y) can be turned into a passage
"""
if not self.inside_image(x, y):
return False
if self.pixels[x, y] == self.PASSAGE_COLOR:
return False
if self.is_colliding_with_other_tunnels(parent_x, parent_y, x, y):
return False
return True
def is_colliding_with_other_tunnels(self, parent_x, parent_y, x, y):
"""
checks whatever tunnel at this legal location can
be placed without colliding with other tunnels
"""
for offset in self.offsets_to_surrounding_tiles():
if self.is_populated(x + offset[0], y + offset[1]):
x_distance_to_parent = x + offset[0] - parent_x
y_distance_to_parent = y + offset[1] - parent_y
if abs(x_distance_to_parent) + abs(y_distance_to_parent) > 1:
return True
return False
def offsets_to_surrounding_tiles(self):
"""
returns list of 2-tuples with distances to
each of 8 neighbouring tiles
"""
return [(1, 0), (1, -1), (0, -1), (-1, -1),
(-1, 0), (-1, 1), (0, 1), (1, 1)]
def is_populated(self, x, y):
"""returns true if this locations contains passage, false if wall or is outside image"""
if not self.inside_image(x, y):
return False
if self.pixels[x, y] == self.PASSAGE_COLOR:
return True
return False
def inside_image(self, x, y):
"""
returns true if (x, y) is inside image,
return false otherwise
"""
if x < 0:
return False
if y < 0:
return False
if x >= self.WIDTH:
return False
if y >= self.HEIGHT:
return False
return True
class MazeWithWideCorridors(Maze):
def is_colliding_with_other_tunnels(self, parent_x, parent_y, x, y):
"""
checks whatever tunnel at this legal location can
be placed without colliding with other tunnels
"""
for offset in self.offsets_to_surrounding_tiles():
if self.is_populated(x + offset[0], y + offset[1]):
x_distance_to_parent = x + offset[0] - parent_x
y_distance_to_parent = y + offset[1] - parent_y
if abs(x_distance_to_parent) > 1 or abs(y_distance_to_parent) > 1:
return True
return False
main()
```发布于 2019-04-16 16:39:41
我看不出有什么大问题。只是吹毛求疵:
offsets_to_surrounding_tiles可以使用列表理解/生成器表达式编写,这样就不需要硬编码了:
def offsets_to_surrounding_tiles2():
return [(x, y)
for y in range(-1, 2)
for x in range(-1, 2)
if (x, y) != (0, 0)] # So we don't include the centre
>>> offsets_to_surrounding_tiles()
[(1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)]这样做的好处是,如果您决定扩展到当前使用的Moore邻域之外,您可以通过包含一个depth参数来修改此函数以生成更大邻域的偏移:
def offsets_to_surrounding_tiles2(depth):
return [(x, y)
for y in range(-depth, depth + 1)
for x in range(-depth, depth + 1)
if (x, y) != (0, 0)]
>>> offsets_to_surrounding_tiles2(1)
[(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)]
>>> offsets_to_surrounding_tiles2(2)
[(-2, -2), (-1, -2), (0, -2), (1, -2), (2, -2), (-2, -1), (-1, -1), (0, -1), (1, -1), (2, -1), (-2, 0), (-1, 0), (1, 0), (2, 0), (-2, 1), (-1, 1), (0, 1), (1, 1), (2, 1), (-2, 2), (-1, 2), (0, 2), (1, 2), (2, 2)]这可能违反雅格尼,但它可能是一个很好的例子,什么是可以做的理解。如果您认为不一定需要使用整个列表,也可以将此函数编写为生成器表达式。
将此函数作为类的实例方法似乎也不合适,因为它与任何特定实例没有任何关系( self被忽略的事实证明了这一点)。这作为静态/类方法更好,或者(imo)更好的是,作为一个与类无关的松散函数。
您有几个函数使用多个returns,而不是仅仅使用逻辑运算符。例如:
def inside_image(self, x, y):
if x < 0:
return False
if y < 0:
return False
if x >= self.WIDTH:
return False
if y >= self.HEIGHT:
return False
return True我认为,通过使用and和比较链接,这样会更干净:
def inside_image(self, x, y):
return 0 <= x < self.WIDTH
and 0 <= y < self.HEIGHT我认为用这个版本来看一下逻辑要容易得多。可以说,这个函数也可以通过通过宽度和高度来简化测试。现在,您需要实例化一个Maze来测试这个函数,这比它需要的更痛苦。
https://codereview.stackexchange.com/questions/217549
复制相似问题