Несмотря на то, что вы не проявили особого интереса к просмотру этой версии, я все равно опубликую ее, потому что думаю, что она может быть интересна другим, если не вам, - и потому что я положил немало время игры с , работающей над этим, чтобы улучшить и оптимизировать его.
Он отслеживает прямоугольники Canvas
, созданные путем добавления атрибута id
к экземплярам класса Cell
, которые сохраняют идентификатор объекта Canvas, возвращенный из метода create_rectangle()
, вместо отдельного canvas_rect_list
. Это позволяет избежать повторного создания их при каждой итерации.
Кроме того, метод iterate()
больше не создает deepcopy
с каждого из существующих Cell
в процессе создания их нового списка. Вместо этого он просто обновляет существующие экземпляры. Это работает потому, что реализованы правила, и они ссылаются только на текущие значения в отдельном массиве grid
, который на данный момент еще не обновлен.
Говоря об этом, я также сделал использование библиотеки numpy
необязательным и использовал словарь на месте. Все строки, затронутые этим изменением, начинаются с #xnp
. Это делает его работоспособным без установки каких-либо дополнительных модулей, но код для использования numpy
все еще там, если вы действительно хотите использовать его по какой-то причине. (Лично я не думаю, что есть много преимуществ в его использовании).
Результатом всего этого является то, что работает очень быстро, и код теперь очень модульный. Я экспериментально уменьшил задержку между итерациями до 16 мсек, и она действительно летела ...
# Conway's Game of life
from random import randint
from enum import IntEnum
#xnp import numpy as np
import tkinter as tk
DELAY = 100 # Msecs between iterations.
class State(IntEnum): Dead = False; Alive = True
class Cell:
__slots__ = "m", "n", "state", "id"
def __init__(self, m, n, state):
#xnp self.m, self.n, self.state = np.uint(m), np.uint(n), state
self.m, self.n, self.state = m, n, state
self.id = None
def get_posn(self):
return self.m, self.n # (Must) return a tuple.
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 CanvasFrame(tk.Frame):
COLORMAP = {State.Dead: 'red', State.Alive: 'blue'}
def __init__(self, parent, m, n, cell_size, cells):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, bg="gray", height=m*cell_size, width=n*cell_size)
self.canvas.pack()
COLORMAP = self.COLORMAP
create_rectangle = self.canvas.create_rectangle
for cell in cells:
cell.id = create_rectangle(cell.n * cell_size, cell.m * cell_size,
(cell.n+1) * cell_size, (cell.m+1) * cell_size,
fill=COLORMAP[cell.state])
def update_canvas(self, cells):
COLORMAP = self.COLORMAP
itemconfig = self.canvas.itemconfig
for cell in cells:
itemconfig(cell.id, fill=COLORMAP[cell.state])
class Game:
def __init__(self, m, n, seed=None):
self.m, self.n = m, n
self.cell_size = 10000 // 400 # (self.m * self.n)
#xnp self.grid = np.ndarray((m, n), dtype=np.uint8)
self.grid = {} # Use a dictionary instead of ndarray.
states = tuple(s for s in State) # Local var for faster access.
statefunc = ((lambda q, r: states[(q, r) in seed]) if seed else
(lambda q, r: states[randint(0, 1)]))
self.cells = [Cell(q, r, statefunc(q, r))
for q, r in (divmod(i, n) for i in range(m*n))]
self.populate_grid()
def populate_grid(self):
#xnp for cell in self.cells:
#xnp self.grid[cell.m, cell.n] = cell.state.value
self.grid = {cell.get_posn(): cell.state.value for cell in self.cells}
def show(self):
self.root = tk.Tk()
self.container = tk.Frame(self.root)
self.container.pack(side="top", fill="both", expand=True)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.canvasframe = CanvasFrame(self.container, self.m, self.n, self.cell_size, self.cells)
self.canvasframe.grid(row=0, column=0, sticky="nsew") # Each in same location.
self.container.after(500, self.tick)
self.root.mainloop()
def tick(self):
""" Transition to next generation. """
self.iterate() # Update cells and grid.
self.canvasframe.update_canvas(self.cells)
self.container.after(DELAY, self.tick)
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
"""
deltas = ((-1, -1), (-1, 0), (-1, 1),
( 0, -1), ( 0, 1),
( 1, -1), ( 1, 0), ( 1, 1))
for cell in self.cells:
alive_neighbours = 0
m, n = cell.m, cell.n
for i, j in ((m+dm, n+dn) for dm, dn in deltas):
try:
alive_neighbours += self.grid[i, j]
# Exception depends on whether np is being used or not.
except (KeyError, IndexError):
pass
if alive_neighbours < 2 or alive_neighbours > 3: # Death?
cell.state = State.Dead
elif alive_neighbours == 3: # Birth?
cell.state = State.Alive
self.populate_grid() # Update.
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)))
random_pattern = (30, 30, None)
# @Mike - SMT version that runs longer.
small_exploder = (32, 30, ((12,10), (11,9), (11,10), (11,11), (12,9), (12,11), (14,10)))
# g = Game(*small_exploder)
g = Game(*random_pattern)
g.show()
Вот оно работает со случайной начальной точки: