How do I have an object rebound off the canvas border? - python

I am using the canvas widget from tkinter to create an ellipse and have it move around in the canvas.
However when the ellipse comes in contact with the border it gets stuck to wall instead of bouncing off.
I'm struggling with debugging the code, thanks in advance!
from tkinter import *
from time import *
import numpy as np
root = Tk()
root.wm_title("Bouncing Ball")
canvas = Canvas(root, width=400, height=400, bg="black")
canvas.grid()
size=10
x = 50
y = 50
myBall = canvas.create_oval(x-size, y-size, x+size, y+size, fill = "red")
while True:
root.update()
root.after(50)
dx = 5
dy = 0
#separating x and y cooridnates from tuple of canvas.coords
x = canvas.coords(myBall)[0]+10
y = canvas.coords(myBall)[1]+10
coordinates = np.array([x, y], dtype = int)
#Checking boundaries
if coordinates[0]-size <= 0:
dx = -1*dx
if coordinates[0]+size >= 400:
dx = -1*dx
if coordinates[1]-size <= 0:
dy = -1*dy
if coordinates[1]+size >= 400:
dy = -1*dy
print(coordinates) #Used to see what coordinates are doing
canvas.move(myBall, dx, dy) #Move ball by dx and dy

Here is a simple way to organize your bouncing ball program, and get you started with GUI programming:
While loops don't work well with a GUI mainloop; it is also not necessary to call update, the mainloop handles that.
Repeated actions are best handles with root.after.
I extracted the bounce logic inside a function bounce that calls itself using root.after. You will see that I simplified the logic.
I also parametrized the canvas size.
The initial speed components dx and dy are randomly chosen from a list of possible values so the game is not too boring.
Here is how it looks:
import tkinter as tk # <-- avoid star imports
import numpy as np
import random
WIDTH = 400
HEIGHT = 400
initial_speeds = [-6, -5, -4, 4, 5, 6]
dx, dy = 0, 0
while dx == dy:
dx, dy = random.choice(initial_speeds), random.choice(initial_speeds)
def bounce():
global dx, dy
x0, y0, x1, y1 = canvas.coords(my_ball)
if x0 <= 0 or x1 >= WIDTH: # compare to left of ball bounding box on the left wall, and to the right on the right wall
dx = -dx
if y0 <= 0 or y1 >= HEIGHT: # same for top and bottom walls
dy = -dy
canvas.move(my_ball, dx, dy)
root.after(50, bounce)
if __name__ == '__main__':
root = tk.Tk()
root.wm_title("Bouncing Ball")
canvas = tk.Canvas(root, width=400, height=400, bg="black")
canvas.pack(expand=True, fill=tk.BOTH)
size=10
x = 50
y = 50
my_ball = canvas.create_oval(x-size, y-size, x+size, y+size, fill="red")
bounce()
root.mainloop()

It's just basic math. The ball moves left when you subtract some amount from the x coordinate. If it hits the left wall and you want it to bounce to the right, you need to stop subtracting from x and start adding to x. The same is true for the y coordinate.

Related

Can I set an exact location of an object using tkinter?

