这个问题是康威生命游戏的一部分。目前,这个程序需要大约70行Python代码才能返回游戏的功能,这可以简化为较少的代码行,并在键盘中断发生时结束。我最终想要消除键盘中断,并改进这段代码,也许尝试和除了功能,让游戏显示一条消息,说明它已经结束。有什么建议可以用一些额外的功能来清理这个代码吗?
就像任何游戏一样,规则是有的!下面是一个简单的概述。这个游戏的核心是四条规则,它们决定细胞是活的还是死的。这完全取决于那个细胞的邻居中有多少人还活着。
规则同时适用于所有单元格。
# Conway's Game of Life
import random
import time
import copy
WIDTH = 60
HEIGHT = 20
# Create a list of list for the cells:
nextCells = []
for x in range(WIDTH):
column = [] # Create a new column.
for y in range(HEIGHT):
if random.randint(0, 1) == 0:
column.append('#') # Add a living cell.
else:
column.append(' ') # Add a dead cell.
nextCells.append(column) # nextCells is a list of column lists.
while True: # Main program loop.
print('\n\n\n\n\n') # Separate each step with newlines.
currentCells = copy.deepcopy(nextCells)
# Print currentCells on the screen:
for y in range(HEIGHT):
for x in range(WIDTH):
print(currentCells[x][y], end='') # Print the # or space.
print() # Print a newline at the end of the row.
# Calculate the next step's cells based on current step's cells:
for x in range(WIDTH):
for y in range(HEIGHT):
# Get neighboring coordinates:
# `% WIDTH` ensures leftCoord is always between 0 and WIDTH - 1
leftCoord = (x - 1) % WIDTH
rightCoord = (x + 1) % WIDTH
aboveCoord = (y - 1) % HEIGHT
belowCoord = (y + 1) % HEIGHT
# Count number of living neighbors:
numNeighbors = 0
if currentCells[leftCoord][aboveCoord] == '#':
numNeighbors += 1 # Top-left neighbor is alive.
if currentCells[x][aboveCoord] == '#':
numNeighbors += 1 # Top neighbor is alive.
if currentCells[rightCoord][aboveCoord] == '#':
numNeighbors += 1 # Top-right neighbor is alive.
if currentCells[leftCoord][y] == '#':
numNeighbors += 1 # Left neighbor is alive.
if currentCells[rightCoord][y] == '#':
numNeighbors += 1 # Right neighbor is alive.
if currentCells[leftCoord][belowCoord] == '#':
numNeighbors += 1 # Bottom-left neighbor is alive.
if currentCells[x][belowCoord] == '#':
numNeighbors += 1 # Bottom neighbor is alive.
if currentCells[rightCoord][belowCoord] == '#':
numNeighbors += 1 # Bottom-right neighbor is alive.
# Set cell based on Conway's Game of Life rules:
if currentCells[x][y] == '#' and (numNeighbors == 2 or
numNeighbors == 3):
# Living cells with 2 or 3 neighbors stay alive:
nextCells[x][y] = '#'
elif currentCells[x][y] == ' ' and numNeighbors == 3:
# Dead cells with 3 neighbors become alive:
nextCells[x][y] = '#'
else:
# Everything else dies or stays dead:
nextCells[x][y] = ' '
time.sleep(1) # Add a 1-second pause to reduce flickering.发布于 2021-11-30 11:40:25
您的代码工作和可读性,这是很好的。它也大多遵循PEP8风格的约定,这很好,尽管这一点可以改进,因为在另一个答案中已经提到了它。
当然,总有改进的余地。
您的代码所缺少的是结构:一切都发生在顶层,而不使用函数或类。
这是不好的,因为它使代码更难阅读和遵循,更少的可维护性和可重用性。
将代码分解为函数可以让您独立地关注代码的每个方面。考虑以下伪码:
initialize()
while True:
display_cells()
update_state()
wait()在这种情况下,主循环非常简单,易于跟踪。现在,如果您想要研究如何显示单元格,您可以轻松地转到相关函数的定义上,并在此基础上进行工作。如果您想尝试另一种显示方式,例如使用图形显示而不是控制台上的字符,则可以定义另一个函数并替换主循环中的一行来尝试。
它还表明,它需要一种方法来跟踪单元格网的当前状态。
在代码中,使用一个名为nextCells的全局变量。全局变量被认为是错误的做法有很多很好的原因,主要是因为它使得很难跟踪它们所执行的代码中的什么地方,因此它的值应该在代码中的给定位置,特别是当您将代码分解成多个函数时。
修复该问题的一种方法是将变量作为参数传递给函数:
cells = initialize()
while True:
display_cells(cells)
cells = update_state(cells)
wait()您可以认为,cells在技术上仍然是一个全局变量,因为它是在代码的根级定义的。处理这个问题的一种方法是将游戏状态封装到一个类中。该类应保持游戏状态,提供对其采取行动的方式,并访问有关它的信息。
实现类时,代码现在如下所示:
class GameOfLife:
def __init__(self):
pass
def update_state(self):
pass
def print_cells(self)
pass
def run(self)
pass
game = GameOfLife()
game.run()现在只剩下几件事要解决了。
首先是文档:虽然代码很简单,而且大多是自文档化,但如果有必要,最好使用docstring和注释来记录代码。
Docstring应该描述类和函数的用途,以及如何使用它们。如果类/函数在其他地方使用,则可以使用Python的help()内置函数访问它们,并允许用户阅读如何使用您的工作而无需阅读实际代码。
如果还不清楚,评论应该提供为什么事情是以某种方式进行的。如果代码是使用描述性名称和逻辑单元编写的,则不需要注释。
最后是可重用性。既然生命游戏已经封装到一个方便的对象中,那么从另一个脚本中import并使用它是非常有用的。然而,实际上,最后2行将执行,游戏的一个实例将运行,在这种情况下这是不可取的。
为了允许imports并运行脚本,您可以将代码放入“主保护程序”中。
我对这个问题的看法是:
import random
from time import sleep
class Life:
'''
An implementation of Conway's game of life on a finite and wrapping grid
'''
LIVE_CELL = '#'
DEAD_CELL = ' '
DELAY = 1
def __init__(self, width=60, height=20):
self.width = width
self.height = height
self.cells = [[random.choice([True, False]) for _ in range(self.width)] for _ in range(self.height)]
def next_generation(self):
'''
Updates the cell grid according to Conway's rules
'''
next_cells = [[False for _ in range(self.width)] for _ in range(self.height)]
for j in range(self.width):
for i in range(self.height):
neighbor_count = self._count_live_neighbors(i, j)
if ((self.cells[i][j] and neighbor_count in (2, 3))
or (not self.cells[i][j] and neighbor_count == 3)):
next_cells[i][j] = True
self.cells = next_cells
def _count_live_neighbors(self, i, j):
'''
Counts the number of live neighbor of a cell, given the cell indices on the grid
'''
count = 0
for di, dj in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
if self.cells[(i + di) % self.height][(j + dj) % self.width]:
count += 1
return count
def print_cells(self):
'''
Print the current state of the cell grid on the console
'''
print('\n\n\n\n\n')
for row in self.cells:
chars = [self.LIVE_CELL if c else self.DEAD_CELL for c in row]
print(''.join(chars))
def run(self):
'''
Run the game of life until a keyboard interrupt is raise (by pressing ctrl+c)
'''
try:
while(True):
self.print_cells()
self.next_generation()
sleep(self.DELAY)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
life = Life()
life.run()正如你所看到的,我也改变了一些逻辑,所以我将解释我的推理。
的布尔值数组
True代表活细胞,False代表死细胞。这是为了提高可重用性,让显示逻辑决定如何显示死活的单元格。它还简化了一些逻辑。
较少的复制和粘贴代码使其更容易出错和更易于维护,但在这种情况下仍然很容易理解。
Python一行有时很难读,但是列表理解来初始化列表是非常有用的,而且比迭代附加值更有效。
在这种简单的情况下,它仍然是相当可读的。
如果您对进一步的练习感兴趣,我可以考虑对代码进行一些改进:
发布于 2021-11-28 17:01:41
请修复您的代码缩进,因为它现在是痛苦的,它是痛苦的,应该发生什么。这些评论提供了一些建议,以使你也容易。
活细胞和死细胞的特征应该是模块级常量。使它们易于更改,代码更易读:
LIVING_CELL = '#'
DEAD_CELL = ' '
CELLS = (LIVING_CELL, DEAD_CELL)variable_namesPEP8声明变量和函数名应该跟在snake_case后面。
nextCells -> next_cells
清单理解常常比手动列表理解更好。考虑一下改进后的next_cells创建:
import random
next_cells = []
for _ in range(WIDTH):
column = [random.choice(CELLS) for _ in range(HEIGHT)]
next_cells.append(column)您甚至可以更进一步,进行嵌套理解:
next_cells = [[random.choice(CELLS) for _ in range(HEIGHT)] for _ in range(WIDTH)]我想说,这一行仍然是相当可读的,可能甚至比第一个建议,因此我推荐它。
random.choice是一个更好的选择,可以在这个片段中表达您的意图。
命名迭代变量_是一种表明变量值从未被实际使用过的惯例。
你在数邻居的时候做了太多的手工检查。不应手动构造所有可能的坐标组合。相反,考虑一下底层逻辑,以及如何将其转换为更简洁的代码。我确信也有许多资源(可能仅在这个站点上)用于正确地检查/计数给定坐标的邻居。
请注意,原来的游戏规则是为无限大小的板而设计的,据我所知,对于有限大小的板没有“官方”规则。
print('\n\n\n\n\n')更好地表示为print('\n' * 5)。
https://codereview.stackexchange.com/questions/270461
复制相似问题