Reputation: 41
I am creating a replica of dodger using tkinter
. I am facing a problem with timing object movement. I was told the time module does not work well with tkinter
, therefore I should use after()
instead. However, I face the same problem with the after()
function as I did with the time module. Here is my code:
from tkinter import *
from random import randint
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.initWindow()
def initWindow(self):
self.master.title('Dodger')
self.pack(fill=BOTH, expand=1)
self.master.geometry('600x800')
self.master.config(bg='black')
menu = Menu(self.master)
self.master.config(menu=menu)
def clientExit():
exit()
file = Menu(menu)
file.add_command(label='Exit', command=clientExit)
file.add_command(label='Start', command=self.game)
menu.add_cascade(label='File', menu=file)
def game(self):
canvas = Canvas(self.master, width='600', height='800', borderwidth='0', highlightthickness='0')
canvas.pack()
canvas.create_rectangle(0, 0, 600, 800, fill='black', outline='black')
character = canvas.create_rectangle(270, 730, 330, 760, fill='magenta', outline='cyan', width='2')
def left(event):
cord = canvas.coords(character)
if cord[0] <= 5:
pass
else:
canvas.move(character, -10, 0)
def right(event):
cord = canvas.coords(character)
if cord[2] >= 595:
pass
else:
canvas.move(character, 10, 0)
self.master.bind('<Left>', left)
self.master.bind('<Right>', right)
class variables:
sizeMin = 10
sizeMax = 80
y = 10
minX = 5
maxX = 545
def createShape():
size = randint(variables.sizeMin, variables.sizeMax)
x = randint(variables.minX, variables.maxX)
topLeft = [x, variables.y]
bottomRight = [x + size, variables.y + size]
shape = canvas.create_rectangle(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1],
fill='red', outline='red')
return shape
def moveShape(shape):
canvas.move(shape, 0, 800)
for x in range(5):
x = createShape()
self.master.after(1000, moveShape(x))
root = Tk()
app = Window(root)
app.mainloop()
As you can see, at the bottom of the game instance, I created a square and moved it down five times at 1 second intervals. However, this did not work; my window just froze for the allotted time, then resumed afterwards. I am not sure if this is because my computer sucks or if I did something wrong. Please run my code in you editor and explain to me if I did something wrong.
Upvotes: 0
Views: 143
Reputation: 13729
You need to replace the sleep
function and the loop with after
. Try this:
def moveShape():
if self.step < 5:
canvas.move(shape, 0, 10)
self.master.after(1000, moveShape)
self.step += 1
self.step = 0
shape = createShape()
moveShape()
Also, if you move it by 800 pixels you won't see it after the first tick, so I reduced the amount to move to 10 pixels.
Edit: this plus a lot of other bugfixes and improvements:
import tkinter as tk
from random import randint
class variables:
sizeMin = 10
sizeMax = 80
y = 10
minX = 5
maxX = 545
class Window(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.master = master
self.initWindow()
def initWindow(self):
self.master.title('Dodger')
self.pack(fill=tk.BOTH, expand=1)
self.master.geometry('600x800')
self.master.config(bg='black')
menu = tk.Menu(self.master)
self.master.config(menu=menu)
file = tk.Menu(menu)
file.add_command(label='Exit', command=self.quit)
file.add_command(label='Start', command=self.game)
menu.add_cascade(label='File', menu=file)
def game(self):
self.canvas = tk.Canvas(self.master, width='600', height='800', borderwidth='0', highlightthickness='0')
self.canvas.pack()
self.canvas.create_rectangle(0, 0, 600, 800, fill='black', outline='black')
self.character = self.canvas.create_rectangle(270, 730, 330, 760, fill='magenta', outline='cyan', width='2')
self.createShape()
self.moveShape() # start the moving
self.master.bind('<Left>', self.left)
self.master.bind('<Right>', self.right)
def left(self, event):
cord = self.canvas.coords(self.character)
if cord[0] <= 5:
pass
else:
self.canvas.move(self.character, -10, 0)
def right(self, event):
cord = self.canvas.coords(self.character)
if cord[2] >= 595:
pass
else:
self.canvas.move(self.character, 10, 0)
def createShape(self):
size = randint(variables.sizeMin, variables.sizeMax)
x = randint(variables.minX, variables.maxX)
topLeft = [x, variables.y]
bottomRight = [x + size, variables.y + size]
self.shape = self.canvas.create_rectangle(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1],
fill='red', outline='red')
def moveShape(self, x=0):
if x < 5: # loop 5 times
self.canvas.move(self.shape, 0, 10)
self.after(1000, self.moveShape, x+1) # run this method again in 1,000 ms
root = tk.Tk()
app = Window(root)
app.mainloop()
Upvotes: 0
Reputation: 385970
The reason it freezes is because you're calling after
wrong.
Consider this code:
self.master.after(1000, moveShape(x))
... it is exactly the same as this code:
result = moveShape(x)
self.master.after(1000, result)
... which is the same as this, since moveShape
returns None
:
result = moveShape(x)
self.master.after(1000, None)
... which is the same as this:
result = moveShape(x)
self.master.after(1000)
... which is the same as
result = moveShape(x)
time.sleep(1)
In other words, you're telling it to sleep, so it sleeps.
after
requires a callable, or a reference to a function. You can pass additional args as arguments to after. So the proper way to call it is this:
self.master.after(1000, moveShape, x)
Though, I doubt that is exactly what you want, since all five iterations will try to run the code 1000ms after the loop starts, rather than 1000ms apart. That's just a simple matter of applying a little math.
Upvotes: 1