How to ignore some Tkinter collisions? - python

I've got a grid on my canvas, and in some cells, circles. My goal is to move circles around the grid but not over another circle. So when I'm moving the circle I check if there's something on its way, if so, cancelling the movement. Firstn I tried to check collision at the end of the movement, but I couldn't get it to work. So I'm trying to check while moving, but I realized that canvas lines are also objects, so there are collisions.
Is there any "simple" way to ignore these lines collisions but not the circles ones ?
class Deplacement:
def __init__(self, canvas, event):
self.x = event.x
self.y = event.y
self.canvas = canvas
self.obj = "à déterminer"
self.obj2 = "à déterminer"
self.couleur = "à déterminer"
self.couleur2 = "à déterminer"
self.collision = "à déterminer"
canvas.bind("<ButtonPress-1>", self.StartMove)
canvas.bind("B1-Motion", self.OnMotion)
canvas.bind("<ButtonRelease-1>", self.StopMove)
def StartMove(self, event): #Getting the object I want to move
self.x = event.x
self.y = event.y
self.obj = self.canvas.find_overlapping(self.x - 15, self.y - 15, self.x + 15, self.y + 15)
def OnMotion(self, event): #This is where I struggle
#Trying to get the second circle if it exists
self.obj2 = self.canvas.find_overlapping(event.x - 15, event.y - 15, event.x + 15, event.y + 15)
if not self.obj2:
self.collision = False
else:
self.collision = True
def StopMove(self, event): #This works fine, in context, checking items colors to move them or not
#And objects must be moved by one axis
self.couleur = self.canvas.itemcget(self.obj, "fill")
if not self.collision:
if self.couleur == 'black' or self.couleur == 'white' or self.couleur == 'red':
if event.x > self.x and self.y - 10 <= event.y <= self.y + 10:
self.move(event, self.obj)
elif event.x < self.x and self.y - 10 <= event.y <= self.y + 10:
self.move(event, self.obj)
elif event.y > self.y and self.x - 10 <= event.x <= self.x + 10:
self.move(event, self.obj)
elif event.y < self.y and self.x - 10 <= event.x <= self.x + 10:
self.move(event, self.obj)
def move(self, event, obj): #Finally moving objects, works nicely aswell
self.x = (event.x - 20) // 50 + 1
self.y = (event.y - 20) // 50 + 1
self.obj = obj
if event.x < 20:
self.x = 1
self.canvas.coords(self.obj, self.x * 50 - 25, self.y * 50 - 25, self.x * 50 + 15, self.y * 50 + 15)
elif event.y < 20:
self.y = 1
self.canvas.coords(self.obj, self.x * 50 - 25, self.y * 50 - 25, self.x * 50 + 15, self.y * 50 + 15)
elif event.x > 470:
self.x = 9
self.canvas.coords(self.obj, self.x * 50 - 25, self.y * 50 - 25, self.x * 50 + 15, self.y * 50 + 15)
elif event.y > 470:
self.y = 9
self.canvas.coords(self.obj, self.x * 50 - 25, self.y * 50 - 25, self.x * 50 + 15, self.y * 50 + 15)
else:
self.canvas.coords(self.obj, self.x * 50 - 25, self.y * 50 - 25, self.x * 50 + 15, self.y * 50 + 15)
PS: Ignore French words.

I assume that the variable named canvas is an instance of Tkinters' Canvas class. You didn't show the code that populates the canvas, but I assume you populate it with method calls such as canvas.create_line() or canvas.create_oval() and so on. Each of these methods returns an integer which is the ID of the created shape. What you must do is to store the IDs of shapes that you want to ignore, in other words, create a set of shape IDs that should not constitute a collision. Let's say you put those into a set named ignore_these.
The method find_overlapping returns a tuple of shapes that overlap with the specified rectangle. Suppose we call this returned value possible_collisions. So to tell if you have a collision you just need a line of code something like:
if any(s not in ignore_these for s in possible_collisions):
# handle the collision
else:
# go ahead and make the move
This is a two-step process (create the set of non-colliding shapes and then use it to analyze the results of find_overlapping). You asked for a simple way of doing this but I doubt if there's a way to avoid these two steps; Tkinter has at least given you the hooks you need to write the necessary logic.
Manual reference: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas

