Tkinter Python OOP: Move seperate widgets at once with canvas.move() - python

I want to translate my procedural bouncing Ball programme to OOP in order to train OOP a bit.
I run into the problem that if I call a function on one instance of the object that contains an infinite loop, the next instance will never call its function. Resulting in only one of the balls moving.
import tkinter as tk
import time
import random
#Define root windows
root = tk.Tk()
root.geometry("800x800")
root.title("TkInter Animation Test")
#Define canvas that is inside the root window
canvas_width = 700
canvas_height = 700
canvas = tk.Canvas(root, width= canvas_width, height= canvas_height, bg="Black")
canvas.pack()
class Oval():
#Oval creation inside the canvas
def __init__(self, y1, x1, y2, x2, color):
self.y1 = y1
self.x1 = x1
self.y2= y2
self.x2= x2
self.oval = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=color)
#Moving the Oval(ov1)
def move(self):
self.xd = random.randint(5,10)
self.yd = random.randint(5,10)
while True:
canvas.move(self.oval, self.xd, self.yd)
# print(self.yd, self.xd)
self.coords = canvas.coords(self.oval)
# print (self.coords)
if self.coords[3] + self.yd >= 700 or self.coords[1] + self.yd <= 0:
if self.yd < 0:
self.yd = random.randint(5,10)
else:
self.yd = -(random.randint(5,10))
if self.coords[2] + self.xd >= 700 or self.coords[0] + self.xd <= 0:
if self.xd < 0:
self.xd = random.randint(5,10)
else:
self.xd = -(random.randint(5,10))
root.update()
time.sleep(.01)
ov1 = Oval(10,10,40,40, "blue")
ov2 = Oval(80,80,120,120, "red")
ov3 = Oval(240,240,270,270, "Yellow")
ov4 = Oval(360,360,400,400, "Green")
ov5 = Oval(500,500,540,540, "white")
#Problem is that ov1.move() has a internal loop and ov2.move() will never be called
# ov1.move()
# ov2.move()
# ov3.move()
# ov4.move()
# ov5.move()
tk.mainloop()

Have solved it on my own.
I just took out the While True: loop from the class and called the funcion in a loop bellow.
import tkinter as tk
import time
import random
#Define root windows
root = tk.Tk()
root.geometry("800x800")
root.title("TkInter Animation Test")
#Define canvas that is inside the root window
canvas_width = 700
canvas_height = 700
canvas = tk.Canvas(root, width= canvas_width, height= canvas_height, bg="Black")
canvas.pack()
class Oval():
#Oval creation inside the canvas
def __init__(self, y1, x1, y2, x2, color):
self.y1 = y1
self.x1 = x1
self.y2= y2
self.x2= x2
self.oval = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=color)
self.xd = random.randint(5,10)
# self.xd = 10
self.yd = random.randint(5,10)
# self.yd = 10
#Moving the Oval(ov1)
def move(self):
canvas.move(self.oval, self.xd, self.yd)
# print(self.yd, self.xd)
self.coords = canvas.coords(self.oval)
# print (self.coords)
if self.coords[3] + self.yd >= 700 or self.coords[1] + self.yd <= 0:
if self.yd < 0:
self.yd = random.randint(5,10)
# self.yd = 10
else:
self.yd = -(random.randint(5,10))
# self.yd = -10
if self.coords[2] + self.xd >= 700 or self.coords[0] + self.xd <= 0:
if self.xd < 0:
self.xd = random.randint(5,10)
# self.xd = 10
else:
self.xd = -(random.randint(5,10))
# self.xd = -10
root.update()
# time.sleep(.000000001)
ov1 = Oval(10,10,40,40, "blue")
ov2 = Oval(80,80,120,120, "red")
ov3 = Oval(240,240,270,270, "Yellow")
ov4 = Oval(360,360,400,400, "Green")
ov5 = Oval(500,500,540,540, "white")
while True:
ov1.move()
ov2.move()
ov3.move()
ov4.move()
ov5.move()
time.sleep(.01)
tk.mainloop()

Related

Why does this collision not work properly?