Using Python3.7 I have created code that will move a ball from the top left corner to the bottom right corner. I am using coords to position the ball and move for the motion of the ball. However, I want the ball to start in a certin place. How can I set the position of the ball?
I have tried using place function and I get the error: 'int' object has no attribute 'place'
I tried using coords and I get the error: IndexError: list index out of range
I have tried changing my create_oval code. It works for the size of the ball but not where it starts from.
The code here works with no errors. How and where should I have a line for the exact coordinates of where the ball will start.
import tkinter as tkr
import time
tk = tkr.Tk()
canvas = tkr.Canvas(tk, width=480, height=480)
canvas.grid()
ball = canvas.create_oval(10,10,20,20,fill="blue")
x = 1
y = 1
while True:
canvas.move(ball,x,y)
pos = canvas.coords(ball)
if pos [3] >= 480 or pos[1] <=0:
y = -y
if pos[2] >= 480 or pos[0] <= 0:
x = -x
tk.update()
time.sleep(0.0099)
pass
tk.mainloop()
Also if I can get rid of the deprecation warning, that would be great as well.
Here's how you do a loop like this within the confines of an event-driven UI framework. Each callback does one little bit of work, then goes back to the loop to wait for future events.
import tkinter as tk
import time
win = tk.Tk()
canvas = tk.Canvas(win, width=480, height=480)
canvas.grid()
x = 10
y = 10
dx = 1
dy = 1
def moveball():
global x, dx
global y, dy
x += dx
y += dy
canvas.move(ball,dx,dy)
if y >= 480 or y <=0:
dy = -dy
if x >= 480 or x <= 0:
dx = -dx
win.after( 10, moveball )
ball = canvas.create_oval(x,y,x+10,y+10,fill="blue")
win.after( 100, moveball )
win.mainloop()
You'll note that the ball doesn't change directions until after it's all the way off the edge of the screen. That's because we're tracking the upper left corner of the ball and not taking the size into account. That's an easy thing to fix.
Used variables with the create_oval.
import tkinter as tkr
import time
tk = tkr.Tk()
canvas = tkr.Canvas(tk, width=480, height=480)
canvas.grid()
x = 47
y = 185
ball = canvas.create_oval(x,y,x+10,y+10,fill="blue")
dx = 1
dy = 1
while True:
canvas.move(ball,dx,dy)
pos = canvas.coords(ball)
if pos [3] >= 480 or pos[1] <=0:
dy = -dy
if pos[2] >= 480 or pos[0] <= 0:
dx = -dx
tk.update()
time.sleep(0.0099)
pass
tk.mainloop()
Big thanks to Tim Roberts. I end up taking his coding advice and edit mine original code.

Building Breakout Game (python) but I can't get bricks to delete_?

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

python tkinter bouncing ball game

I'm making a basic game. I want the ball to bounce back up ONLY when it hits the platform. So far, I've written code that will make the ball bounce off the top and bottom screen, but I'm having trouble with getting the ball to bounce off the platform.
from tkinter import *
import time
import tkinter
tk = Tk()
canvas = Canvas(tk, bg="white",width=(900),height=(500))
canvas.pack()
platform = canvas.create_rectangle(400,400,500,410)
def ball():
xspeed = 2
yspeed = 2
ball = canvas.create_oval(430,10,470,50)
while True:
canvas.move(ball, xspeed, yspeed)
pos = canvas.coords(ball)
if pos[2] >=900 or pos[0] <0:
xspeed = -xspeed
tk.update()
time.sleep(0.01)
def board():
board_right()
board_left()
def board_right(event):
xspeed = 5
yspeed = 0
canvas.move(platform,xspeed,yspeed)
tk.update
time.sleep(0.01)
def board_left(event):
xspeed = 5
yspeed = 0
canvas.move(platform,-xspeed,yspeed)
tk.update()
time.sleep(0.01)
canvas.bind_all("<Right>",board_right)
canvas.bind_all("<Left>",board_left)
ball()
tk.mainloop()
Do not use time.sleep() as it will block the tkinter mainloop, use after() instead.
To check whether the ball hits the platform, you need to get the center x of the ball and the lower y of the ball. If center x is within the left and right of platform and the ball lower y is the platform upper y, then reverse the ball y speed. Otherwise game over!
Below is sample code based on yours:
import tkinter as tk
root = tk.Tk()
width = 900
height = 500
canvas = tk.Canvas(root, bg='white', width=width, height=height)
canvas.pack()
ball = canvas.create_oval(430, 10, 470, 50, fill='green')
platform_y = height - 20
platform = canvas.create_rectangle(width//2-50, platform_y, width//2+50, platform_y+10, fill='black')
# ball moving speed
xspeed = yspeed = 2
def move_ball():
global xspeed, yspeed
x1, y1, x2, y2 = canvas.coords(ball)
if x1 <= 0 or x2 >= width:
# hit wall, reverse x speed
xspeed = -xspeed
if y1 <= 0:
# hit top wall
yspeed = 2
elif y2 >= platform_y:
# calculate center x of the ball
cx = (x1 + x2) // 2
# check whether platform is hit
px1, _, px2, _ = canvas.coords(platform)
if px1 <= cx <= px2:
yspeed = -2
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)
# make sure the platform is not moved beyond right wall
if x2 < width:
dx = min(width-x2, 10)
canvas.move(platform, dx, 0)
def board_left(event):
x1, y1, x2, y2 = canvas.coords(platform)
# make sure the platform is not moved beyond left wall
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()

