I'm writing Conway's game of life using Tkinter, and I want to have a "Go" button which allows the animation to begin, and continue stepping automatically until terminated. I'm using a Canvas to draw the environment, but since the "Go" button needs to complete the function call before it updates the canvas, the window just hangs until I kill the process. I've attempted to use canvas.update_idletasks()
and canvas.update()
(followed by a few seconds of sleeping) at points where I want to updated the canvas but that doesn't seem to do the trick. Any Ideas? Below is my GameOfLife class, the Environment class just manages the "board" of cells.
from Tkinter import *
from random import *
from time import time
from Environment import *
class GameOfLife(object):
def __init__(self, master, envDim):
self.unitSize = 10
self.dimension = envDim * self.unitSize
self.environment = Environment(envDim)
self.environment.seedBoard()
self.started = False
frame = Frame(master)
frame.pack()
Button(frame, text = "Go", command = self.go_call).pack(side = LEFT)
Button(frame, text = "Clear", command = self.reset_call).pack(side = LEFT)
Button(frame, text = "Close", command = frame.quit).pack(side = RIGHT)
canvas = self.drawCanvas(master, self.dimension)
def drawCanvas(self, master, dimension):
self.canvas = Canvas(master, width = self.dimension, height = self.dimension)
self.canvas.pack()
return self.canvas
def go_call(self):
print "<< Go Call >>"
if self.environment.started == False:
self.environment.seedBoard()
self.drawState(self.environment)
self.environment.nextBoard()
self.started = True
while True:
self.environment.nextBoard()
self.canvas.delete(ALL)
self.drawState(self.environment)
self.canvas.update_idletasks()
sleep(4)
def reset_call(self):
print "<< Reset Call >>"
self.canvas.delete(ALL)
self.environment = Environment(self.environment.dim)
def drawState(self, environment):
size = self.unitSize
for x in range(environment.dim):
for y in range(environment.dim):
if environment.matrix[x][y].alive == True:
xs = x * size
ys = y * size
self.canvas.create_rectangle(xs, ys, xs+size, ys+size, fill = 'black')
envDim = 70
root = Tk()
gol = GameOfLife(root, envDim)
root.mainloop()
You should never put an infinite loop inside your GUI program, and you definitely should never, ever call sleep. You already have an infinite loop running -- the event loop -- so take advantage of it.
The way to do animations like this is to write a function that draws one frame of the animation (or turn of the game, pick your metaphor). Then, have that function call itself via after
.
Roughly speaking your code should look like this:
def draw(self):
# draw the board according to the current state
...
# arrange for the next frame to draw in 4 seconds
self.after(4000, self.draw)
def __init__(self, ...):
...
self.go = tk.Button(self, text="Go", command=self.draw)
...
If you want to add a stop button, all it needs to do is set a flag. Then, in draw
, simply check for the flag before calling self.after
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With