2
\$\begingroup\$

I created a simple game of life and I used console prints to debug and visualize it at first. However, when I tried to introduce GUI (using Python tkinter), the game slowed down to a crawl. Everything is working fine, as far as I'm concerned, but I would like to know how to improve performance.

Notes:

  1. I asked something similar on Stackoverflow, but that was a specific question about a bug I think I had. Hope that's OK
  2. I have a design bug, where when a shape gets to the outskirts of the grid, it starts to behave badly. Known, and not the primary issue currnetly

Thanks in advance, really appriciate any input (include styling, abuse-of-methods / Python built-ins, alogrithmic and anything that comes up to mind in order to improve).

# 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) 
 self.rectangulars = []
 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):
 # delete old rectangulars
 for rect in self.rectangulars:
 self.canvas.delete(rect)
 for cell in self.cells:
 if cell.state == State.Alive:
 color = 'blue'
 else:
 color = 'red'
 self.rectangulars.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))
 self.canvas.pack()
 self.update_canvas()
 self.top.mainloop()
 def update_canvas(self): # iterate -> show -> draw_canvas
 self.top.after(100, self.iterate)
if __name__ == "__main__":
 glider = (40, 40, ((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()
200_success
146k22 gold badges190 silver badges478 bronze badges
asked Nov 12, 2018 at 9:59
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

The main problem is that you're attempting to create about 9000 canvas objects a second. While the canvas is fairly flexible and powerful, it starts to have performance issues once you create a few tens of thousands of objects, even when you delete them later. Since you are creating 9000 objects per second, you're going to hit that limit very quickly.

You also have the problem that you continually append to self.rectangulars but you never remove anything from that array. So, every second that array grows by 9000 elements. Since you attempt to iterate over that entire list every 100ms, the program will continue to get slower and slower.

The solution is to re-use the canvas items rather than recreate them. The create_rectangle method returns an identifier that you can use forever. For example, to reset the entire grid you could do something like this:

for item in self.rectangulars:
 self.canvas.itemconfigure(item, background=gray)
answered Nov 12, 2018 at 12:53
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.