I am coding a basic ping pong game in python.
I have this code:
from tkinter import *
from tkinter.ttk import *
from math import sqrt
import time, random
# creates tkinter window or root window
Window = Tk()
HEIGHT = 700
WIDTH = 700
c = Canvas(Window, width = WIDTH, height = HEIGHT)
c.pack()
c.configure(bg='black')
#Define mid x and y
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
#Create paddles and ball
paddle_1 = c.create_rectangle(0, 0, 100, 20, fill='grey', outline = 'white')
paddle_2 = c.create_rectangle(0, 0, 100, 20, fill='grey', outline = 'white')
ball = c.create_oval(0, 0, 30, 30, fill='white', outline = 'grey')
c.move(paddle_1, 300, 0)
c.move(paddle_2, 300, 680)
c.move(ball, 330, 340)
id = [paddle_1, paddle_2, ball]
ball_move_x = 0
ball_move_y = 10
cooldown = 0
#get co-ordinates of object
def get_coords(i):
pos = c.coords(i)
x = int(pos[0] + pos[2]/2)
y = int(pos[1] + pos[3]/2)
return x, y
#Bounce (makes goofy physics)
def bounce(x, y):
x += random.randint(1,20)/10
x *= -1
y += 1
y *= -1
return x, y
#Collision code (uses co-ords and checks if within a certain range)
def collision(paddle_x, paddle_y, ball_x, ball_y, x = ball_move_x, y = ball_move_y,):
if(ball_x in range(paddle_x-50, paddle_x+50, 1) and ball_y in range(paddle_y-30, paddle_y+30, 1)):
x, y = bounce(ball_move_x, ball_move_y)
print("collision")
return x, y
else:
return x, y
# Optimised movement functions without cycling through with an if operator
def move_paddle_1_left(e):
c.move(paddle_1, -100, 0)
def move_paddle_1_right(e):
c.move(paddle_1, +100, 0)
def move_paddle_2_left(e):
c.move(paddle_2, -100, 0)
def move_paddle_2_right(e):
c.move(paddle_2, +100, 0)
# bind functions to key
c.bind_all('<KeyPress-a>', move_paddle_1_left)
c.bind_all('<KeyPress-d>', move_paddle_1_right)
c.bind_all('<Left>', move_paddle_2_left)
c.bind_all('<Right>', move_paddle_2_right)
c.pack()
#MAIN GAME LOOP
while True:
Window.update()
paddle_1_x, paddle_1_y = get_coords(id[0])
paddle_2_x, paddle_2_y = get_coords(id[1])
ball_x, ball_y = get_coords(id[2])
ball_move_x, ball_move_y = collision(paddle_1_x, paddle_1_y, ball_x, ball_y)
ball_move_x, ball_move_y = collision(paddle_2_x, paddle_2_y, ball_x, ball_y)
c.move(ball, +ball_move_x, +ball_move_y)
time.sleep(0.0333333)
Window.update()
The problem is that the collision (so far, only bouncing off paddles is implemented) is buggy: it will essentially collide and collide infinitely.
My approach to collision detection and response is that I get the balls middle co-ords and the bat's middle co-ords; if the ball's co-ords are within a range of the bats co-ords I count it as a collision.
Since then I have been having an issue where essentially it will collide infinitely and just spam up and down. Why does this occur, and how can I fix it?
When you detect collision then you use x *= -1 to move in different direction (up) but in next moves you don't use -1 to keep the same direction and it move again down.
You should keep this value as global variable
direction_x = 1
direction_y = 1
and alwasy use
return x*direction_x, y*direction_y
And when you detect collision then change direction
direction_y = -direction_y
direction_x = 1
direction_y = 1
def collision(paddle_x, paddle_y, ball_x, ball_y, x = ball_move_x, y = ball_move_y,):
global direction_x
global direction_y
if (paddle_x-50 <= ball_x <= paddle_x+50) and (paddle_y-30 <= ball_y <= paddle_y+30):
direction_y = - direction_y
print("collision")
return x*direction_x, y*direction_y
Full working code with other changes
EDIT:
You forgot () when you calculate center - you have to first add and later divide but without () it first divided and later added.
x = int((pos[0] + pos[2])/2)
y = int((pos[1] + pos[3])/2)
import tkinter as tk # PEP8: `import *` is not preferred
from math import sqrt
import time
import random
# --- constants ---
HEIGHT = 700
WIDTH = 700
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
BALL_MOVE_X = 0
BALL_MOVE_Y = 10
# --- functions ---
def get_coords(canvas, item):
pos = canvas.coords(item)
x = int( (pos[0] + pos[2]) / 2 )
y = int( (pos[1] + pos[3]) / 2 )
return x, y
def collision(paddle_x, paddle_y, ball_x, ball_y, x=BALL_MOVE_X, y=BALL_MOVE_Y):
global direction_x
global direction_y
if (paddle_x-50 <= ball_x <= paddle_x+50) and (paddle_y-30 <= ball_y <= paddle_y+30):
direction_y = - direction_y
print("paddle collision")
return x*direction_x, y*direction_y
def move_paddle_1_left(e):
canvas.move(paddle_1, -100, 0)
def move_paddle_1_right(e):
canvas.move(paddle_1, +100, 0)
def move_paddle_2_left(e):
canvas.move(paddle_2, -100, 0)
def move_paddle_2_right(e):
canvas.move(paddle_2, +100, 0)
def gameloop():
# MAIN GAME LOOP
paddle_1_x, paddle_1_y = get_coords(canvas, paddle_1)
paddle_2_x, paddle_2_y = get_coords(canvas, paddle_2)
ball_x, ball_y = get_coords(canvas, ball)
ball_move_x, ball_move_y = collision(paddle_1_x, paddle_1_y, ball_x, ball_y)
ball_move_x, ball_move_y = collision(paddle_2_x, paddle_2_y, ball_x, ball_y)
canvas.move(ball, ball_move_x, ball_move_y)
window.after(25, gameloop) # repeat after 25ms
# --- main ---
window = tk.Tk() # PEP8: `lower case name`
canvas = tk.Canvas(window, width=WIDTH, height=HEIGHT, bg='black') # PEP8: `=` without spaces inside `( )`
canvas.pack()
paddle_1 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
paddle_2 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
canvas.move(paddle_1, 300, 0)
canvas.move(paddle_2, 300, 680)
ball = canvas.create_oval(0, 0, 30, 30, fill='white', outline='grey')
canvas.move(ball, 330, 340)
ball_move_x = 0
ball_move_y = 10
direction_x = 1
direction_y = 1
cooldown = 0
canvas.bind_all('<KeyPress-a>', move_paddle_1_left)
canvas.bind_all('<KeyPress-d>', move_paddle_1_right)
canvas.bind_all('<Left>', move_paddle_2_left)
canvas.bind_all('<Right>', move_paddle_2_right)
window.after(25, gameloop) # 25ms = 0.025s = 40 Frames Per Second (FPS)
window.mainloop()
PEP 8 -- Style Guide for Python Code
EDIT:
Version which change direction when touch window's border
import tkinter as tk # PEP8: `import *` is not preferred
from math import sqrt
import time
import random
# --- constants ---
HEIGHT = 700
WIDTH = 700
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
BALL_MOVE_X = 5
BALL_MOVE_Y = 10
# --- functions ---
def get_coords(canvas, item):
pos = canvas.coords(item)
x = int((pos[0] + pos[2])/2)
y = int((pos[1] + pos[3])/2)
return x, y
def collision(paddle_x, paddle_y, ball_x, ball_y, x=BALL_MOVE_X, y=BALL_MOVE_Y):
global direction_x
global direction_y
if (paddle_x-50 <= ball_x <= paddle_x+50) and (paddle_y-30 <= ball_y <= paddle_y+30):
direction_y = - direction_y
print("paddle collision")
#return x, y
return x, y
def move_paddle_1_left(e):
canvas.move(paddle_1, -100, 0)
def move_paddle_1_right(e):
canvas.move(paddle_1, +100, 0)
def move_paddle_2_left(e):
canvas.move(paddle_2, -100, 0)
def move_paddle_2_right(e):
canvas.move(paddle_2, +100, 0)
def gameloop():
# MAIN GAME LOOP
global direction_x
global direction_y
paddle_1_x, paddle_1_y = get_coords(canvas, paddle_1)
paddle_2_x, paddle_2_y = get_coords(canvas, paddle_2)
ball_x, ball_y = get_coords(canvas, ball)
if ball_x <= 0 or ball_x >= WIDTH:
direction_x = - direction_x
if ball_y <= 0 or ball_y >= HEIGHT:
print("Get point")
direction_y = - direction_y
ball_move_x, ball_move_y = collision(paddle_1_x, paddle_1_y, ball_x, ball_y)
ball_move_x, ball_move_y = collision(paddle_2_x, paddle_2_y, ball_x, ball_y)
canvas.move(ball, ball_move_x*direction_x, ball_move_y*direction_y)
window.after(25, gameloop) # repeat after 25ms
# --- main ---
window = tk.Tk() # PEP8: `lower case name`
canvas = tk.Canvas(window, width=WIDTH, height=HEIGHT, bg='black') # PEP8: `=` without spaces inside `( )`
canvas.pack()
paddle_1 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
paddle_2 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
canvas.move(paddle_1, 300, 0)
canvas.move(paddle_2, 300, 680)
ball = canvas.create_oval(0, 0, 30, 30, fill='white', outline='grey')
canvas.move(ball, 330, 340)
ball_move_x = 0
ball_move_y = 10
direction_x = 1
direction_y = 1
cooldown = 0
canvas.bind_all('<KeyPress-a>', move_paddle_1_left)
canvas.bind_all('<KeyPress-d>', move_paddle_1_right)
canvas.bind_all('<Left>', move_paddle_2_left)
canvas.bind_all('<Right>', move_paddle_2_right)
window.after(25, gameloop) # 25ms = 0.025s = 40 Frames Per Second (FPS)
window.mainloop()

