我已经看了很长一段时间的生活游戏,但不愿意使用图形化的软件包,如游戏。但今天我终于改过自新了。
你知道在计算上“问题”只取决于一个条件吗?我没有,一开始我就跟踪比赛的情况。
一开始,我使用list来包含坐标,如果活着的砖块或胸罩,我称之为它们。但我很快意识到,秩序并不重要,于是我放弃了lists。Python,list,S,是这么慢吗?
同龄人的阅读让我更聪明,所以你可以自由地给我指点。
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()当硬编码这个游戏的配置时,许多行和可笑的大量时间被浪费掉了。
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)
}发布于 2016-10-14 23:44:28
硬编码配置看起来非常痛苦。在这种情况下,一种很好的技术是想出一种由人编写配置的方便方法,以及一些帮助函数将人类可读的配置转换为程序所需的数据结构。就像这样:
glider = (
"###",
" #",
" # ",
)然后,用一个帮助函数来解析这个函数,它使用元组行以及x和y偏移量,它应该在画布上开始绘图。
def parse_config(x, y, lines):
# ...这个函数应该创建您在不小的痛苦中所做的配置。
这一观点的另一个变体是:
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.white和self.black不是域的术语,而是实现细节。如果将它们分别重命名为self.empty_color和self.alive_color,则实现将更自然地读取。
在每次对patterns的调用中都会重新创建get_connected_plebs列表。这是不必要的,因为这个列表是一个常量。最好让它成为类的静态属性。
而且,patterns不是一个很好的名字。这些是坐标偏移。所以我叫他们offsets。当迭代它时,我称它们为i和j,而不是循环变量,它们通常用于计数循环,我称之为dx和dy。
与cache.update([n for n in neighbors])不同,您可以使用cache.update(neighbors[:])更容易地复制列表。但你真的需要一份吗?简单的cache.update(neighbors)还不够吗?
发布于 2016-10-15 10:33:04
您对setup的使用并不是很友好。最好允许用户指定配置本身,并因此从generate中删除其调用。我会把它定义为:
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。
https://codereview.stackexchange.com/questions/144259
复制相似问题