代码之家  ›  专栏  ›  技术社区  ›  CIsForCookies

画布更新速度减慢(可能需要释放一些对象)

  •  1
  • CIsForCookies  · 技术社区  · 7 年前

    # 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()
    
    3 回复  |  直到 7 年前
        1
  •  2
  •   martineau    7 年前

    我终于把事情弄清楚了,你的脚本也能正常工作,不会崩溃。我在修复垃圾对象问题后遇到的递归问题是因为 mainloop() 一次又一次地被叫。在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()
    
        2
  •  2
  •   martineau    7 年前

    我修改了您的代码,使用列表存储每个单元格,然后更新颜色。这可以通过使用类属性列表和跟踪中每个单元格的索引来实现 self.cells

    # 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)
    
    # made the class inherit from Tk to make it easier to manage.
    class Game(tk.Tk):
        def __init__(self, m, n, alive_cells=None):
            super().__init__()
            self.m = m
            self.n = n
            self.grid = np.ndarray((m, n), dtype = np.uint8)
            self.first = False # This variable is used to check if we need to draw or update rectangles.
            self.canvas_rect_list = [] # This list is used to track the list.
            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)]
            self.cell_size = 10000 // 400 #(self.m * self.n)
            self.canvas = tk.Canvas(self, bg="gray", height=self.m * self.cell_size, width=self.n * self.cell_size)
            self.show()
    
        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)
    
            # Added this to check if we need to draw or update rectangles.
            if self.first == False:
                self.draw_canvas()
            else:
                self.update_canvas()
    
        def iterate(self):
            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'
                # Add each rectangle to a list.
                self.canvas_rect_list.append(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))
    
            # sets first to True after first use of draw so we do not create new rectangles later.
            self.first = True
            self.canvas.pack()
            self.update_canvas()
    
        def update_canvas(self): 
            for ndex, cell in enumerate(self.cells):
                if cell.state == State.Alive:
                    color = 'blue'
                else:
                    color = 'red'
                # Configure each item in list baste of cell state and index.
                self.canvas.itemconfig(self.canvas_rect_list[ndex], fill=color)
            self.after(500, self.iterate)
    
    
    if __name__ == "__main__":
        # Change your glider and small_exploder variables to the
        # commented out ones below for an interesting pattern
        # That ends with a rotating 3 block line.
        # glider = (20, 17, ((1,3), (2,3), (2,1), (3,2), (3,3)))
        # small_exploder = (32, 30, ((12,10), (11,9), (11,10), (11,11), (12,9), (12,11), (14,10)))
        glider = (20, 17, ((1,3), (2,3), (2,1), (3,2), (3,3)))
        small_exploder = (32, 30, ((12,10), (11,9), (11,10), (11,11), (12,9), (12,11), (14,10)))
        M, N, STARTING_LIVE_CELLS, ITERATIONS = * small_exploder, 0
        g = Game(M, N, STARTING_LIVE_CELLS)
        g.mainloop()
    
        3
  •  1
  •   martineau    7 年前

    虽然你对这个版本没有任何兴趣,但我还是要发布它,因为我认为如果不是你,其他人可能会感兴趣,因为我花了相当多的时间 玩弄 对其进行改进和优化。

    它会跟踪 Canvas 通过添加 id 归因于 Cell 类实例,用于存储从中返回的画布对象id create_rectangle() 方法而不是单独的 canvas_rect_list . 这允许它避免每次迭代都重新创建它们。

    此外, iterate() 方法不再使 deepcopy 细胞 在创建新列表的过程中。它只是更新现有实例。这是因为规则的实现方式不同,并且它们只引用单独 grid

    说到这里,我还利用了 numpy #xnp . 这使得它不需要安装任何额外的模块就可以运行 如果你真的出于某种原因想使用它,它仍然存在(我个人认为使用它没有什么好处)。

    # 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()
    

    Screenshot of it running

    推荐文章