How do I get a ball to glide in python tkinter?

I have this program that I tried to make in python tkinter. A ball would appear on screen and every time I click I want the ball to glide to the point at which I clicked. The x and y locations of the ball changed but the ball only redraws after the ball is finished "moving." Can someone tell me what I am doing wrong.
from tkinter import *
import time
width = 1280
height = 700
ballRadius = 10
iterations = 100
mouseLocation = [width/2, height/2]
ballLocation = [width/2, height/2]
root = Tk()
def drawBall(x, y):
canvas.delete(ALL)
canvas.create_oval(x - ballRadius, y - ballRadius, x + ballRadius, y + ballRadius, fill="blue")
print(x, y)
def getBallLocation(event):
mouseLocation[0] = event.x
mouseLocation[1] = event.y
dx = (ballLocation[0] - mouseLocation[0]) / iterations
dy = (ballLocation[1] - mouseLocation[1]) / iterations
for i in range(iterations):
ballLocation[0] -= dx
ballLocation[1] -= dy
drawBall(round(ballLocation[0]), round(ballLocation[1]))
time.sleep(0.02)
ballLocation[0] = event.x
ballLocation[1] = event.y
canvas = Canvas(root, width=width, height=height, bg="black")
canvas.pack()
canvas.create_oval(width/2-ballRadius, height/2-ballRadius, width/2+ballRadius, height/2+ballRadius, fill="blue")
canvas.bind("<Button-1>", getBallLocation)
root.mainloop()
In your code time.sleep pauses the entire GUI, that's why you don't see the intermediate locations of the ball. Instead you can construct a function with widget.after method. Try the following:
print(x, y)
dx = 0
dy = 0
def getBallLocation(event):
canvas.unbind("<Button-1>")
global dx, dy
mouseLocation[0] = event.x
mouseLocation[1] = event.y
dx = (ballLocation[0] - mouseLocation[0]) / iterations
dy = (ballLocation[1] - mouseLocation[1]) / iterations
draw()
i = 0
def draw():
global i
ballLocation[0] -= dx
ballLocation[1] -= dy
drawBall(round(ballLocation[0]), round(ballLocation[1]))
if i < iterations-1:
canvas.after(20, draw)
i += 1
else:
canvas.bind("<Button-1>", getBallLocation)
i = 0
canvas = Canvas(root, width=width, height=height, bg="black")

Make circles (balls) bounce in window using Python Zelle Graphics

