First of all, as @furas mentioned, sleep is a wrong option, so remember to use after
for time delays in tkinter
!
Also, remember to structure your code (link#1, link#2) and don't try to put unnecessary multiple statements on one line.
Trivia
For movable buttons it's better to use Canvas
widget instead of any of Layout Managers (unless your real goal is teleportable buttons). It's easy to place
the button
randomly two times in a row, but if you want to simulate move you need not a set of new random coordinates, but old coordinates, random distance (offset) and random direction.
It's possible to implement this idea with place
, but there sweet move
method, which do all of this for you.
All you need is to place each button
on canvas
with create_window
(also it's gives you object ID
to control your widget on canvas) and simulate mass moving!
Example
import tkinter as tk
import random
class App(tk.Tk):
def __init__(self, difficulty, *args, **kwargs):
super().__init__(*args, **kwargs)
self.difficulty = difficulty
self.play_area = tk.Canvas(self, background='bisque')
self.play_area.pack(expand=True, fill='both')
self.animals = self.generate_animals()
self.play()
def play(self):
# move all animals continuously (each 100 ms)
self.move_animals()
self.after(100, self.play)
def generate_animals(self):
# generate all button-like animals
animals = [Animal('deer', self, text='DEER')]
for _ in range(self.difficulty * 2):
animals.append(Animal('doe', self, text='DOE'))
return animals
def move_animals(self):
# move all animals
for animal in self.animals:
animal.move()
class Animal(tk.Button):
def __init__(self, animal_type, *args, **kwargs):
super().__init__(*args, **kwargs)
self.animal_type = animal_type
self.base_speed = self.master.difficulty
self.play_area = self.master.play_area
self.move_sets = ['n', 's', 'w', 'e']
# place button on canvas (it's possible to randomize starting locations)
self.id = self.play_area.create_window(0, 0, window=self)
self.configure(command=self.kill)
def move(self):
# move animal
# get random speed and direction
distance = random.randint(0, 15) * self.base_speed
direction = random.choice(self.move_sets)
if direction in self.move_sets[:2]:
if direction == 'n':
distance *= -1
# to prevent case when an animal leaves play area
if 0 <= self.play_area.coords(self.id)[1] + distance <= self.play_area.winfo_height():
self.play_area.move(self.id, 0, distance)
else:
if direction == 'w':
distance *= -1
# to prevent case when an animal leaves play area
if 0 <= self.play_area.coords(self.id)[0] + distance <= self.play_area.winfo_width():
self.play_area.move(self.id, distance, 0)
def kill(self):
if self.animal_type == 'deer':
print('You shot the deer!')
else:
print('You shot a doe!')
print('The rest of the herd have trampled you to death')
print('Oh deer!')
self.master.destroy()
app = App(difficulty=2)
app.mainloop()
As you can see - it works somehow.
However, there's a big room to improve and adjust things. For example, a "smoother" movement. Although it's depends on how further we move objects it's also depends on frame rate, which 24 fps for a human specie (high frame rate). Thanks to after
again, we can control this parameter via time delay, which can be calculated by a formula time_delay = 1000 // desired_fps
:
...
def play(self):
# move all animals continuously
self.move_animals()
self.after(41, self.play) # 24 fps
# self.after(33, self.play) # 30 fps
...
It's also possible to improve movement with additional directions and simplify conditional logic to one statement:
...
class Animal(tk.Button):
def __init__(self, animal_type, *args, **kwargs):
super().__init__(*args, **kwargs)
...
# dictionary for representation purpose
self.move_sets = {'n': (0, -1), 's': (0, 1), 'w': (-1, 0), 'e': (1, 0),
'nw': (-1, -1), 'sw': (-1, 1), 'ne': (1, -1), 'se': (1, 1)}
...
def move(self):
# move animal
# get random distance and direction
distance = random.randint(0, 5) * self.base_speed
direction = random.choice(list(self.move_sets.values()))
movement = [_ * distance for _ in direction]
current_coords = self.play_area.coords(self.id)
# to prevent case when an animal leaves play area
if 0 <= current_coords[0] + movement[0] <= self.play_area.winfo_width() and \
0 <= current_coords[1] + movement[1] <= self.play_area.winfo_height():
self.play_area.move(self.id, *movement)
...
Similar problems