首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >生命的游戏,一个更短的故事

生命的游戏,一个更短的故事
EN

Code Review用户
提问于 2016-10-14 23:15:51
回答 2查看 277关注 0票数 5

我已经看了很长一段时间的生活游戏,但不愿意使用图形化的软件包,如游戏。但今天我终于改过自新了。

你知道在计算上“问题”只取决于一个条件吗?我没有,一开始我就跟踪比赛的情况。

一开始,我使用list来包含坐标,如果活着的砖块或胸罩,我称之为它们。但我很快意识到,秩序并不重要,于是我放弃了lists。Python,list,S,是这么慢吗?

同龄人的阅读让我更聪明,所以你可以自由地给我指点。

代码语言:javascript
复制
class GameOfLife:
    def __init__(self, size=800):
        """
        The implementation is based on sets and dict, because order never
         matters in this game. Sets and dicts are faster. There is no need
         for complex data-structures like list of lists, order don't matter.


        :param self.screen: the screen used by pygame.
        :param self.white: color for pygame. alive plebs
        :param self.black: color for pygame. dead plebs or "empty" area

        :param self.width: screen width
        :param self.size: size of a pleb.
        :param self.alive: alive plebs.
        :param self.last_config: if self.alive, has not changed, it will not
         change. So this is an end condition.
        """
        self.screen = pygame.display.set_mode((size, size))
        self.white = (255, 255, 255)
        self.black = (0, 0, 0)

        self.width = size
        self.size = size//100
        self.brick = [self.size, self.size]
        self.alive = set()
        self.last_config = set()

    def show(self):
        pygame.Surface.fill(self.screen, self.black)
        for pos in self.alive:
            x, y = pos
            pos = x*self.size, y*self.size
            pygame.draw.rect(self.screen, self.white, list(pos)+self.brick, 0)
        pygame.display.flip()

    def setup(self):
        """
        Initialize the game, i.e. sets initial alive plebs.
        """

        alive_at_start = Configurations.glider_gun_mirroed.union(
            Configurations.glider_gun)

        self.alive = {(x, y) for x, y in alive_at_start}

    def get_connected_plebs(self, part: tuple) -> list:
        """
        Relative to a grid brick, there is only eight possible other connecting
         bricks. patterns defines them and if the brick is in the grid, it is
         returned
        """
        x, y = part
        patterns = [
            [-1, 0],
            [0, -1],
            [0, 1],
            [1, 0],
            [-1, 1],
            [1, -1],
            [1, 1],
            [-1, -1]
        ]
        return {(x+i, y+j) for i, j in patterns if
                all(k <= self.width//self.size for k in (x+i, y+j))}

    def generation(self):
        """
        For each generation there is only one condition we have to check, i.e,
        if a alive brick will survive. Everything computational else depends on
        this condition.
        """

        next_generation = set()
        cache = Counter()

        for pleb in self.alive:
            neighbors = self.get_connected_plebs(pleb)
            cache.update([n for n in neighbors])

            alive_neighbors = [x for x in neighbors if x in self.alive]
            if 1 < len(alive_neighbors) < 4:
                next_generation.add(pleb)

        for key, value in cache.items():
            if value == 3:
                next_generation.add(key)

        self.alive = next_generation

    def generate(self):
        """ The main loop of this game. """

        self.setup()
        while self.last_config != self.alive:
            self.last_config = self.alive
            self.show()
            self.generation()
            sleep(0.1)
        sleep(4)


def main():
    m = GameOfLife()
    m.generate()


if __name__ == '__main__':
    main()

当硬编码这个游戏的配置时,许多行和可笑的大量时间被浪费掉了。

代码语言:javascript
复制
class Configurations:
    """ Starting configurations of the game. Don't examine this. """
    pentadecathlon = {
        (20, 19), (19, 19), (18, 19), (20, 20), (19, 20), (18, 20), (19, 21),
        (19, 22), (19, 23), (18, 24), (20, 24), (18, 27), (20, 27), (19, 28),
        (19, 29), (19, 30), (18, 31), (19, 31), (20, 31), (18, 32), (19, 32),
        (20, 32)
    }
    glider_gun = {
        (10, 32), (11, 30), (11, 32), (12, 20), (12, 21), (12, 28), (12, 29),
        (12, 42), (12, 43), (13, 19), (13, 23), (13, 28), (13, 29), (13, 42),
        (13, 43), (14, 8), (14, 9), (14, 18), (14, 24), (14, 28), (14, 29),
        (15, 8), (15, 9), (15, 18), (15, 22), (15, 24), (15, 25), (15, 30),
        (15, 32), (16, 18), (16, 24), (16, 32), (17, 19), (17, 23), (18, 20),
        (18, 21)
      }
    glider_gun_mirroed = {
        (72, 94), (72, 93), (74, 93), (84, 92), (83, 92), (76, 92), (75, 92),
        (62, 92), (61, 92), (85, 91), (81, 91), (76, 91), (75, 91), (62, 91),
        (61, 91), (96, 90), (95, 90), (86, 90), (80, 90), (76, 90), (75, 90),
        (96, 89), (95, 89), (86, 89), (82, 89), (80, 89), (79, 89), (74, 89),
        (72, 89), (86, 88), (80, 88), (72, 88), (85, 87), (81, 87), (84, 86),
        (83, 86)
    }
EN

回答 2

Code Review用户

回答已采纳

发布于 2016-10-14 23:44:28

硬编码配置看起来非常痛苦。在这种情况下,一种很好的技术是想出一种由人编写配置的方便方法,以及一些帮助函数将人类可读的配置转换为程序所需的数据结构。就像这样:

代码语言:javascript
复制
glider = (
    "###",
    "  #",
    " # ",
)

然后,用一个帮助函数来解析这个函数,它使用元组行以及x和y偏移量,它应该在画布上开始绘图。

代码语言:javascript
复制
def parse_config(x, y, lines):
    # ...

这个函数应该创建您在不小的痛苦中所做的配置。

这一观点的另一个变体是:

代码语言:javascript
复制
import textwrap

def normalized_ascii(text):
    return textwrap.dedent(text).strip()

glider_raw = """
    ###
      #
     #
    """

glider_ascii = normalized_ascii(glider_raw)

def parse_config(x, y, ascii):
    pass

不同之处在于,原始配置不是一个列表,而是一个多行字符串,输入起来可能稍微容易一些。多余的压痕用textwrap.dedent很容易消除,多余的空行用strip很容易消除。需要重写parse_config助手来解释这种格式。实际上,它可以将ascii输入拆分成行字符,然后它的格式与第一个示例相同。

确保正确地对辅助函数(parse_config)进行单元测试。然后,您可以轻松地开始使用更加友好的配置语法。

请注意,不值得担心配置解析器添加的额外计算。在程序的主要操作旁边,开销通常是微不足道的。以牺牲额外的解析步骤为代价,使配置易于编写和读取总是值得的,并且可以节省您的时间。这要归功于消除了错误配置带来的挫折感,当格式不容易编写和读取时,这是一个很高的风险。

实现应该以域的形式编写。self.whiteself.black不是域的术语,而是实现细节。如果将它们分别重命名为self.empty_colorself.alive_color,则实现将更自然地读取。

在每次对patterns的调用中都会重新创建get_connected_plebs列表。这是不必要的,因为这个列表是一个常量。最好让它成为类的静态属性。

而且,patterns不是一个很好的名字。这些是坐标偏移。所以我叫他们offsets。当迭代它时,我称它们为ij,而不是循环变量,它们通常用于计数循环,我称之为dxdy

cache.update([n for n in neighbors])不同,您可以使用cache.update(neighbors[:])更容易地复制列表。但你真的需要一份吗?简单的cache.update(neighbors)还不够吗?

票数 7
EN

Code Review用户

发布于 2016-10-15 10:33:04

您对setup的使用并不是很友好。最好允许用户指定配置本身,并因此从generate中删除其调用。我会把它定义为:

代码语言:javascript
复制
def setup(self, alive_at_start):
    """ Initialize the game, i.e. sets initial alive plebs. """
    self.alive = set(alive_at_start)

在调用generate之前,您还需要使用所需的配置调用它一次。

您还碰巧有一个错误的get_connected_plebs文档,因为您说它返回一个list,但是返回一个set

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

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

复制
相关文章

相似问题

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