首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于教学Python 3的迷宫生成器

用于教学Python 3的迷宫生成器
EN

Code Review用户
提问于 2019-04-16 11:28:04
回答 1查看 250关注 0票数 9

我正在教授编程(在这种情况下,1对1辅导对编程感兴趣的青少年),这段代码将是进展程序生成迷宫的最后阶段。

欢迎对此代码进行改进的任何评论!但是,不明确的代码违反标准实践的问题尤其受欢迎,性能问题在这里不太重要。

请注意,在我教初学者的时候,我更喜欢避免更复杂或更特定于Python的构造,例如if __name__ == '__main__'、生成器、迭代工具,并专注于更通用的构造--程序的结构、调试策略、循环或类。

代码语言:javascript
复制
"""
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()
```
代码语言:javascript
复制
EN

回答 1

Code Review用户

发布于 2019-04-16 16:39:41

我看不出有什么大问题。只是吹毛求疵:

offsets_to_surrounding_tiles可以使用列表理解/生成器表达式编写,这样就不需要硬编码了:

代码语言:javascript
复制
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参数来修改此函数以生成更大邻域的偏移:

代码语言:javascript
复制
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,而不是仅仅使用逻辑运算符。例如:

代码语言:javascript
复制
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和比较链接,这样会更干净:

代码语言:javascript
复制
def inside_image(self, x, y):
    return 0 <= x < self.WIDTH
           and 0 <= y < self.HEIGHT

我认为用这个版本来看一下逻辑要容易得多。可以说,这个函数也可以通过通过宽度和高度来简化测试。现在,您需要实例化一个Maze来测试这个函数,这比它需要的更痛苦。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/217549

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档