I am in the process of learning tkinter on Python 3.X. I am writing a simple program which will get one or more balls (tkinter ovals) bouncing round a rectangular court (tkinter root window with a canvas and rectangle drawn on it).
I want to be able to terminate the program cleanly by pressing the q key, and have managed to bind the key to the root and fire the callback function when a key is pressed, which then calls root.destroy().
However, I'm still getting errors of the form _tkinter.TclError: invalid command name ".140625086752360" when I do so. This is driving me crazy. What am I doing wrong?
from tkinter import *
import time
import numpy
class Ball:
def bates():
"""
Generator for the sequential index number used in order to
identify the various balls.
"""
k = 0
while True:
yield k
k += 1
index = bates()
def __init__(self, parent, x, y, v=0.0, angle=0.0, accel=0.0, radius=10, border=2):
self.parent = parent # The parent Canvas widget
self.index = next(Ball.index) # Fortunately, I have all my feathers individually numbered, for just such an eventuality
self.x = x # X-coordinate (-1.0 .. 1.0)
self.y = y # Y-coordinate (-1.0 .. 1.0)
self.radius = radius # Radius (0.0 .. 1.0)
self.v = v # Velocity
self.theta = angle # Angle
self.accel = accel # Acceleration per tick
self.border = border # Border thickness (integer)
self.widget = self.parent.canvas.create_oval(
self.px() - self.pr(), self.py() - self.pr(),
self.px() + self.pr(), self.py() + self.pr(),
fill = "red", width=self.border, outline="black")
def __repr__(self):
return "[{}] x={:.4f} y={:.4f} v={:.4f} a={:.4f} r={:.4f} t={}, px={} py={} pr={}".format(
self.index, self.x, self.y, self.v, self.theta,
self.radius, self.border, self.px(), self.py(), self.pr())
def pr(self):
"""
Converts a radius from the range 0.0 .. 1.0 to window coordinates
based on the width and height of the window
"""
assert self.radius > 0.0 and self.radius <= 1.0
return int(min(self.parent.height, self.parent.width)*self.radius/2.0)
def px(self):
"""
Converts an X-coordinate in the range -1.0 .. +1.0 to a position
within the window based on its width
"""
assert self.x >= -1.0 and self.x <= 1.0
return int((1.0 + self.x) * self.parent.width / 2.0 + self.parent.border)
def py(self):
"""
Converts a Y-coordinate in the range -1.0 .. +1.0 to a position
within the window based on its height
"""
assert self.y >= -1.0 and self.y <= 1.0
return int((1.0 - self.y) * self.parent.height / 2.0 + self.parent.border)
def Move(self, x, y):
"""
Moves ball to absolute position (x, y) where x and y are both -1.0 .. 1.0
"""
oldx = self.px()
oldy = self.py()
self.x = x
self.y = y
deltax = self.px() - oldx
deltay = self.py() - oldy
if oldx != 0 or oldy != 0:
self.parent.canvas.move(self.widget, deltax, deltay)
def HandleWallCollision(self):
"""
Detects if a ball collides with the wall of the rectangular
Court.
"""
pass
class Court:
"""
A 2D rectangular enclosure containing a centred, rectagular
grid of balls (instances of the Ball class).
"""
def __init__(self,
width=1000, # Width of the canvas in pixels
height=750, # Height of the canvas in pixels
border=5, # Width of the border around the canvas in pixels
rows=1, # Number of rows of balls
cols=1, # Number of columns of balls
radius=0.05, # Ball radius
ballborder=1, # Width of the border around the balls in pixels
cycles=1000, # Number of animation cycles
tick=0.01): # Animation tick length (sec)
self.root = Tk()
self.height = height
self.width = width
self.border = border
self.cycles = cycles
self.tick = tick
self.canvas = Canvas(self.root, width=width+2*border, height=height+2*border)
self.rectangle = self.canvas.create_rectangle(border, border, width+border, height+border, outline="black", fill="white", width=border)
self.root.bind('<Key>', self.key)
self.CreateGrid(rows, cols, radius, ballborder)
self.canvas.pack()
self.afterid = self.root.after(0, self.Animate)
self.root.mainloop()
def __repr__(self):
s = "width={} height={} border={} balls={}\n".format(self.width,
self.height,
self.border,
len(self.balls))
for b in self.balls:
s += "> {}\n".format(b)
return s
def key(self, event):
print("Got key '{}'".format(event.char))
if event.char == 'q':
print("Bye!")
self.root.after_cancel(self.afterid)
self.root.destroy()
def CreateGrid(self, rows, cols, radius, border):
"""
Creates a rectangular rows x cols grid of balls of
the specified radius and border thickness
"""
self.balls = []
for r in range(1, rows+1):
y = 1.0-2.0*r/(rows+1)
for c in range(1, cols+1):
x = 2.0*c/(cols+1) - 1.0
self.balls.append(Ball(self, x, y, 0.001,
numpy.pi/6.0, 0.0, radius, border))
def Animate(self):
"""
Animates the movement of the various balls
"""
for c in range(self.cycles):
for b in self.balls:
b.v += b.accel
b.Move(b.x + b.v * numpy.cos(b.theta),
b.y + b.v * numpy.sin(b.theta))
self.canvas.update()
time.sleep(self.tick)
self.root.destroy()
I've included the full listing for completeness, but I'm fairly sure that the problem lies in the Court class. I presume it's some sort of callback or similar firing but I seem to be beating my head against a wall trying to fix it.
You have effectively got two mainloops. In your Court.__init__ method you use after to start the Animate method and then start the Tk mainloop which will process events until you destroy the main Tk window.
However the Animate method basically replicates this mainloop by calling update to process events then time.sleep to waste some time and repeating this. When you handle the keypress and terminate your window, the Animate method is still running and attempts to update the canvas which no longer exists.
The correct way to handle this is to rewrite the Animate method to perform a single round of moving the balls and then schedule another call of Animate using after and provide the necessary delay as the after parameter. This way the event system will call your animation function at the correct intervals while still processing all other window system events promptly.
Related
import time
from tkinter import *
import random
class SpaceField:
def __init__(self):
self.window = Tk()
self.window.title("Asteriods")
self.canvas = self.canvas_display() #creates canvas
self.asteriods = self.asteriod_creation_seperation() #creates asteroids
self.active = True
self.move_active() #Moves asteroids
self.canvas.update()
def asteriod_creation_seperation(self): #creation of multple asteriods
asteriod_spacingx = random.randint(1,800)
asteriod_spacingy = random.randint(1,800)
asteriod_list = list() # could list([])
for i in range(15):
asteriod = self.canvas.create_oval( 30, 50 , 80 , 100 , tags="asteriod", width=2, outline="white")
asteriod_list.append("asteriod")
self.canvas.move(asteriod, asteriod_spacingx, asteriod_spacingy)
asteriod_spacingx = random.randint(1,500)
asteriod_spacingy = random.randint(1,500)
print(asteriod_spacingy)
return asteriod_list
Asteroid Creation. Creates asteroids and gives them random positions.
def asteriod_update(self): #asteriods movement method #MAin problem
x12 = 1
self.canvas.move("asteriod", 3, x12)
pos = self.canvas.coords("asteriod")
print(pos)
if (pos)[2] > 500:
x12 *= 5
I think this is where I need to add the collision detection. I just have no idea how to combine the lists of the circles and the collisions.
def move_active(self): #Basically a true loop
if self.active:
self.asteriod_update()
self.window.after(40, self.move_active)
def canvas_display(self): #canvas
canvas = Canvas(self.window, width=500, height=400, background='black')
canvas.pack(expand=True, fill="both")
canvas.update()
return canvas
Canvas display nothing special
def run(self):
self.window.mainloop()
if __name__ == '__main__':
SpaceF = SpaceField()
SpaceF.run()
Asteroids is a classic game but there were a number of problems in your code. The main one was calling move_active during initialization. This prevented the code from completing its mainloop initialization.
The other problem was the asteroid_update method that basically didn't do anything, also using tags to control all asteroids didn't work either.
Everything else was OK, although you might consider using polygons.
Here is one way to produce a bouncing objects program. I've inserted remarks that describe the methods used.
Objects change the speed and direction when they hit the boundary so their trajectories are randomized.
from tkinter import *
from random import randint as rnd
class SpaceField:
def __init__(self):
self.window = Tk()
self.window.title("Asteriods")
# Define canvas size and active flag
self.wide, self.high, self.active = 500, 400, True
self.canvas_display()
self.asteriod_creation_seperation()
def asteriod_creation_seperation(self):
self.asteroids, self.speed = [], []
size, radius = 50, 25
for i in range(15):
spacex = rnd(size, self.wide - size)
spacey = rnd(size, self.high - size)
self.asteroids.append( # Store oval item id
self.canvas.create_oval(
spacex, spacey, spacex+size, spacey+size,
width=2, tags = "asteriod", outline = "white"))
self.speed.append((rnd(1,4),rnd(1,4))) # Store random speed x, y
def asteriod_update(self): # MAIN DRIVER: Work on ALL asteroids
for i, a in enumerate(self.asteroids):
xx, yy = self.speed[i] # get speed data
x, y, w, h = self.canvas.coords(a)
# check for boundary hit then change direction and speed
if x < 0 or w > self.wide:
xx = -xx * rnd(1, 4)
if y < 0 or h > self.high:
yy = -yy * rnd(1, 4)
# Limit max and min speed then store it
self.speed[i] = (max( -4, min( xx, 4)), max( -4, min( yy, 4 )))
self.canvas.move(a, xx, yy) # update asteroid position
def move_active(self):
if self.active:
self.asteriod_update()
self.window.after(40, self.move_active)
def canvas_display(self):
self.canvas = Canvas(
self.window, width = self.wide,
height = self.high, background = "black")
self.canvas.pack(expand = True, fill = "both")
def run(self): # Begin asteroids here so that mainloop is executed
self.window.after(200, self.move_active)
self.window.mainloop()
if __name__ == "__main__":
SpaceF = SpaceField()
SpaceF.run()
I am a new coder and I am building a "Breakout" game in python and i got most of it to except getting the bricks to delete. *What is odd, is after the ball touches the bottom wall (out of bounds) and regenerates, the bricks are then able to be deleted by playing the game! Also, I need to stop the game after 3 chances. I'm stuck, with those 2 problems. Help please. Here is my code:
import tkinter
import time
# How big is the playing area?
CANVAS_WIDTH = 600 # Width of drawing canvas in pixels
CANVAS_HEIGHT = 800 # Height of drawing canvas in pixels
# Constants for the bricks
N_ROWS = 8 # How many rows of bricks are there?
N_COLS = 10 # How many columns of bricks are there?
SPACING = 5 # How much space is there between each brick?
BRICK_START_Y = 50 # The y coordinate of the top-most brick
BRICK_HEIGHT = 20 # How many pixels high is each brick
BRICK_WIDTH = (CANVAS_WIDTH - (N_COLS + 1) * SPACING) // N_COLS
# Constants for the ball and paddle
BALL_SIZE = 70
PADDLE_Y = CANVAS_HEIGHT - 40
PADDLE_WIDTH = 200
def main():
canvas = make_canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Brick Breaker')
# Makes a ball
ball = canvas.create_oval(300, 300, 350, 350, fill="red", outline="red")
# Makes a paddle
paddle = canvas.create_rectangle(299, PADDLE_Y, PADDLE_WIDTH, CANVAS_HEIGHT - 20, fill="black")
# Change_X
dx = 6
# Change_Y
dy = 6
for row in range(N_ROWS):
# Draws columns of brick
for col in range(N_COLS):
draw_brick(canvas, row, col)
while True:
# Mouse location and respond to movement
mouse_x = canvas.winfo_pointerx()
# Move Paddle to X location
canvas.moveto(paddle, mouse_x, PADDLE_Y)
# Ball movement
canvas.move(ball, dx, dy)
# If ball hits left of right wall, change X location
if hit_left_wall(canvas, ball) or hit_right_wall(canvas, ball):
dx *= -1
# If ball hits top wall, then change Y location
elif hit_top_wall(canvas, ball):
dy *= -1
elif hit_brick(canvas, ball, paddle):
dy *= -1
if hit_bottom(canvas, ball):
canvas.delete(ball)
ball = make_ball(canvas)
# Recreates canvas
canvas.update()
# Pause time
time.sleep(1 / 50.)
canvas.mainloop()
# Finds coordinates of paddle
def hit_paddle(canvas, ball, paddle):
paddle_coords = canvas.coords(paddle)
x1 = paddle_coords[0]
y1 = paddle_coords[1]
x2 = paddle_coords[2]
y2 = paddle_coords[3]
# If any object begins to overlap with paddle, create a Hit
result = canvas.find_overlapping(x1, y1, x2, y2)
return len(result) > 1
def make_ball(canvas):
return canvas.create_oval(300, 300, 350, 350, fill="red", outline="red")
def hit_brick(canvas, ball, paddle):
ball_coord = canvas.coords(ball)
x_1 = ball_coord[0]
y_1 = ball_coord[1]
x_2 = ball_coord[2]
y_2 = ball_coord[3]
results = canvas.find_overlapping(x_1, y_1, x_2, y_2)
for object in results:
if object == paddle or object == ball:
return len(results) > 1
else:
canvas.delete(object)
def moveto(canvas, oval, x, y):
# Get current position
x0, y0, x1, y1 = canvas.coords(oval)
# Sets new position
canvas.move(oval, x - x0, y - y0)
def hit_bottom(canvas, ball):
return get_bottom_y(canvas, ball) >= CANVAS_HEIGHT
def hit_left_wall(canvas, ball):
return get_left_x(canvas, ball) <= 0
def hit_right_wall(canvas, ball):
return get_right_x(canvas, ball) >= CANVAS_WIDTH
def hit_top_wall(canvas, ball):
return get_top_y(canvas, ball) <= 0
def draw_brick(canvas, row, col):
x = col * (BRICK_WIDTH + SPACING)
y = row * (BRICK_HEIGHT + SPACING)
color = "blue"
canvas.create_rectangle(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, fill=color, outline=color)
def get_bottom_y(canvas, ball):
return canvas.coords(ball)[3]
def get_top_y(canvas, ball):
"""
This friendly method returns the y coordinate of the top of an object.
Recall that canvas.coords(object) returns a list of the object
bounding box: [x_1, y_1, x_2, y_2]. The element at index 1 is the top-y
"""
return canvas.coords(ball)[1]
def get_left_x(canvas, ball):
"""
This friendly method returns the x coordinate of the left of an object.
Recall that canvas.coords(object) returns a list of the object
bounding box: [x_1, y_1, x_2, y_2]. The element at index 0 is the left-x
"""
return canvas.coords(ball)[0]
def get_right_x(canvas, ball):
"""
This friendly method returns the x coordinate of the right of an object.
Recall that canvas.coords(object) returns a list of the object
bounding box: [x_1, y_1, x_2, y_2]. The element at index 2 is the right-x
"""
return canvas.coords(ball)[2]
def make_canvas(width, height, title):
"""
Creates and returns a drawing canvas
of the given int size with a blue border,
ready for drawing.
"""
top = tkinter.Tk()
top.minsize(width=width, height=height)
top.title(title)
canvas = tkinter.Canvas(top, width=width + 1, height=height + 1)
canvas.pack()
return canvas
if __name__ == '__main__':
main()
The first problem is due to the if statement in the for loop inside hit_brick():
def hit_brick(canvas, ball, paddle):
ball_coord = canvas.coords(ball)
x_1 = ball_coord[0]
y_1 = ball_coord[1]
x_2 = ball_coord[2]
y_2 = ball_coord[3]
results = canvas.find_overlapping(x_1, y_1, x_2, y_2)
for object in results:
if object == paddle or object == ball: # <-- problem here
return len(results) > 1
else:
canvas.delete(object)
As the values of ball and paddle are 1 and 2 (as they are the first two canvas items created) and so results is something like (1, N) when the ball hit one of the bricks.
So the if statement returns true for the first checking and then the function exits by the return statement.
Now let the ball hits the bottom and it will be recreated with id greater than existing canvas items. The results will be something like (N, ball) when the ball hits one of the bricks.
This time the if statement will return false and the brick is deleted.
So hit_brick() should be modified as below:
def hit_brick(canvas, ball, paddle):
ball_coord = canvas.coords(ball)
results = canvas.find_overlapping(*ball_coord)
for object in results:
if object not in (paddle, ball):
canvas.delete(object)
return len(results) > 1
For the second problem, you need to declare a variable, for example lives = 3, before the while loop and decrease it by one if the ball hits the bottom.
The while loop should be terminated if lives == 0:
def main():
...
lives = 3
while lives > 0:
...
if hit_bottom(canvas.ball):
...
lives -= 1
For my homework I have to draw a rectangle frame and the turtle should be a dot and moving to a random destination.
When I press the space bar(which starts and stops the simulation), the frame starts changing position bouncing with the dot. The dot is also not moving but only bounce in the center.
'''
import turtle
import random
#used to infect
class Virus:
def __init__(self, colour, duration):
self.colour = colour
self.duration = duration
## This class represents a person
class Person:
def __init__(self, world_size):
self.world_size = world_size
self.radius = 7
self.location = turtle.position()
self.destination = self._get_random_location()
#random locations are used to assign a destination for the person
#the possible locations should not be closer than 1 radius to the edge of the world
def _get_random_location(self):
x = random.randint(-349, 349)
y = random.randint(-249, 249)
return (x, y)
#draw a person using a dot. Use colour if implementing Viruses
def draw(self):
turtle.penup()
turtle.home()
turtle.pendown()
turtle.dot(self.radius*2)
#returns true if within 1 radius
def reached_destination(self):
self.location = turtle.position()
distX = abs(abs(self.destination[0])-abs(self.location[0]))
distY = abs(abs(self.destination[1])- abs(self.location[1]))
if distX and distY < self.radius:
return True
else:
pass
#Updates the person each hour.
#- moves each person by calling the move method
#- if the destination is reached then set a new destination
#- progress any illness
def update(self):
self.move()
if self.reached_destination():
self._get_random_location()
else:
self.move()
#moves person towards the destination
def move(self):
turtle.setheading(turtle.towards(self.destination))
turtle.forward(self.radius/2)
class World:
def __init__(self, width, height, n):
self.size = (width, height)
self.hours = 0
self.people = []
self.add_person()
#add a person to the list
def add_person(self):
person = Person(1)
self.people.append(person)
#simulate one hour in the world.
#- increase hours passed.
#- update all people
#- update all infection transmissions
def simulate(self):
self.hours += 1
for item in self.people:
item.update()
#Draw the world. Perform the following tasks:
# - clear the current screen
# - draw all the people
# - draw the box that frames the world
# - write the number of hours and number of people infected at the top of the frame
def draw(self):
turtle.clear()
turtle.hideturtle()
turtle.penup()
turtle.right(180)
turtle.forward(250)
turtle.right(90)
turtle.forward(350)
turtle.left(180)
turtle.pendown()
turtle.forward(700)
turtle.left(90)
turtle.forward(500)
turtle.left(90)
turtle.forward(700)
turtle.left(90)
turtle.forward(500)
turtle.right(180)
turtle.forward(500)
turtle.write(f'Hours: {self.hours}', False, 'left')
turtle.update()
for item in self.people:
item.draw()
#---------------------------------------------------------
#Should not need to alter any of the code below this line
#---------------------------------------------------------
class GraphicalWorld:
""" Handles the user interface for the simulation
space - starts and stops the simulation
'z' - resets the application to the initial state
'x' - infects a random person
'c' - cures all the people
"""
def __init__(self):
self.WIDTH = 800
self.HEIGHT = 600
self.TITLE = 'COMPSCI 130 Project One'
self.MARGIN = 50 #gap around each side
self.PEOPLE = 200 #number of people in the simulation
self.framework = AnimationFramework(self.WIDTH, self.HEIGHT, self.TITLE)
self.framework.add_key_action(self.setup, 'z')
self.framework.add_key_action(self.infect, 'x')
self.framework.add_key_action(self.cure, 'c')
self.framework.add_key_action(self.toggle_simulation, ' ')
self.framework.add_tick_action(self.next_turn)
self.world = None
def setup(self):
""" Reset the simulation to the initial state """
print('resetting the world')
self.framework.stop_simulation()
self.world = World(self.WIDTH - self.MARGIN * 2, self.HEIGHT - self.MARGIN * 2, self.PEOPLE)
self.world.draw()
def infect(self):
""" Infect a person, and update the drawing """
print('infecting a person')
self.world.infect_person()
self.world.draw()
def cure(self):
""" Remove infections from all the people """
print('cured all people')
self.world.cure_all()
self.world.draw()
def toggle_simulation(self):
""" Starts and stops the simulation """
if self.framework.simulation_is_running():
self.framework.stop_simulation()
else:
self.framework.start_simulation()
def next_turn(self):
""" Perform the tasks needed for the next animation cycle """
self.world.simulate()
self.world.draw()
## This is the animation framework
## Do not edit this framework
class AnimationFramework:
"""This framework is used to provide support for animation of
interactive applications using the turtle library. There is
no need to edit any of the code in this framework.
"""
def __init__(self, width, height, title):
self.width = width
self.height = height
self.title = title
self.simulation_running = False
self.tick = None #function to call for each animation cycle
self.delay = 1 #smallest delay is 1 millisecond
turtle.title(title) #title for the window
turtle.setup(width, height) #set window display
turtle.hideturtle() #prevent turtle appearance
turtle.tracer(0, 0) #prevent turtle animation
turtle.listen() #set window focus to the turtle window
turtle.mode('logo') #set 0 direction as straight up
turtle.penup() #don't draw anything
turtle.setundobuffer(None)
self.__animation_loop()
def start_simulation(self):
self.simulation_running = True
def stop_simulation(self):
self.simulation_running = False
def simulation_is_running(self):
return self.simulation_running
def add_key_action(self, func, key):
turtle.onkeypress(func, key)
def add_tick_action(self, func):
self.tick = func
def __animation_loop(self):
try:
if self.simulation_running:
self.tick()
turtle.ontimer(self.__animation_loop, self.delay)
except turtle.Terminator:
pass
gw = GraphicalWorld()
gw.setup()
turtle.mainloop()
'''
The turtle dot should be bouncing slowly to the random location and the frame should stay still when I press the space bar. And I know the code is long sorry about that.
the frame starts changing position bouncing with the dot. The dot is
also not moving but only bounce in the center.
I've reworked your Person and World classes below to address these two issues. This simulation model you're given uses a single turtle which means we have to play by certain rules:
Each Person must keep track of their own position and heading
Only the draw() method should have the pen down, all other Person methods should have the pen up if using the turtle to do calculations
Whenever a Person uses the turtle, you can't assume anything about it as another Person was just using the turtle so you must set your heading, position and pen state
The changed portion of your posted code:
FONT = ('Arial', 16, 'normal')
# This class represents a person
class Person():
def __init__(self, world_size):
self.world_size = world_size
self.radius = 7
self.location = turtle.position()
self.destination = self._get_random_location()
turtle.penup()
turtle.setposition(self.location)
turtle.setheading(turtle.towards(self.destination))
self.heading = turtle.heading()
# random locations are used to assign a destination for the person
# the possible locations should not be closer than 1 radius to the edge of the world
def _get_random_location(self):
x = random.randint(self.radius - 349, 349 - self.radius)
y = random.randint(self.radius - 249, 249 - self.radius)
return (x, y)
# draw a person using a dot. Use colour if implementing Viruses
def draw(self):
x, y = self.location
# use .circle() not .dot() as the latter causes an extra update (flicker)
turtle.penup()
turtle.setposition(x, y - self.radius)
turtle.pendown()
turtle.begin_fill()
turtle.circle(self.radius)
turtle.end_fill()
# returns true if within 1 radius
def reached_destination(self):
distX = abs(self.destination[0] - self.location[0])
distY = abs(self.destination[1] - self.location[1])
return distX < self.radius and distY < self.radius
# Updates the person each hour.
# - moves each person by calling the move method
# - if the destination is reached then set a new destination
# - progress any illness
def update(self):
self.move()
if self.reached_destination():
self.destination = self._get_random_location()
turtle.penup()
turtle.setposition(self.location)
turtle.setheading(turtle.towards(self.destination))
self.heading = turtle.heading()
# moves person towards the destination
def move(self):
turtle.penup()
turtle.setheading(self.heading)
turtle.setposition(self.location)
turtle.forward(self.radius / 2)
self.location = turtle.position()
class World:
def __init__(self, width, height, n):
self.size = (width, height)
self.hours = 0
self.people = []
for _ in range(n):
self.add_person()
# add a person to the list
def add_person(self):
person = Person(1)
self.people.append(person)
# simulate one hour in the world.
# - increase hours passed.
# - update all people
# - update all infection transmissions
def simulate(self):
self.hours += 1
for item in self.people:
item.update()
# Draw the world. Perform the following tasks:
# - clear the current screen
# - draw all the people
# - draw the box that frames the world
# - write the number of hours and number of people infected at the top of the frame
def draw(self):
turtle.clear() # also undoes hideturtle(), etc.
turtle.hideturtle()
turtle.setheading(0)
turtle.penup()
turtle.setposition(-350, -250)
turtle.pendown()
for _ in range(2):
turtle.forward(500)
turtle.right(90)
turtle.forward(700)
turtle.right(90)
for item in self.people:
item.draw()
turtle.penup()
turtle.setposition(-350, -250)
# leave this to the end as .write() forces an extra update (flicker)
turtle.write(f'Hours: {self.hours}', move=False, align='left', font=FONT)
turtle.update()
I've also included some tweaks to reduce flicker. I'm sure there's lots more work to be done.
If we wanted to run this simulation faster, we'd make each Person a separate turtle (e.g. via inheritance) and use the reshaped turtle itself as it's presence on the screen and not draw the Person. And we'd throw separate turtles at the outline frame and the text to simplify updates.
I created some code to show 2 balls moving, but when I run it, it doesn't show the movement of the balls.Furthermore it stops running and ignores the infinite loop This is my code up to now:
import tkinter as tk
class ObjectHolder:
def __init__(self, pos, velocity, radius, id):
self.id = id # the id of the canvas shape
self.pos = pos # the position of the object
self.r = radius # the radius of incluence of the object
self.velocity = velocity # the velocity of the object
def moveobject(object):
x = object.pos[0] + object.velocity[0] # moves the object where
y = object.pos[1] + object.velocity[1] # 0=x and 1=y
object.pos = (x, y)
canvas.move(object, x, y)
class App():
def __init__(self, canvas):
self.canvas = canvas
self.objects = []
for i in range(0, 2):
position = ((i+1)*100, (i+1)*100)
velocity = (-(i+1)*10, -(i+1)*10)
radius = (i + 1) * 20
x1 = position[0]-radius
y1 = position[1]-radius
x2 = position[0]+radius
y2 = position[1]+radius
id = canvas.create_oval(x1, y1, x2, y2)
self.objects.append(ObjectHolder(position, velocity, radius, id))
self.symulation(self.objects)
def symulation(self, objects):
for object in objects: # this moves each object
ObjectHolder.moveobject(object)
# This part doesn't work. It is supposed to update the canvas
# and repeat forever.
self.canvas.update()
root.update()
self.canvas.after(50, self.symulation, objects)
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg="light blue")
canvas.pack()
App(canvas)
There are a number of issues with your code. One big one was the way you were updating the position of the existing canvas objects. The move() method needs to know the amount of movement (change in x and y value), not the new absolute position.
When I fixed that it turned out that the velocities were too big, so I reduced them to only be 10% of the values you had.
Another problem was with the way the ObjectHolder class was implemented. For one thing, the moveobject() method had no self argument, which it should have been using instead of having an object argument. You should probably also rename the method simply move().
The code below runs and does animate the movement.
import tkinter as tk
class ObjectHolder:
def __init__(self, pos, velocity, radius, id):
self.id = id # the id of the canvas shape
self.pos = pos # the position of the object
self.r = radius # the radius of incluence of the object
self.velocity = velocity # the velocity of the object
def moveobject(self):
x, y = self.pos
dx, dy = self.velocity
self.pos = (x + dx, y + dy)
canvas.move(self.id, dx, dy) # Amount of movement, not new position.
class App():
def __init__(self, canvas):
self.canvas = canvas
self.objects = []
for i in range(0, 2):
position = ((i+1)*100, (i+1)*100)
# velocity = (-(i+1)*10, -(i+1)*10)
velocity = (-(i+1), -(i+1)) # Much slower speed...
radius = (i + 1) * 20
x1 = position[0]-radius
y1 = position[1]-radius
x2 = position[0]+radius
y2 = position[1]+radius
id = canvas.create_oval(x1, y1, x2, y2)
self.objects.append(ObjectHolder(position, velocity, radius, id))
self.simulation(self.objects)
def simulation(self, objects):
for object in objects: # this moves each object
object.moveobject()
# This part doesn't work. It is supposed to update the canvas
# and repeat forever.
# self.canvas.update() # Not needed.
# root.update() # Not needed.
self.canvas.after(50, self.simulation, objects)
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg="light blue")
canvas.pack()
app = App(canvas)
root.mainloop() # Added.
So i've this code who create moving balls :
from Tkinter import *
from random import randrange
from threading import Thread
Matrice = (600*400)*[0]
class Ball(Frame):
def __init__(self, can, posx, posy, name):
self.can = can
self.largeur_can = int(self.can.cget("width"))
self.hauteur_can = int(self.can.cget("height"))
self.posx = posx
self.posy = posy
self.name = name
self.ball1 = self.can.create_oval(self.posy, self.posx, self.posy+10, self.posx+10, outline="red", fill=self.name, width=2)
self.nx = randrange(-10,10,1)
self.nx /= 2.0
self.ny = randrange(-10,10,1)
self.ny /= 2.0
self.move()
def move(self):
global Matrice
self.pos_ball = self.can.coords(self.ball1)
self.posx_ball = self.pos_ball[0]
self.posy_ball = self.pos_ball[1]
if self.posx_ball < 0 or (self.posx_ball + 10) > self.largeur_can:
self.nx = -self.nx
if self.posy_ball < 0 or (self.posy_ball + 10) > self.hauteur_can:
self.ny = -self.ny
self.can.move(self.ball1, self.nx, self.ny)
Matrice[int(self.posy_ball)*600 + int(self.posx_ball)] += 100
self.can.after(10, self.move)
root=Tk()
can=Canvas(root,width=600,height=400,bg="black")
for x in range(10):
x=Ball(can,100,400, "blue")
x=Ball(can,100,400, "green")
can.pack()
root.mainloop()
And i would create traces behind balls, i created a matrix Matrice where I recorded where each ball is passed and now i want to show it un the background but i don't know how.
Nota : values in the matrix could decrease or be changed somewhere other than in move.
So anyone have an idea how i could do that ?
This is an inefficient way of doing it, but it's the simplest. Every time you move a ball just draw a line from where it used to be to where it currently is.
self.can.move(self.ball1, self.nx, self.ny)
new_pos = self.can.coords(self.ball1)
self.can.create_line(self.posx_ball, self.posy_ball, new_pos[0], new_pos[1], fill='red')
self.can.after(10, self.move)
Note that this line will follow the top left of the sprite - you can adjust the coordinates if you want it to follow the middle of the sprite.