这个游戏在大约 10 次迭代后变慢了。我很确定这是由于垃圾对象(可能是我在每次迭代中创建的矩形)位于那里并占用空间,即使它们不需要。如何识别不需要的对象并将其删除?
# Game of life
from random import randint
import numpy as np
from copy import deepcopy
from enum import Enum
import tkinter as tk
class State(Enum):
Dead = 0
Alive = 1
def __str__(self):
return str(self.value)
class Cell:
def __init__(self, m, n, state):
self.m = np.uint(m)
self.n = np.uint(n)
self.state = state
def kill(self):
self.state = State.Dead
def birth(self):
self.state = State.Alive
def __str__(self):
return '({},{}) {}'.format(self.m, self.n, self.state)
def __repr__(self):
return '({},{}) {}'.format(self.m, self.n, self.state)
class Game:
def __init__(self, m, n, alive_cells = None):
self.m = m
self.n = n
self.grid = np.ndarray((m,n), dtype = np.uint8)
if alive_cells:
self.cells = [Cell(i // n,i % n, State.Alive if (i // n,i % n) in alive_cells else State.Dead) for i in range(m*n)]
else:
self.cells = [Cell(i / n,i % n,randint(0,1)) for i in range(m*n)]
# GUI #
self.top = tk.Tk()
self.cell_size = 10000 // 400 #(self.m * self.n)
self.canvas = tk.Canvas(self.top, bg="gray", height=self.m *self. cell_size, width=self.n * self.cell_size)
def populate_grid(self):
for cell in self.cells:
self.grid[cell.m,cell.n] = cell.state.value
def show(self, show_GUI = True, print_2_console = False):
self.populate_grid()
if print_2_console:
print('#'*self.m*3)
print(self.grid)
if show_GUI:
self.draw_canvas()
def iterate(self):
'''
Rules:
(1) If cell has less than 2 neighbours, it dies
(2) If cell has more than 3 neighbours, it dies
(3) If cell has 2-3 neighbours, it survives
(4) If cell has 3 neighbours, it rebirths
'''
new_cells = []
for cell in self.cells:
alive_neighbours = 0
for i in range(cell.m - 1, cell.m + 2):
for j in range(cell.n - 1, cell.n + 2):
if i == cell.m and j == cell.n:
continue
else:
try:
alive_neighbours += self.grid[i,j]
except IndexError:
pass
tmp = deepcopy(cell)
if alive_neighbours < 2 or alive_neighbours > 3:
tmp.kill()
elif alive_neighbours == 3:
tmp.birth()
else: # == 2
pass
new_cells.append(tmp)
self.cells = new_cells
self.show()
def draw_canvas(self):
for cell in self.cells:
if cell.state == State.Alive:
color = 'blue'
else:
color = 'red'
self.canvas.create_rectangle(cell.n*self.cell_size, cell.m*self.cell_size, (1+cell.n)*self.cell_size, (1+cell.m)*self.cell_size, fill=color)
self.canvas.pack()
self.update_canvas()
self.top.mainloop()
def update_canvas(self):
for cell in self.cells:
if cell.state == State.Alive:
color = 'blue'
else:
color = 'red'
self.canvas.create_rectangle(cell.n*self.cell_size, cell.m*self.cell_size, (1+cell.n)*self.cell_size, (1+cell.m)*self.cell_size, fill=color)
# call again after 100 ms
self.top.after(100, self.iterate)
if __name__ == "__main__":
glider = (20, 20, ((1,3), (2,3), (2,1), (3,2), (3,3)))
small_exploder = (30, 30, ((10,10), (11,9), (11,10), (11,11), (12,9), (12,11), (13,10)))
M, N, STARTING_LIVE_CELLS, ITERATIONS = *small_exploder, 0
g = Game(M, N, STARTING_LIVE_CELLS)
g.show()
请您参考如下方法:
我终于弄明白了,您的脚本可以正常运行而不会崩溃。解决垃圾对象问题后我遇到的递归问题是因为 mainloop() 被一遍又一遍地调用。它只应在 tkinter 脚本中调用一次。
此版本通过使用 相同 标记值“标记”它们来避免不断创建越来越多的 tkinter 矩形,这允许使用单个 Canvas 将它们作为一个组删除.delete() 方法调用。
每次迭代仅修改现有矩形会更有效,但此版本每次迭代仅删除并重新创建它们。要更新它们,需要通过某种方式在创建后单独引用它们。如果我有动力并且您有兴趣,我可能会稍后更新此答案。
我做了很多更改,但您的核心代码仍然存在。
# Game of life
from random import randint
import numpy as np
from copy import deepcopy
from enum import Enum
import tkinter as tk
class State(Enum):
Dead = 0
Alive = 1
def __str__(self):
return str(self.value)
@property
def color(self):
return 'blue' if self.value else 'red'
class Cell:
def __init__(self, m, n, state):
self.m = np.uint(m)
self.n = np.uint(n)
self.state = state
def kill(self):
self.state = State.Dead
def birth(self):
self.state = State.Alive
def __str__(self):
return '({},{}) {}'.format(self.m, self.n, self.state)
def __repr__(self):
return '({},{}) {}'.format(self.m, self.n, self.state)
class Game:
CELL_TAG = 'cells'
def __init__(self, m, n, alive_cells = None):
self.m = m
self.n = n
self.grid = np.ndarray((m,n), dtype = np.uint8)
if alive_cells:
self.cells = [Cell(i // n, i % n,
State.Alive if (i // n, i % n) in alive_cells else State.Dead)
for i in range(m*n)]
else:
self.cells = [Cell(i / n, i % n, randint(0,1)) for i in range(m*n)]
# GUI #
self.top = tk.Tk()
self.cell_size = 10000 // 400 # (self.m * self.n)
self.canvas = tk.Canvas(self.top, bg="gray", height=self.m * self.cell_size,
width=self.n * self.cell_size)
self.canvas.pack()
def populate_grid(self):
for cell in self.cells:
self.grid[cell.m, cell.n] = cell.state.value
def show(self, show_GUI = True, print_2_console = False):
self.populate_grid()
if print_2_console:
print('#'*self.m*3)
print(self.grid)
if show_GUI:
self.draw_canvas()
self.top.mainloop()
def iterate(self):
'''
Rules:
(1) If cell has less than 2 neighbours, it dies
(2) If cell has more than 3 neighbours, it dies
(3) If cell has 2-3 neighbours, it survives
(4) If cell has 3 neighbours, it rebirths
'''
new_cells = []
for cell in self.cells:
alive_neighbours = 0
for i in range(cell.m - 1, cell.m + 2):
for j in range(cell.n - 1, cell.n + 2):
if i == cell.m and j == cell.n:
continue
else:
try:
alive_neighbours += self.grid[i,j]
except IndexError:
pass
tmp = deepcopy(cell)
if alive_neighbours < 2 or alive_neighbours > 3:
tmp.kill()
elif alive_neighbours == 3:
tmp.birth()
else: # == 2
pass
new_cells.append(tmp)
self.cells = new_cells
self.populate_grid()
self.draw_canvas()
def draw_canvas(self):
self.canvas.delete(self.CELL_TAG) # Gets rid of any existing cell rects.
for cell in self.cells:
self.canvas.create_rectangle(
cell.n*self.cell_size, cell.m*self.cell_size,
(1+cell.n)*self.cell_size, (1+cell.m)*self.cell_size,
fill=cell.state.color, tag=self.CELL_TAG)
self.top.after(100, self.iterate)
if __name__ == "__main__":
glider = (20, 20, ((1,3), (2,3), (2,1), (3,2), (3,3)))
small_exploder = (30, 30, ((10,10), (11,9), (11,10), (11,11), (12,9), (12,11), (13,10)))
M, N, STARTING_LIVE_CELLS, ITERATIONS = *small_exploder, 0
g = Game(M, N, STARTING_LIVE_CELLS)
g.show()