Drawing lines between two object in tkinter UI

Thanks to lots of help in Drag and drop the object in Tkinter UI, I could manage to draw three square that are draggable.
Now I am trying to draw 3 lines between each squares and I cannot find way to enable it. What I tried is following :
from tkinter import *
window = Tk()
window.state('zoomed')
window.configure(bg = 'white')
def drag(event):
new_x = event.x_root - window.winfo_rootx()
new_y = event.y_root - window.winfo_rooty()
event.widget.place(x=new_x, y=new_y,anchor=CENTER)
card = Canvas(window, width=10, height=10, bg='red1')
card.place(x=300, y=600,anchor=CENTER)
card.bind("<B1-Motion>", drag)
another_card = Canvas(window, width=10, height=10, bg='red2')
another_card.place(x=600, y=600,anchor=CENTER)
another_card.bind("<B1-Motion>", drag)
third_card = Canvas(window, width=10, height=10, bg='red3')
third_card.place(x=600, y=600,anchor=CENTER)
third_card.bind("<B1-Motion>", drag)
def line(x1, y1, x2, y2):
print(x1, y1, x2, y2)
Canvas.create_line(x1, y1, x2, y2, fill="green")
coor_1 = canvas.coords(card)
coor_2 = canvas.coords(another_card)
line(coor_1[0],coor_1[1],coor_1[0],coor_2[1])
window.mainloop()
It didn't work and I don't think it will work since this code does not catch the change occurred by dragging object, But I cannot guess how to code since I do not understand how the event function works completely. How should I make a code for it ?
for the purpose of self-study:
import tkinter as tk
window = tk.Tk()
window.state('zoomed')
class DragAndDropArea(tk.Canvas):
def __init__(self,master, **kwargs):
tk.Canvas.__init__(self,master, **kwargs)
self.active = None
card_I = self.draw_card(300,600, 100,100, 'red1')
card_II = self.draw_card(600,600, 100,100, 'red2')
card_III = self.draw_card(400,400, 100,100, 'red3')
self.bind_tention(card_I,card_III)
self.bind_tention(card_I,card_II)
self.bind_tention(card_III,card_II)
self.bind('<ButtonPress-1>', self.get_item)
self.bind('<B1-Motion>',self.move_active)
self.bind('<ButtonRelease-1>', self.set_none)
def set_none(self,event):
self.active = None
def get_item(self,event):
try:
item = self.find_withtag('current')
self.active = item[0]
except IndexError:
print('no item was clicked')
def move_active(self,event):
if self.active != None:
coords = self.coords(self.active)
width = coords[2] - coords[0] #x2-x1
height= coords[1] - coords[3] #y1-y2
position = coords[0],coords[1]#x1,y1
x1 = event.x - width/2
y1 = event.y - height/2
x2 = event.x + width/2
y2 = event.y + height/2
self.coords(self.active, x1,y1, x2,y2)
try:
self.update_tention(self.active)
except IndexError:
print('no tentions found')
def update_tention(self, tag):
tentions = self.find_withtag(f'card {tag}')
for tention in tentions:
bounded_cards = self.gettags(tention)
card = bounded_cards[0].split()[-1]
card2= bounded_cards[1].split()[-1]
x1,y1 = self.get_mid_point(card)
x2,y2 = self.get_mid_point(card2)
self.coords(tention, x1,y1, x2,y2)
self.lower(tention)
def draw_card(self, x,y, width,height, color):
x1,y1 = x,y
x2,y2 = x+width,y+height
reference = self.create_rectangle(x1,y1,x2,y2,
fill = color)
return reference
def bind_tention(self, card, another_card):
x1,y1 = self.get_mid_point(card)
x2,y2 = self.get_mid_point(another_card)
tag_I = f'card {card}'
tag_II= f'card {another_card}'
reference = self.create_line(x1,y1,x2,y2, fill='green',
tags=(tag_I,tag_II))
self.lower(reference)
def get_mid_point(self, card):
coords = self.coords(card)
width = coords[2] - coords[0] #x2-x1
height= coords[1] - coords[3] #y1-y2
position = coords[0],coords[1]#x1,y1
mid_x = position[0] + width/2
mid_y = position[1] - height/2
return mid_x,mid_y
area = DragAndDropArea(window, bg='white')
area.pack(fill='both',expand=1)
window.mainloop()