When you add items to the canvas, the methods used return a unique id for these objects (an integer). You must gather these ids in a collection, with an additional collection for the ids of the items that do collide. (you could segregate the items that do not collide, but you would have to filter them every time you run the collision detection part)
This is pseudo-code:
def create_canvas_objects(self):
id = canvas.create_object()
self.all_canvas_objects_ids.append(id)
if object_collides:
self.colliding_objects.append(id)
def check_collisions(self):
for object in self.colliding_objects:
check the collision
This way, you filter the colliding vs. the non colliding objects only once at their creation, instead of every frame.

Related

tkinter breakout game beginner problems

I am trying to build a Breakout game for python tkinter with different levels. I don't quite understand the self and __init__ functions in this code.
Is there a way to create the game without those functions or replacing them with simpler functions if it's possible? Also, I don't quite understand the + self.radius parts in the code as well.
import tkinter as tk
def new_window():
root1 = tk.Tk()
root1.title('Jeu')
game = Game(root1)
class GameObject(object):
def __init__(self, canvas, item):
self.canvas = canvas
self.item = item
def get_position(self):
return self.canvas.coords(self.item)
def move(self, x, y):
self.canvas.move(self.item, x, y)
def delete(self):
self.canvas.delete(self.item)
class Ball(GameObject):
def __init__(self, canvas, x, y):
self.radius = 10
self.direction = [1, -1]
# increase the below value to increase the speed of ball
self.speed = 5
item = canvas.create_oval(x - self.radius, y - self.radius,
x + self.radius, y + self.radius,
fill='white')
super(Ball, self).__init__(canvas, item)
def update(self):
coords = self.get_position()
width = self.canvas.winfo_width()
if coords[0] <= 0 or coords[2] >= width:
self.direction[0] *= -1
if coords[1] <= 0:
self.direction[1] *= -1
x = self.direction[0] * self.speed
y = self.direction[1] * self.speed
self.move(x, y)
def collide(self, game_objects):
coords = self.get_position()
x = (coords[0] + coords[2]) * 0.5
if len(game_objects) > 1:
self.direction[1] *= -1
elif len(game_objects) == 1:
game_object = game_objects[0]
coords = game_object.get_position()
if x > coords[2]:
self.direction[0] = 1
elif x < coords[0]:
self.direction[0] = -1
else:
self.direction[1] *= -1
for game_object in game_objects:
if isinstance(game_object, Brick):
game_object.hit()
class Paddle(GameObject):
def __init__(self, canvas, x, y):
self.width = 80
self.height = 10
self.ball = None
item = canvas.create_rectangle(x - self.width / 2,
y - self.height / 2,
x + self.width / 2,
y + self.height / 2,
fill='#FFB643')
super(Paddle, self).__init__(canvas, item)
def set_ball(self, ball):
self.ball = ball
def move(self, offset):
coords = self.get_position()
width = self.canvas.winfo_width()
if coords[0] + offset >= 0 and coords[2] + offset <= width:
super(Paddle, self).move(offset, 0)
if self.ball is not None:
self.ball.move(offset, 0)
class Brick(GameObject):
COLORS = {1: '#4535AA', 2: '#ED639E', 3: '#8FE1A2'}
def __init__(self, canvas, x, y, hits):
self.width = 75
self.height = 20
self.hits = hits
color = Brick.COLORS[hits]
item = canvas.create_rectangle(x - self.width / 2,
y - self.height / 2,
x + self.width / 2,
y + self.height / 2,
fill=color, tags='brick')
super(Brick, self).__init__(canvas, item)
def hit(self):
self.hits -= 1
if self.hits == 0:
self.delete()
else:
self.canvas.itemconfig(self.item,
fill=Brick.COLORS[self.hits])
class Game(tk.Frame):
def __init__(self, master):
super(Game, self).__init__(master)
self.lives = 3
self.width = 610
self.height = 400
self.canvas = tk.Canvas(self, bg='#D6D1F5',
width=self.width,
height=self.height, )
self.canvas.pack()
self.pack()
self.items = {}
self.ball = None
self.paddle = Paddle(self.canvas, self.width / 2, 326)
self.items[self.paddle.item] = self.paddle
# adding brick with different hit capacities - 3,2 and 1
for x in range(5, self.width - 5, 75):
self.add_brick(x + 37.5, 50, 3)
self.add_brick(x + 37.5, 70, 2)
self.add_brick(x + 37.5, 90, 1)
self.hud = None
self.setup_game()
self.canvas.focus_set()
self.canvas.bind('<Left>',
lambda _: self.paddle.move(-10))
self.canvas.bind('<Right>',
lambda _: self.paddle.move(10))
def setup_game(self):
self.add_ball()
self.update_lives_text()
self.text = self.draw_text(300, 200,
'Press Space to start')
self.canvas.bind('<space>', lambda _: self.start_game())
def add_ball(self):
if self.ball is not None:
self.ball.delete()
paddle_coords = self.paddle.get_position()
x = (paddle_coords[0] + paddle_coords[2]) * 0.5
self.ball = Ball(self.canvas, x, 310)
self.paddle.set_ball(self.ball)
def add_brick(self, x, y, hits):
brick = Brick(self.canvas, x, y, hits)
self.items[brick.item] = brick
def draw_text(self, x, y, text, size='40'):
font = ('Forte', size)
return self.canvas.create_text(x, y, text=text,
font=font)
def update_lives_text(self):
text = 'Lives: %s' % self.lives
if self.hud is None:
self.hud = self.draw_text(50, 20, text, 15)
else:
self.canvas.itemconfig(self.hud, text=text)
def start_game(self):
self.canvas.unbind('<space>')
self.canvas.delete(self.text)
self.paddle.ball = None
self.game_loop()
def game_loop(self):
self.check_collisions()
num_bricks = len(self.canvas.find_withtag('brick'))
if num_bricks == 0:
self.ball.speed = None
self.draw_text(300, 200, 'You win! You the Breaker of Bricks.')
elif self.ball.get_position()[3] >= self.height:
self.ball.speed = None
self.lives -= 1
if self.lives < 0:
self.draw_text(300, 200, 'You Lose! Game Over!')
else:
self.after(1000, self.setup_game)
else:
self.ball.update()
self.after(50, self.game_loop)
def check_collisions(self):
ball_coords = self.ball.get_position()
items = self.canvas.find_overlapping(*ball_coords)
objects = [self.items[x] for x in items if x in self.items]
self.ball.collide(objects)
game.mainloop()
root = tk.Tk()
root.title('Jeu')
game = Game(root)
btn1= tk.Button(root, text='Click me', bd= '5',command=new_window)
Is there a way to create the game without those functions or replacing them with simpler functions if it's possible?
As a proof of concept below a much simpler code of the game for beginners rewritten to get rid of classes as an example that it is possible to write the game without them. You can compare the rewritten code with the original code (link is given in a comment at the beginning of code) to see how it can be done and then rewrite yourself a more complex code:
# https://codingshiksha.com/python/python-3-tkinter-2d-brick-breaker-breakout-game-in-gui-desktop-app-full-project-for-beginners/
from tkinter import Tk, Canvas
import random
import time
tk = Tk()
tk.title("Game")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas_height = 400
canvas_width = 500
canvas = Canvas(tk, width=canvas_width, height=canvas_height, bd=0, highlightthickness=0)
canvas.pack()
tk.update()
ball_id = canvas.create_oval(10, 10, 25, 25, fill='red')
canvas.move(ball_id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
ball_x = starts[0]
ball_y = -3
paddle_id = canvas.create_rectangle(0, 0, 100, 10, fill='blue')
canvas.move(paddle_id, 200, 300)
paddle_x = 0
def draw_ball():
global ball_x, ball_y
canvas.move(ball_id, ball_x, ball_y)
pos = canvas.coords(ball_id)
if pos[1] <= 0:
ball_y = 3
if pos[3] >= canvas_height:
ball_y = -3
if hit_paddle(pos) == True:
ball_y = -3
if pos[0] <= 0:
ball_x = 3
if pos[2] >= canvas_width:
ball_x = -3
def hit_paddle(pos):
paddle_pos = canvas.coords(paddle_id)
if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
return True
return False
def turn_left(evt):
global paddle_x
paddle_x = -2
def turn_right(evt):
global paddle_x
paddle_x = 2
def draw_paddle():
global paddle_x
canvas.move(paddle_id, paddle_x, 0)
pos = canvas.coords(paddle_id)
if pos[0] <= 0:
paddle_x = 0
elif pos[2] >= canvas_width:
paddle_x = 0
canvas.bind_all('<KeyPress-Left>' , turn_left)
canvas.bind_all('<KeyPress-Right>', turn_right)
while True:
draw_ball()
draw_paddle()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
By the way: not always usage of classes makes sense if there is a simpler and sometimes even more intuitive way of achieving the same result.
The init function is a part of a class that is run every time you create an instance of that class. Self makes the variable an attribute to the class, or that it can be accessed in other parts of your code by, for example ball = Ball() print(ball.radius). There is more information here at the documentation: https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces . If you are using object-oriented programming, there aren't any alternatives to self and init. The only way not to use them would be to not use classes in your code.
The self.radius part of the code is creating a variable representing the size of the ball. This link describes how the different points you set creates the shape of the oval: https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/create_oval.html. The short answer is that self.radius creates the size by spacing out the two points of the oval.

Move object function has no effect even after calling: Tkinter

So, yeah I'm a beginner to Tkinter Canvas Games and it's being too difficult for me to code than I thought.
Okk, let's come to the chase, below is my code:
import time
from tkinter import *
from numpy import random
class Paddle:
def __init__(self, Main_Frame):
Main_Frame.title("Paddle")
Main_Frame.geometry("300x300+630+150")
self.Random_X = random.randint(270)
self.Random_Y = random.randint(120)
self.x = 3
self.y = 3
self.Y_Position = 288
self.Can = Canvas(Main_Frame, height = 300, width = 300)
self.Can.pack()
self.paddle = self.Can.create_rectangle(0, 288, 90, 300, fill = "Aqua", outline = "Aqua")
self.ball = self.Can.create_oval(self.Random_X, self.Random_Y, self.Random_X + 20, self.Random_Y + 20, outline = "Red", fill = "Red")
self.Can.bind("<Motion>", self.Move_Paddle)
self.Move_Ball()
def Move_Ball(self):
Ball_x1, Ball_y1, Ball_x2, Ball_y2 = self.Can.coords(self.ball)
if Ball_x2 > 300:
self.x = -self.x
if Ball_y2 > 300:
self.y = -self.y
if Ball_x1 < 0:
self.x = -self.x
if Ball_y2 < 10:
self.y = -self.y
self.Can.moveto(self.ball, self.x, self.y)
Window.after(1, self.Move_Ball)
def Move_Paddle(self, event):
self.X_Position = event.x
self.Can.moveto(self.paddle, self.X_Position, self.Y_Position)
Window = Tk()
Class = Paddle(Window)
Window.mainloop()
So, my problem here is that even after calling the Move_Ball() function, it had no effect on the ball, (okk there is no error occurring though) when I tried to call the function under Move_Paddle() function (before adding the Window.after(1, self.Move_Ball) line), it worked well when I moved the mouse around the Canvas, well, I want it to automatically activate when the Paddle class is called.
I was trying to check the problem in the code by placing the function call in Move_Paddel() (with the Window.after(1, self.Move_Ball) line as in the code), it increased the trouble and insanely increased the speed of the ball.
If anyone could help me with these couple of problems, it would be highly appreciated.
Thanks in advance!
Use move() instead of moveto() method. move() method moves each of the items given by tagOrId in the canvas coordinate space by adding X amount to the x-coordinate of each point associated with the item and y amount to the y-coordinate of each point associated with the item.
Then you may increase the delay in the after() method.
Here is the working code:
import time
from tkinter import *
from numpy import random
class Paddle:
def __init__(self, Main_Frame):
Main_Frame.title("Paddle")
Main_Frame.geometry("300x300+630+150")
self.Random_X = random.randint(270)
self.Random_Y = random.randint(120)
self.x = 3
self.y = 3
self.Y_Position = 288
self.Can = Canvas(Main_Frame, height = 300, width = 300)
self.Can.pack()
self.paddle = self.Can.create_rectangle(0, 288, 90, 300, fill = "Aqua", outline = "Aqua")
self.ball = self.Can.create_oval(self.Random_X, self.Random_Y, self.Random_X + 20, self.Random_Y + 20, outline = "Red", fill = "Red")
self.Can.bind("<Motion>", self.Move_Paddle)
self.Move_Ball()
def Move_Ball(self):
Ball_x1, Ball_y1, Ball_x2, Ball_y2 = self.Can.coords(self.ball)
if Ball_x2 > 300:
self.x = -self.x
if Ball_y2 > 300:
self.y = -self.y
if Ball_x1 < 0:
self.x = -self.x
if Ball_y2 < 10:
self.y = -self.y
self.Can.move(self.ball, self.x, self.y)
Window.after(50 ,self.Move_Ball)
def Move_Paddle(self, event):
self.X_Position = event.x
self.Can.moveto(self.paddle, self.X_Position, self.Y_Position)
Window = Tk()
Class = Paddle(Window)
Window.mainloop()
I rewrote your code and now I think it works as you wished. Red ball slowly goes down:
import time
from tkinter import *
from numpy import random
class Paddle:
def __init__(self, main_frame):
self.root = main_frame
random.seed(int(time.time()))
self.x = random.randint(270)
self.y = random.randint(120)
self.Y_Position = 288
self.Can = Canvas(self.root, height=300, width=300)
self.Can.pack()
self.paddle = self.Can.create_rectangle(0, 288, 90, 300, fill="Aqua", outline="Aqua")
self.ball = self.Can.create_oval(self.x, self.y, self.x + 20, self.y + 20, outline="Red", fill="Red")
self.Can.bind("<Motion>", self.move_paddle)
self.move_ball()
def move_ball(self):
Ball_x1, Ball_y1, Ball_x2, Ball_y2 = self.Can.coords(self.ball)
# if Ball_x2 > 300:
# self.x = -self.x
# if Ball_y2 > 300:
# self.y = -self.y
# if Ball_x2 < 0:
# self.x = 300
if Ball_y2 > 300:
self.y = 0
self.y += 0.1
self.Can.moveto(self.ball, self.x, self.y)
self.root.after(1, self.move_ball)
def move_paddle(self, event):
self.X_Position = event.x
self.Can.moveto(self.paddle, self.X_Position, self.Y_Position)
root = Tk()
root.title("Paddle")
root.geometry("300x300+630+150")
Class = Paddle(root)
root.mainloop()
And please read about PEP8
You should use move() instead of moveto() inside Move_Ball() and after(1, ...) is too frequent so the ball will be moved very fast. Try after(50, ...) instead:
def Move_Ball(self):
...
self.Can.move(self.ball, self.x, self.y)
Window.after(50, self.Move_Ball)

Tkinter won't recognize key presses

I'm trying to write a program so that a car is drawn moving along the screen, and the up and down arrow keys can change the speed of the car. However, when I press either key, neither command is called. I've tried changing the bind_all and focus lines to self.canvas but that hasn't worked either. How can I fix this?
class racingCar:
def __init__(self):
window = Tk()
window.title("Racing Car")
self.cWidth = 500
self.cHeight = 200
self.canvas = Canvas(window, width = self.cWidth, height = self.cHeight, bg = "white")
self.canvas.pack()
self.x = 0
self.y = self.cHeight
self.drawCar(self.x, self.y)
self.dx = 5
self.sleepTime = 50
self.animate()
window.bind_all("<Up>", self.increaseSpeed)
window.bind_all("<Down>", self.decreaseSpeed)
window.focus()
window.mainloop()
def drawCar(self, x, y):
self.canvas.create_rectangle(x, y - 20, x + 50, y - 10, fill = "yellow", tags = "car")
self.canvas.create_oval(x + 10, y - 10, x + 20, y, fill = "black", tags = "car")
self.canvas.create_oval(x + 30, y - 10, x + 40, y, fill = "black", tags = "car")
self.canvas.create_polygon([x + 10, y - 20, x + 20, y - 30, x + 30, y - 30, x + 40, y - 20], fill = "blue", tags = "car")
def increaseSpeed(self, event):
print("pressed up")
self.dx += 2.5
def decreaseSpeed(self, event):
print("pressed down")
if self.dx > 0:
self.dx -= 2.5
def animate(self):
while not self.dx == 0:
self.canvas.move("car", self.dx, 0)
self.canvas.after(self.sleepTime)
self.canvas.update()
if self.x < self.cWidth + 10:
self.x += self.dx
else:
self.x = -60
self.canvas.delete("car")
self.drawCar(self.x, self.y)
racingCar()
You should not use while or sleep ever in the same thread tkinter is running in.
In this case how you are using after() it is functioning exactly like sleep.
Because tkinter is single threaded those methods will block the mainloop and nothing else can happen until the loop is ended or sleep has ended. The reason your canvas is updated is because you are calling update in the while loop but your events are still being blocked.
To fix this use after() as it is meant for this kind of loop in tkinter.
I have updated your code to use after() and cleaned it up a bit to more closely follow PEP8 style guide. let me know if you have any questions.
import tkinter as tk
class RacingCar(tk.Tk):
def __init__(self):
super().__init__()
self.title("Racing Car")
self.c_width = 500
self.c_height = 200
self.canvas = tk.Canvas(self, width=self.c_width, height=self.c_height, bg="white")
self.canvas.pack()
self.x = 0
self.y = self.c_height
self.draw_car(self.x, self.y)
self.dx = 5
self.after_time = 50
self.animate()
self.bind("<Up>", self.increase_speed)
self.bind("<Down>", self.decrease_speed)
def draw_car(self, x, y):
self.canvas.create_rectangle(x, y - 20, x + 50, y - 10, fill="yellow", tags="car")
self.canvas.create_oval(x + 10, y - 10, x + 20, y, fill="black", tags="car")
self.canvas.create_oval(x + 30, y - 10, x + 40, y, fill="black", tags="car")
self.canvas.create_polygon([x + 10, y - 20, x + 20, y - 30, x + 30, y - 30, x + 40, y - 20],
fill="blue", tags="car")
def increase_speed(self, _):
print("pressed up")
self.dx += 2.5
def decrease_speed(self, _):
print("pressed down")
if self.dx > 0:
self.dx -= 2.5
def animate(self):
if self.dx != 0:
self.canvas.move("car", self.dx, 0)
if self.x < self.c_width + 10:
self.x += self.dx
else:
self.x = -60
self.canvas.delete("car")
self.draw_car(self.x, self.y)
self.after(self.after_time, self.animate)
RacingCar().mainloop()

Moving a Tkinter widget to a specific coordinates and changing the text of a text widget

I'm trying to make a game like snake, where when you catch/eat the apple and the apple moves to a different spot in tkinter.
But I want to place the "apple", or in this case, a blue triangle, to a random place on the canvas,but the only command I can find is canvas.move(), which moves the widget a certain number of pixels in any direction. This can't fulfill what I need(I think, maybe there is a way around it?). Is there a way to place the blue triangle randomly on the canvas?
from Tkinter import *
import random
import time
class Ball:
def __init__(self, canvas,square):
self.square = square
self.canvas = canvas
self.id = canvas.create_oval(10,10,25,25,fill='red')
self.canvas.move(self.id,245,100)
self.text = self.canvas.create_text(10, 10, text='GAME OVER', font=('Courier', 80))
self.canvas.move(self.text, -7000, -7000)
starts = [-3,-2,-1,1,2,3]
self.x = random.choice(starts)
self.y = -30
def draw(self):
self.canvas.move(self.id,self.x,self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 4
if pos[3] >= self.canvas.winfo_height():
self.y = -4
if self.hit_square(pos) == True:
self.canvas.move(self.text,245,100)
time.sleep(2)
tk.destroy()
if pos[0] <= 0:
self.x = 4
if pos[2] >= self.canvas.winfo_width():
self.x = -4
def hit_square(self, pos):
square_pos = self.canvas.coords(self.square.id)
if pos[2] >= square_pos[0] and pos[0] <= square_pos[2]:
if pos[3] >= square_pos[1] and pos[3] <= square_pos[3]:
return True
return False
def stay(self):
self.x = 0
self.y = 0
class Square:
def __init__(self,canvas):
self.canvas = canvas
self.id = canvas.create_rectangle(15, 15, 30, 30,fill='green')
self.x = 0
self.y = 0
self.canvas.move(self.id, 200, 250)
self.canvas.bind_all('<KeyPress-Left>',self.left)
self.canvas.bind_all('<KeyPress-Right>', self.right)
self.canvas.bind_all('<KeyPress-Up>', self.up)
self.canvas.bind_all('<KeyPress-Down>', self.down)
def draw(self):
self.canvas.move(self.id,self.x,self.y)
pos = self.canvas.coords(self.id)
if pos[0] <= 0:
self.x = 0
elif pos[2] >= self.canvas.winfo_width():
self.x = 0
if pos[1] <= 0:
self.y = 0
elif pos[3] >= self.canvas.winfo_height():
self.y = 0
def left(self, evt):
self.x = -2
self.y = 0
def right(self, evt):
self.x = 2
self.y = 0
def up(self, evt):
self.y = -2
self.x = 0
def down(self, evt):
self.y = 2
self.x = 0
class Triangle:
def __init__(self,canvas,square):
self.canvas = canvas
self.square = square
self.id = self.canvas.create_polygon(26.5,10,20,25,35,25,fill='blue')
self.canvas.move(self.id,random.randint(10,450),random.randint(10,380))
self.score = 0
def draw_score(self):
self.score_show = self.canvas.create_text(450, 20, text='score:' + str(self.score), font=('Arial', 20))
def hit_square(self):
pos = self.canvas.coords(self.id)
square_pos = self.canvas.coords(self.square.id)
if pos[2] >= square_pos[0] and pos[0] <= square_pos[2]:
if pos[3] >= square_pos[1] and pos[3] <= square_pos[3]:
self.teleport(pos)
def teleport(self, pos):
x = self.canvas.winfo_width()-pos[0]-10
y = self.canvas.winfo_height() - pos[1]-10
self.score += 1
self.canvas.move(self.id,)
tk = Tk()
tk.title("Run from the ball!")
tk.resizable(0,0)
tk.wm_attributes('-topmost',1)
canvas = Canvas(tk, width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
tk.update()
square = Square(canvas)
ball = Ball(canvas, square)
ball1 = Ball(canvas, square)
ball2 = Ball(canvas, square)
ball3 = Ball(canvas, square)
ball4 = Ball(canvas, square)
triangle = Triangle(canvas, square)
x = 0
while x < float('inf'):
ball.draw()
triangle.draw_score()
triangle.hit_square()
if x >= 10:
ball1.draw()
if x >= 20:
ball2.draw()
if x >= 30:
ball3.draw()
if x >= 40:
ball4.draw()
square.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
x += 0.01
Yes, I know that the score function is messed up, and that the one overlaps the zero. Could you guys help me with that too?
Is there a way to place the blue triangle randomly on the canvas?
Yes. the coords method can get you the current coordinates, but it also lets you change the coordinates to whatever you want.
self.canvas.coords(self.id, 36.5, 20, 30, 35, 45, 35)

Pong with Python(Tkinter) paddle not working

So I'm new to GUI and Tkinter. I'm working on a one paddle pong game using Tkinter and I need the paddle to act like a paddle and bounce the disk off when the disk hits the paddle from the right or the left. The current code I have does bounce the disk of the paddles location on the x axis but it doesn't let the disk get past the line_x. Am I thinking in the right direction? or am I way off? It would be awesome if someone can fix my code so it works. This is probably very easy for someone that's been working with GUI's a while but I'm stomped. Please help.
from tkinter import *
import random
class ControlAnimation:
def __init__(self):
my_window = Tk() # create a window
my_window.title("Control Animation Demo")
self.width = 400
self.height = 200
self.line_x = 350
self.line_top = 75
self.line_bot = 125
self.paddle_width = 10
self.dy = 5
self.sleep_time = 50
self.is_stopped = False
self.my_canvas = Canvas(my_window, bg = 'white', \
width = self.width, height = self.height)
self.my_canvas.pack()
frm_control = Frame(my_window) # for comand buttons below canvas
frm_control.pack()
btn_stop = Button(frm_control, text = 'Stop', \
command = self.stop)
btn_stop.pack(side = LEFT)
btn_resume = Button(frm_control, text = 'Resume', \
command = self.resume)
btn_resume.pack(side = LEFT)
btn_faster = Button(frm_control, text = 'Faster', \
command = self.faster)
btn_faster.pack(side = LEFT)
btn_slower = Button(frm_control, text = 'Slower', \
command = self.slower)
btn_slower.pack(side = LEFT)
self.radius = 20
self.x = self.radius # just to start; y is at canvas center
self.y = self.height/2
# (x, y) is center of disk for this program, but ...
# recall: x1,y1 and x2,y2 form a bounding box for disk
self.my_canvas.create_oval(\
self.x - self.radius, self.height/2 + self.radius,\
self.x + self.radius, self.height/2 - self.radius,\
fill = "red", tags = "disk")
self.my_canvas.create_line(self.line_x, self.line_top, \
self.line_x, self.line_bot, \
width = self.paddle_width, fill = "blue", tags = "paddle")
self.my_canvas.bind("<KeyPress-Up>", self.move_paddle)
self.my_canvas.bind("<KeyPress-Down>", self.move_paddle)
self.my_canvas.bind("<B1-Motion>", self.left_click_paddle)
self.my_canvas.bind("<B3-Motion>", self.right_click_paddle)
self.animate()
self.my_canvas.focus_set()
my_window.mainloop()
def stop(self):
self.is_stopped = True
def resume(self):
self.is_stopped = False
self.animate()
def faster(self):
if self.sleep_time > 5:
self.sleep_time -= 15
def slower(self):
self.sleep_time += 15
def animate(self):
dx = 3
dy = 2
while not self.is_stopped :
self.my_canvas.move("disk", dx, dy) # move right
self.my_canvas.after(self.sleep_time) # sleep for a few ms
# redraw/update the canvas w/ new oval position
self.my_canvas.update()
# increment x to set up for next re-draw
r = random.randint(-1, 1)
self.x += dx # moves the disk
if self.x + self.radius > self.width: # hit right boundary
dx = -dx + r # add randomness
elif self.x - self.radius <= 0: # hit left boundary
dx = -dx + r # add randomness
elif self.x + self.radius > self.line_x and self.x + self.radius <= self.line_top:
dx = -dx + r
#elif self.x - self.radius <= self.line_x:
#dx = -dx + r
# increment y to set up for next re-draw
self.y += dy
if self.y + self.radius > self.height: # hit bottom boundary
dy = -dy
elif self.y - self.radius <= 0: # hit top boundary
dy = -dy
def left_click_paddle(self, event):
print(" clicked at =", event.x, event.y)
print("-"*30)
self.move_paddle( -self.dy)
def right_click_paddle(self, event):
print(" clicked at =", event.x, event.y)
print("-"*30)
self.move_paddle( self.dy)
def move_paddle(self, increment):
self.line_top += increment
self.line_bot += increment
self.my_canvas.delete("paddle")
self.my_canvas.create_line(self.line_x, self.line_top, \
self.line_x, self.line_bot, \
width = 10, fill = "blue", tags = "paddle")
ControlAnimation() # create instance of the GUI
In your movement loop, the logic to check whether to trigger a direction change in x is not complete. It should be something like this:
# new disk x, y positions
self.x += dx
self.y += dy
# Change "dx" sign if the ball hit something horizontally
if self.x + self.radius > self.width-1:
# ball hit right frame boundary
dx = -dx + r
elif self.x - self.radius <= 1:
# ball hit left frame boundary
dx = -dx + r
elif ( self.line_x <= self.x+self.radius <= self.line_x + 2*dx
and self.line_top <= self.y <= self.line_bot ):
# ball hit paddle from the left
dx = -dx + r
elif ( self.line_x + 2*dx <= self.x-self.radius <= self.line_x
and self.line_top <= self.y <= self.line_bot ):
# ball hit paddle from the right
dx = -dx + r

Categories

Resources