I have a program in Python for making one ball bounce off of all 4 sides of a window, but now I need to make 10 balls using an array. I'm still very new to this and confused on how to implement this. I'll post what I have so far below:
#create 10 balls bouncing off all 4 sides of the window (400 x 500)
#The ball must start moving after a click, then stop after a given amount of time
#After a second click, the program ends
#----Algorithm----#
#create screen 400 x 500
#create array of 10 circles in different starting points
#wait for click anywhere on screen
#click anywhere on screen
#all 10 balls move in different directions bouncing off all 4 walls
#if ball hits left or right wall
#dy=dy*-1
#if ball hits top or bottom
#dx=dx*-1
#ball moves for no more than 30 seconds
#ball stops
#wait for next click
#program ends
#----ProgramStarts----#
from graphics import *
import time, random
#create screen
winWidth = 400;
winHeight = 500;
win = GraphWin('Ball Bounce', winWidth, winHeight);
win.setBackground(color_rgb(255,255,255));
win.setCoords(0,0,winWidth, winHeight);
numBalls= 10;
#create 10 balls
def makeBall(center, radius, win):
balls=[];
radius=10;
for i in range (0,numBalls):
aBall=Circle(center, radius);
aBall.setFill("red");
aBall.draw(win);
balls.append(aBall);
#animate 10 balls bouncing off edges of window
def bounceInWin(shape, dx, dy, xLow, xHigh, yLow, yHigh):
clickPoint=win.getMouse();
delay = .005
for x in range(900):
shape.move(dx, dy)
center = shape.getCenter()
x = center.getX()
y = center.getY()
if x < xLow:
dx = -dx
if x > xHigh:
dx = -dx
if y < yLow:
dy = -dy
if y > yHigh:
dy = -dy
time.sleep(delay);
#get a random Point
def getRandomPoint(xLow, xHigh, yLow, yHigh):
x = random.randrange(xLow, xHigh+1)
y = random.randrange(yLow, yHigh+1)
return Point(x, y)
#make ball bounce
def bounceBall(dx, dy):
winWidth = 400
winHeight = 500
win = GraphWin('Ball Bounce', winWidth, winHeight);
win.setCoords(0,0,winWidth, winHeight);
radius = 10
xLow = radius
xHigh = winWidth - radius
yLow = radius
yHigh = winHeight - radius
center = getRandomPoint(xLow, xHigh, yLow, yHigh)
ball = makeBall(center, radius, win)
bounceInWin(ball, dx, dy, xLow, xHigh, yLow, yHigh)
bounceBall(3, 5);
#wait for another click to end program
win.getMouse(); #close doesn't work, tried putting in loop
win.close;
Specific problems with your code: makeBall() fails to return balls; makeBall() needs to generate random starting points, not all starting at the same place; bounceInWin() needs to have a separate, and separately updated, dx and dy for every ball, not just the one; the code creates the main window twice -- just once is fine; need to add time calculation to shut down motion instead of fixed number of iterations; win.close() is a method so it needs parentheses; avoid using semi-colon ";" in Python code.
Below is a rework of your code with the above changes and some style tweaks:
# Create 10 balls bouncing off all 4 sides of a window (400 x 500)
# The ball must start moving after a click, then stop after a given
# amount of time. After a second click, the program ends.
#----Algorithm----#
# create screen 400 x 500
# create array of 10 circles in different starting points
# wait for click anywhere on screen
# all 10 balls move in different directions bouncing off all 4 walls
# if ball hits left or right wall
# dx = -dx
# if ball hits top or bottom
# dy = -dy
# balls move for no more than 30 seconds
# balls stops
# wait for next click
# program ends
#----ProgramStarts----#
import time
import random
from graphics import *
winWidth, winHeight = 400, 500
ballRadius = 10
ballColor = 'red'
numBalls = 10
delay = .005
runFor = 30 # in seconds
# create 10 balls, randomly located
def makeBalls(xLow, xHigh, yLow, yHigh):
balls = []
for _ in range(numBalls):
center = getRandomPoint(xLow, xHigh, yLow, yHigh)
aBall = Circle(center, ballRadius)
aBall.setFill(ballColor)
aBall.draw(win)
balls.append(aBall)
return balls
# animate 10 balls bouncing off edges of window
def bounceInWin(shapes, dx, dy, xLow, xHigh, yLow, yHigh):
movedShapes = [(getRandomDirection(dx, dy), shape) for shape in shapes]
start_time = time.time()
while time.time() < start_time + runFor:
shapes = movedShapes
movedShapes = []
for (dx, dy), shape in shapes:
shape.move(dx, dy)
center = shape.getCenter()
x = center.getX()
if x < xLow or x > xHigh:
dx = -dx
y = center.getY()
if y < yLow or y > yHigh:
dy = -dy
# Could be so much simpler if Point had setX() and setY() methods
movedShapes.append(((dx, dy), shape))
time.sleep(delay)
# get a random direction
def getRandomDirection(dx, dy):
x = random.randrange(-dx, dx)
y = random.randrange(-dy, dy)
return x, y
# get a random Point
def getRandomPoint(xLow, xHigh, yLow, yHigh):
x = random.randrange(xLow, xHigh + 1)
y = random.randrange(yLow, yHigh + 1)
return Point(x, y)
# make balls bounce
def bounceBalls(dx, dy):
xLow = ballRadius * 2
xHigh = winWidth - ballRadius * 2
yLow = ballRadius * 2
yHigh = winHeight - ballRadius * 2
balls = makeBalls(xLow, xHigh, yLow, yHigh)
win.getMouse()
bounceInWin(balls, dx, dy, xLow, xHigh, yLow, yHigh)
# create screen
win = GraphWin('Ball Bounce', winWidth, winHeight)
bounceBalls(3, 5)
# wait for another click to end program
win.getMouse()
win.close()

Categories

Resources