Bouncing ball game tkinter canvas

I write a game in python in which the goal is to bounce the ball off the platform.
Everything works pretty well, but the platform's movement is not that smooth. Could you help me make the platform movement more smooth? If the code isn't too clear, I'm sorry, but I'm new in python
import tkinter as tk
import random
root = tk.Tk()
width = 900
height = 500
canvas = tk.Canvas(root, bg='white', width=width, height=height)
canvas.pack()
x = random.randrange(700)
ball = canvas.create_oval(x+10, 10, x+50, 50, fill='green')
platform_y = height - 20
platform = canvas.create_rectangle(width//2-50, platform_y, width//2+50, platform_y+10, fill='black')
xspeed = 2
yspeed = 2
skore = 0
body = 0
def move_ball():
global xspeed
global yspeed
x1, y1, x2, y2 = canvas.coords(ball)
if x1 <= 0 or x2 >= width:
xspeed = -xspeed
if y1 <= 0:
yspeed = 10
elif y2 == platform_y:
cx = (x1 + x2) // 2
px1, _, px2, _ = canvas.coords(platform)
if px1 <= cx <= px2:
yspeed = -10
else:
canvas.create_text(width//2, height//2, text='Game Over', font=('Arial Bold', 32), fill='red')
return
canvas.move(ball, xspeed, yspeed)
canvas.after(20, move_ball)
def board_right(event):
x1, y1, x2, y2 = canvas.coords(platform)
if x2 < width:
dx = min(width-x2, 10)
canvas.move(platform, dx, 0)
def board_left(event):
x1, y1, x2, y2 = canvas.coords(platform)
if x1 > 0:
dx = min(x1, 10)
canvas.move(platform, -dx, 0)
canvas.bind_all('<Right>', board_right)
canvas.bind_all('<Left>', board_left)
move_ball()
root.mainloop()
The problem is that the speed of the platform is dependent on the auto-repeat speed of your keyboard.
Instead of moving once for each <Right> or <Left> event, use a key press to start the platform moving in the desired direction and a key release to stop the platform moving. Then, use after to repeatedly move the platform in the given direction.
Example:
after_id = None
def platform_move(direction):
"""
direction should be -1 to move left, +1 to move right,
or 0 to stop moving
"""
global after_id
speed = 10
if direction == 0:
canvas.after_cancel(after_id)
after_id = None
else:
canvas.move(platform, direction*speed, 0)
after_id = canvas.after(5, platform_move, direction)
canvas.bind_all("<KeyPress-Right>", lambda event: platform_move(1))
canvas.bind_all("<KeyRelease-Right>", lambda event: platform_move(0))
canvas.bind_all("<KeyPress-Left>", lambda event: platform_move(-1))
canvas.bind_all("<KeyRelease-Left>", lambda event: platform_move(0))
The above code doesn't handle the case where you might press both keys at the same time, but that can be handled with a little additional logic. The point is to show how you can use the keys to start and stop an animation.

How to display and update a score on screen?

My question is about displaying and updating text, in order to display the score on screen.
I would like to create a score like the real game that would appear on the screen. But after researching Google, I have not found anyone wishing to increase a score on the screen ...
Indeed, I would like the score to increase each time the bird passes between the pipes and therefore whenever the pipes have an X of 67 pixels. So does anyone know how to do this?
from tkinter import *
import random
from random import randint
def sauter(event):
canvas.move(image_oiseau, 0, -10*DY)
def deplacement():
global tuyx,tuyx2,h,H,oisx,oisy,solx,sol2x
x0, y0, x1, y1 = canvas.bbox(image_oiseau)
if y1 < 416:
canvas.move(image_oiseau, 0, DY)
canvas.coords(image_sol,solx,512)
if solx >= -144:
solx=solx-5
else:
solx=144
canvas.coords(image_sol2,sol2x,512)
if sol2x >= 144:
sol2x=sol2x-5
else:
sol2x=432
canvas.coords(image_tuyau_haut,tuyx,h)
canvas.coords(image_tuyau_bas,tuyx,h-241)
if tuyx>=-28:
tuyx=tuyx-5
else:
tuyx=316
h=randint(256,505)
canvas.coords(image_tuyau_haut2,tuyx2,H)
canvas.coords(image_tuyau_bas2,tuyx2,H-241)
if tuyx2>=-28:
tuyx2=tuyx2-5
else:
tuyx2=316
H=randint(256,505)
canvas.after(40,deplacement)
LARGEUR = 286
HAUTEUR = 510
DY = 5
tuyx=316
tuyx2=488
h=randint(256,505)
H=randint(256,505)
oisx=67
oisy=244
solx=144
sol2x=432
fenetre = Tk()
canvas = Canvas(fenetre, width=LARGEUR, height=HAUTEUR)
fond = PhotoImage(file="background-day.png")
fond2 = PhotoImage(file="background-night.png")
fond=[fond,fond2]
F= random.choice(fond)
canvas.create_image(144,256, anchor=CENTER,image=F)
tuyau_haut = PhotoImage(file="tuyau_vers_le_haut.png")
image_tuyau_haut = canvas.create_image(tuyx,h,anchor=CENTER,image=tuyau_haut)
image_tuyau_haut2 = canvas.create_image(tuyx2,H,anchor=CENTER,image=tuyau_haut)
tuyau_bas = PhotoImage(file="tuyau_vers_le_bas.png")
image_tuyau_bas = canvas.create_image(tuyx,h,anchor=CENTER,image=tuyau_bas)
image_tuyau_bas2 = canvas.create_image(tuyx2,H,anchor=CENTER,image=tuyau_bas)
sol = PhotoImage(file="sol-day.png")
image_sol = canvas.create_image(144,512, anchor=S,image=sol)
image_sol2 = canvas.create_image(432,512, anchor=S,image=sol)
oiseau = PhotoImage(file="yellowbird-midflap.png")
oiseau2 = PhotoImage(file="bluebird-midflap.png")
oiseau3 = PhotoImage(file="redbird-midflap.png")
oiseau=[oiseau,oiseau2,oiseau3]
O=random.choice(oiseau)
image_oiseau=canvas.create_image(oisx,oisy, anchor=W,image=O)
deplacement()
canvas.pack()
canvas.focus_set()
canvas.bind("<space>",sauter)
fenetre.mainloop()
Could someone explain the problem to me because I thought it would be easy :(
Here are the pictures of the game :)
Here are the pictures of the game
Here is one approach to display the scores: It uses a tk.Label, that is updated at the same time the score increases.
The trigger that increases the score is currently a random call to on_change; you can modify this to be a test if a pipe x coordinates becomes lower than the bird x coordinates (the bird successfully crossed the obstacle)
You can, if you want relocate the score label on the canvas.
import random
import tkinter as tk
WIDTH, HEIGHT = 500, 500
def create_pipes():
pipes = []
for x in range(0, WIDTH, 40):
y1 = random.randrange(50, HEIGHT - 50)
y0 = y1 + 50
pipes.append(canvas.create_line(x, 0, x, y1))
pipes.append(canvas.create_line(x, y0, x, HEIGHT))
return pipes
def move_pipes():
for pipe in pipes:
canvas.move(pipe, -2, 0)
x, y0, _, y1 = canvas.coords(pipe)
if x < 0:
canvas.coords(pipe, WIDTH+20, y0, WIDTH+20, y1)
if random.randrange(0, 20) == 10:
on_change()
root.after(40, move_pipes)
def on_change():
global score
score += 1
score_variable.set(f'score: {score}')
root = tk.Tk()
tk.Button(root, text='start', command=move_pipes).pack()
score = 0
score_variable = tk.StringVar(root, f'score: {score}')
score_lbl = tk.Label(root, textvariable=score_variable)
score_lbl.pack()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg="cyan")
canvas.pack()
pipes = create_pipes()
root.mainloop()

appending code to create multiple random moving sprites - python tkinter

Below is the code I am trying to get working, i currently have it creating the sprites although once the second sprite is created the movement for the first is stopped.
Say there are 2 sprites both of them should be moving individually, completely random from each other.
The code creates a new sprite every 3 seconds(for testing purposes once the code is working it will be set to 10 seconds)
class enemymove(object):
def create():
global enemy, radiusenemy, xenemy, yenemy
amount = 0
enemy = list()
xenemy = list()
yenemy = list()
enemypositionx = list()
enemypositiony = list()
lastop = len(enemy)
enemy.append(amount)
xenemy.append(amount)
yenemy.append(amount)
enemypositionx.append(amount)
enemypositiony.append(amount)
amount = amount + 1
radiusenemy = 12.5
enemypositionx[lastop] = random.uniform(12.5, resX-12.5)
enemypositiony[lastop] = random.uniform(12.5, resY-12.5)
print(lastop)
enemy[lastop] = canvas.create_oval((enemypositionx[lastop]) + radiusenemy ,(enemypositiony[lastop]) - radiusenemy ,(enemypositionx[lastop]) - radiusenemy ,(enemypositiony[lastop]) + radiusenemy, fill="black", outline="black")
xenemy[lastop] = (canvas.coords(enemy[lastop])[2]) - 12.5
yenemy[lastop] = (canvas.coords(enemy[lastop])[3]) - 12.5
Thread(target = spawntime.timer).start()
enemymove.movement(lastop);
def movement(lastop):
global timer
timer = random.randint(150,3000)
count = random.randint(1, 4)
print(count)
if count == 1:
enemymove.up(lastop);
if count == 2:
enemymove.downward(lastop);
if count == 3:
enemymove.rightran(lastop);
if count == 4:
enemymove.leftran(lastop);
def up(lastop):
global xenemy, yenemy
print ("forward")
yenemy[lastop] = (canvas.coords(enemy[lastop])[1])
canvas.coords(enemy[lastop], xenemy[lastop] + radiusenemy, yenemy[lastop] + radiusenemy, xenemy[lastop] - radiusenemy, yenemy[lastop] - radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
def downward(lastop):
global xenemy, yenemy
print("back")
yenemy[lastop] = (canvas.coords(enemy[lastop])[3])
canvas.coords(enemy[lastop], xenemy[lastop] - radiusenemy, yenemy[lastop] + radiusenemy, xenemy[lastop] + radiusenemy, yenemy[lastop] - radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
def rightran(lastop):
global xenemy, yenemy
print("right")
xenemy[lastop] = (canvas.coords(enemy[lastop])[2])
canvas.coords(enemy[lastop], xenemy[lastop] - radiusenemy, yenemy[lastop] - radiusenemy, xenemy[lastop] + radiusenemy, yenemy[lastop] + radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
def leftran(lastop):
global xenemy, yenemy
print("left")
xenemy[lastop] = (canvas.coords(enemy[lastop])[0])
canvas.coords(enemy[lastop], xenemy[lastop] - radiusenemy, yenemy[lastop] - radiusenemy, xenemy[lastop] + radiusenemy, yenemy[lastop] + radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
class spawntime():
def timer():
global timeset
timeset = 3
spawntime.calculation()
def calculation():
global timeset
print ('The count is: ', timeset)
if timeset <= 0:
enemymove.create()
else:
timeset -= 1
canvas.after(1000, spawntime.calculation)
#runs the main code
def main():
global root, canvas
root.title("")
canvas = Canvas(root, width= resX, height=resY, bg = "white")
canvas.pack()
Thread(target = spawntime.timer).start()
root.mainloop()
main()
I'm reasonably new to appending, so making multiple different appended sprites move all together is out of my range, and so I am unsure of how to get this to work.
I don't understand your code so I create own version. Now every enemy moves after random time. After random time I add new enemy
I keep enemies on list but I don't need this list.
import random
import tkinter as tk
# --- constants --- # UPPERCASE name
RES_X = 800
RES_Y = 600
# --- classes --- # CamelCase name
class Enemies(object):
def __init__(self, canvas):
# access to canvas
self.canvas = canvas
# started amount of enemies
self.amount = 5
# list for all enemies
self.enemies = list()
# create enemies
for _ in range(self.amount):
self.create_one_enemy()
def create_one_enemy(self):
radius = 12.5 # random
x = random.uniform(radius, RES_X-radius)
y = random.uniform(radius, RES_Y-radius)
oval = self.canvas.create_oval(x-radius, y-radius, x+radius, y+radius, fill="black", outline="black")
# one enemy
enemy = [x, y, radius, oval]
# apped to list - but I don't need this list
self.enemies.append(enemy)
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move_one_enemy, enemy)
def move_one_enemy(self, enemy):
#print('moving:', enemy)
# get old values
x, y, radius, oval = enemy
direction = random.randint(1,4)
if direction == 1: # up
y -= radius
elif direction == 2: # down
y += radius
elif direction == 3: # left
x -= radius
elif direction == 4: # right
x += radius
self.canvas.coords(oval, x-radius, y-radius, x+radius, y+radius)
# remember new values
enemy[0] = x
enemy[1] = y
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move_one_enemy, enemy)
# --- functions --- # lower_case name
def add_new_enemy():
enemies.create_one_enemy()
# add next enemy after random time
timer = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# --- main ---
root = tk.Tk()
root.title("")
canvas = tk.Canvas(root, width=RES_X, height=RES_Y, bg="white")
canvas.pack()
# create enemies and move it using `root.after`
enemies = Enemies(canvas)
# add new enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
root.mainloop()
EDIT: but rather I would create class Enemy for single enemy and then list enemies to keep all Enemy instances.
import random
import tkinter as tk
# --- constants --- # UPPERCASE name
RES_X = 800
RES_Y = 600
# --- classes --- # CamelCase name
class Enemy(object):
'''single enemy'''
def __init__(self, canvas):
# access to canvas
self.canvas = canvas
self.radius = 12.5 # random
self.color = random.choice( ('black', 'red', 'green', 'blue', 'yellow') )
self.x = random.uniform(self.radius, RES_X-self.radius)
self.y = random.uniform(self.radius, RES_Y-self.radius)
self.x1 = self.x-self.radius
self.y1 = self.y-self.radius
self.x2 = self.x+self.radius
self.y2 = self.y+self.radius
self.oval = self.canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=self.color, outline=self.color)
self.moving = True
self.start()
def start(self):
'''start moving'''
self.moving = True
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
def stop(self):
'''stop moving'''
self.moving = False
def move(self):
if self.moving: # to stop root.after
direction = random.randint(1,4)
if direction == 1: # up
self.y -= self.radius
self.y1 -= self.radius
self.y2 -= self.radius
elif direction == 2: # down
self.y += self.radius
self.y1 += self.radius
self.y2 += self.radius
elif direction == 3: # left
self.x -= self.radius
self.x1 -= self.radius
self.x2 -= self.radius
elif direction == 4: # right
self.x += self.radius
self.x1 += self.radius
self.x2 += self.radius
self.canvas.coords(self.oval, self.x1, self.y1, self.x2, self.y2)
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
# --- functions --- # lower_case name
def add_new_enemy():
enemies.append(Enemy(canvas))
# add next enemy after random time
timer = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# --- main ---
root = tk.Tk()
root.title("")
canvas = tk.Canvas(root, width=RES_X, height=RES_Y, bg="white")
canvas.pack()
# 5 enemies at the beginning
enemies = list()
for _ in range(5):
enemies.append(Enemy(canvas))
# add new enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
root.mainloop()
And now you can use list to stop/start enemies
for one_enemy in enemies:
one_enemy.stop()
or check some information
for one_enemy in enemies:
print("x:", one_enemy.x)
print("y:", one_enemy.y)
btw: and then you can create EnemiesGroup class
EDIT: EnemiesGroup and buttons to control group
import random
import tkinter as tk
# --- constants --- # UPPERCASE name
RES_X = 800
RES_Y = 600
# --- classes --- # CamelCase name
class Enemy(object):
'''single enemy'''
def __init__(self, canvas):
# access to canvas
self.canvas = canvas
self.radius = 12.5 # random
self.color = random.choice( ('black', 'red', 'green', 'blue', 'yellow') )
self.x = random.uniform(self.radius, RES_X-self.radius)
self.y = random.uniform(self.radius, RES_Y-self.radius)
self.x1 = self.x-self.radius
self.y1 = self.y-self.radius
self.x2 = self.x+self.radius
self.y2 = self.y+self.radius
self.oval = self.canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=self.color, outline=self.color)
self.moving = True
self.start()
def start(self):
'''start moving'''
self.moving = True
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
def stop(self):
'''stop moving'''
self.moving = False
def move(self):
if self.moving: # to stop root.after
direction = random.randint(1,4)
if direction == 1: # up
self.y -= self.radius
self.y1 -= self.radius
self.y2 -= self.radius
elif direction == 2: # down
self.y += self.radius
self.y1 += self.radius
self.y2 += self.radius
elif direction == 3: # left
self.x -= self.radius
self.x1 -= self.radius
self.x2 -= self.radius
elif direction == 4: # right
self.x += self.radius
self.x1 += self.radius
self.x2 += self.radius
self.canvas.coords(self.oval, self.x1, self.y1, self.x2, self.y2)
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
class EnemiesGroup(object):
def __init__(self, canvas):
self.canvas = canvas
self.enemies = list()
self.moving = True
def add_new_enemy(self):
# can be only 5 enemies
if len(self.enemies) < 5:
e = Enemy(self.canvas)
# stop new enemy if all enemies are stoped
e.moving = self.moving
self.enemies.append(e)
else:
print("You have 5 enemies - I can't add more.")
def stop_all_enemies(self):
for e in self.enemies:
e.stop()
# all enemies are stoped
self.moving = False
def start_all_enemies(self):
for e in self.enemies:
e.start()
# all enemies are moving
self.moving = True
# --- functions --- # lower_case name
def add_new_enemy():
enemies_group.add_new_enemy()
# add next enemy after random time
timer = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# --- main ---
root = tk.Tk()
root.title("")
canvas = tk.Canvas(root, width=RES_X, height=RES_Y, bg="white")
canvas.pack()
# enemies
enemies_group = EnemiesGroup(canvas)
for _ in range(5):
enemies_group.add_new_enemy()
# add new enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# buttons to control all enemies
button_stop = tk.Button(root, text='STOP', command=enemies_group.stop_all_enemies)
button_stop.pack()
button_start = tk.Button(root, text='START', command=enemies_group.start_all_enemies)
button_start.pack()
button_add = tk.Button(root, text='ADD NEW ENEMY', command=enemies_group.add_new_enemy)
button_add.pack()
root.mainloop()
EDIT: removing enemy by clicking oval.
In add_new_enemy I bind to oval event <Button-1> and function clicked (with enemy object).
Function clicked removes oval from canvas and removes enemy from enemies group.
class EnemiesGroup(object):
# ... other functions ...
def clicked(self, event, enemy):
print('clicked:', enemy),
# remove oval from canvas
self.canvas.delete(enemy.oval)
# remove enemy from list
self.enemies.remove(enemy)
# create new enemy after 10s
root.after(10000, self.add_new_enemy)
def add_new_enemy(self):
# can be only 5 enemies
if len(self.enemies) < 5:
print('create new enemy')
e = Enemy(self.canvas)
# stop new enemy if all enemies are stoped
e.moving = self.moving
# bind mouse button to enemy
self.canvas.tag_bind(e.oval, '<Button-1>', lambda event:self.clicked(event, e))
self.enemies.append(e)
else:
print("You have 5 enemies - I can't add more.")
# ... other functions ...

Categories