tkinter python - Can I create window collision? - python

New to programming. Working on a simple pong clone. Started the ball but want to make sure all sides of the window (500x500) will have the ball bounce off of it. How could I do this? Thanks!
P.S. This is my current code if needed.
import threading
import random
import time
import string
import os.path
from random import randint
from tkinter import *
class Pong:
Title = 'Pong'
Size = '500x500'
class Ball:
def __init__(self,canvas,x1,y1,x2,y2):
self.x1 =x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="black")
def move_ball(self):
deltax = randint(0,5)
deltay = randint(0,5)
self.canvas.move(self.ball,deltax,deltay)
self.canvas.after(50,self.move_ball)
def PongGame():
print("Moved to PongGame.")
ball1 = Ball(canvas,10,10,30,30)
ball1.move_ball()
def titleButtonClicked(event):
print("Title screen button clicked.")
btn.pack_forget()
btn.place(x=600,y=600)
msg.pack_forget()
PongGame()
root = Tk()
root.geometry(Pong.Size)
root.title(Pong.Title)
root.resizable(False,False)
msg = Label(root, text = Pong.Title, font = ("", 50))
msg.pack()
canvas = Canvas(root, width = 500, height = 500)
canvas.pack()
btn=Button(root, text = "Start")
btn.bind('<Button-1>', titleButtonClicked)
btn.place(x=220,y=300)
root.mainloop()

Collisions are not trivial; the simplest is to reverse the x or the y velocity after checking which edge of the bounding box of the ball intersects with the boundaries of the canvas.
Maybe something like this:
import random
import tkinter as tk
WIDTH, HEIGHT = 500, 500
class Ball:
radius = 10
spawn_center = (250, 100)
def __init__(self, canvas):
self.canvas = canvas
self.id = None
self.create_ball()
self.velocity = None
self.assign_random_velocity()
self.keep_moving = True
self.move()
def create_ball(self):
xc, yc = self.spawn_center
x0, y0, = xc - self.radius, yc + self.radius
x1, y1, = xc + self.radius, yc - self.radius
self.id = self.canvas.create_oval(x0, y0, x1, y1)
def assign_random_velocity(self):
dx = random.randrange(1, 5) * random.choice((1, -1))
dy = random.randrange(1, 5) * random.choice((1, -1))
self.velocity = (dx, dy)
def move(self):
if self.keep_moving is None:
return
self.check_collision()
self.canvas.move(self.id, *self.velocity)
self.keep_moving = self.canvas.after(10, self.move)
def cancel_move(self):
if self.keep_moving is not None:
self.canvas.after_cancel(self.keep_moving)
self.keep_moving = None
def check_collision(self):
x0, y0, x1, y1 = self.canvas.coords(self.id)
dx, dy = self.velocity
if x0 < 0:
x0 = 0
dx = -dx
elif x1 > WIDTH:
x1 = WIDTH
dx = -dx
if y0 < 0:
y0 = 0
dy = -dy
elif y1 > HEIGHT:
y1 = HEIGHT
dy = -dy
self.velocity = dx, dy
class PongBoard(tk.Canvas):
def __init__(self, master):
self.master = master
super().__init__(self.master)
self.ball = None
self.spawn_new_ball()
def spawn_new_ball(self):
if self.ball is not None:
self.ball.cancel_move()
self.delete(self.ball.id)
self.ball = Ball(self)
root = Tk()
root.geometry(f'{WIDTH}x{HEIGHT+20}')
board = PongBoard(root)
new_ball_btn = tk.Button(root, text='spawn new ball', command=board.spawn_new_ball)
board.pack(expand=True, fill=tk.BOTH)
new_ball_btn.pack()
root.mainloop()
This will get you started, but you will have to implement the paddles, the paddles movement, the collision checking of the ball with the paddles, and keep the score by yourself.

Related

tkinter molecular simulation issue

I have constructed a lattice of particles with tkinter and now I would like each particle in the lattice to move according to a list of x and y coordinates that I read into an array from two text files. I have tried to create a class with a function inside that defines movement with the canvas.move function but I get the error TclError: wrong # coordinates: expected 0 or 4, got 400. How to get around this?
from Tkinter import *
import random
import time
import csv
tk = Tk()
N = 100
T = 500
canvas = Canvas(tk, width=100, height=100)
tk.title("Test")
canvas.pack()
n = 5
t = 10
step1 = []
step2 = []
textFile1 = open('/Users/francislempp/Desktop/major project/C++ programs/Molecular Dynamics 2D/Molecular_Dynamics_2D-gupnvjunowwmjcfiyoursdhzytow/Build/Products/Debug/motionX', 'r')
lines = textFile1.readlines()
for line in lines:
step1.append(line.split(" "))
textFile2 = open('/Users/francislempp/Desktop/major project/C++ programs/Molecular Dynamics 2D/Molecular_Dynamics_2D-gupnvjunowwmjcfiyoursdhzytow/Build/Products/Debug/motionY', 'r')
lines = textFile2.readlines()
for line in lines:
step2.append(line.split(" "))
def moves(xspeed, yspeed):
canvas.move(xspeed, yspeed)
class Ball:
def __init__(self, x, y, color):
self.x = x
self.y = y
self.shape = canvas.create_oval((x,y,x,y), fill = color)
def move(self):
canvas.move(self.shape, self.x, self.y)
pos = canvas.coords(self.shape)
if pos[3] >= 100 or pos[1] <= 0:
self.y = -self.y
if pos[2] > 100 or pos[0] <= 0:
self.x = -self.x
def delete(self):
canvas.delete(self.shape)
balls = []
for x in range(4,100,10):
for y in range(4,100,10):
#canvas.create_oval((x,y,x,y), fill='red')
Ball(x,y,"red")
tk.update()
for i in step1:
for j in step2:
Ball(i,j,"red")
Ball.move()
tk.update()
tk.mainloop()
You have to save items on list and use this list to move
I copy example from previous question
import tkinter as tk
import random
# --- functions ---
def move():
for point_id in points:
x = random.randint(-1, 1)
y = random.randint(-1, 1)
canvas.move(point_id, x, y)
root.after(100, move)
# --- main ---
points = []
root = tk.Tk()
canvas = tk.Canvas(root, width=100, height=100)
canvas.pack()
for x in range(4, 100, 10):
for y in range(4, 100, 10):
point_id = canvas.create_oval(x, y, x, y, fill="red")
points.append(point_id)
move()
root.mainloop()
EDIT: with your class but without data from file.
I use random instead of data from files to show how to create particles and move them.
Every ball moves with own speed/direction which change when touch border.
import tkinter as tk
import random
# --- classes ---
class Ball:
def __init__(self, x, y, speed_x, speed_y, color):
self.speed_x = speed_x
self.speed_y = speed_y
self.shape = canvas.create_oval((x, y, x, y), fill=color)
def move(self):
canvas.move(self.shape, self.speed_x, self.speed_y)
pos = canvas.coords(self.shape)
# change speed/direction when touch border
if pos[3] >= 100 or pos[1] <= 0:
self.speed_y = -self.speed_y
if pos[2] > 100 or pos[0] <= 0:
self.speed_x = -self.speed_x
def delete(self):
canvas.delete(self.shape)
# --- functions ---
def move():
for ball in all_balls:
ball.move()
root.after(100, move)
# --- main ---
root = tk.Tk()
canvas = tk.Canvas(root, width=100, height=100)
canvas.pack()
all_balls = []
for x in range(4, 100, 10):
for y in range(4, 100, 10):
offset_x = random.randint(-2, 2)
offset_y = random.randint(-2, 2)
all_balls.append(Ball(x, y, offset_x, offset_y, "red"))
move()
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")

Initializing superclasses in Python

I'm learning Python. At the moment, I can do what I want to do by composition, but when I try to do the same thing using inheritance, I get an error. Here's my code. I'm basically trying to make a class for a colored square.
from graphics import *
class Block(Rectangle):
def __init__(self, corner, colour):
self.corner = corner
self.colour = colour
self.x1 = self.corner.getX() * 30
self.y1 = self.corner.getY() * 30
self.x2 = self.x1 + 30
self.y2 = self.y1 + 30
self.point1 = Point(self.x1, self.y1)
self.point2 = Point(self.x2, self. y2)
Rectangle.__init__(self, self.point1, self.point2)
def draw(self, window):
self.window = window
self.Rectangle.draw(self.window)
new_win = GraphWin("thingy", 700, 500)
corner = Point(1, 1)
square1 = Block(corner, 'red')
square1.draw(new_win)
new_win.mainloop()
The error I get is
File "F:\Python\4\4_3.py", line 24, in draw
self.draw(self.window)
The error is repeated indefinitely.
Here is the code that does what I want when I do it with composition:
from graphics import *
class Block():
def __init__(self, corner, colour):
self.corner = corner
self.colour = colour
self.x1 = self.corner.getX() * 30
self.y1 = self.corner.getY() * 30
self.x2 = self.x1 + 30
self.y2 = self.y1 + 30
self.point1 = Point(self.x1, self.y1)
self.point2 = Point(self.x2, self. y2)
self.Rectangle = Rectangle(self.point1, self.point2)
def draw(self, window):
self.window = window
self.Rectangle.draw(self.window)
self.Rectangle.setFill(self.colour)
new_win = GraphWin("thingy", 150, 150)
corner = Point(1, 1)
square1 = Block(corner, 'red')
square1.draw(new_win)
new_win.mainloop()
from graphics import *
class Block(Rectangle):
def __init__(self, corner, colour):
self.corner = corner
self.colour = colour
self.x1 = self.corner.getX() * 30
self.y1 = self.corner.getY() * 30
self.x2 = self.x1 + 30
self.y2 = self.y1 + 30
self.point1 = Point(self.x1, self.y1)
self.point2 = Point(self.x2, self. y2)
Rectangle.__init__(self, self.point1, self.point2)
def draw(self, window):
self.window = window
Rectangle.draw(self, self.window)
# instead of self.Rectangle.draw(self.window)
In the case of inheritance, there is no self.Rectangle
The simple code for python 2.7 is:
BaseClassName.__init__(self, args)

Mutli-threading python with Tkinter

I'm drawing little circles on a canvas with these functions :
This is the function that will draw the circles :
class Fourmis:
def __init__(self, can, posx, posy, name, radius):
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.radius = radius
self.ball1 = self.can.create_oval(self.posy, self.posx, self.posy+radius, self.posx+radius, outline=self.name, 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.can.bind("<Motion>", self.destruction, add="+")
self.statut = True
self.move()
def move(self):
if self.statut == True :
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 + self.radius) > self.largeur_can:
self.nx = -self.nx
if self.posy_ball < 0 or (self.posy_ball + self.radius) > self.hauteur_can:
self.ny = -self.ny
self.can.move(self.ball1, self.nx, self.ny)
self.can.after(10, self.move)
this one creates the canvas and the circles :
class App(Frame):
def __init__(self):
self.root=Tk()
self.can=Canvas(self.root,width=800,height=600,bg="black")
self.can.pack()
self.create(50, "green")
self.create(50, "purple")
def mainloop(self):
self.root.mainloop()
def create(self, i, name):
for x in range(i):
self.x=Fourmis(self.can,100,400, name,0)
I call these lines to run the project :
jeu = App()
jeu.mainloop()
What is the correct way to execute self.create(50, "green") and self.create(50, "purple") in different threads?
I have tried the following, but could not get it to work.:
class FuncThread(threading.Thread):
def __init__(self, i, name):
self.i = i
self.name = name
threading.Thread.__init__(self)
def run(self):
App.create(self, self.i, self.name)
Is someone able to tell me how to run these threads?
When this functionality is needed, what you do is schedule the events you wish to perform by putting them in a queue shared by the threads. This way, in a given thread you specify that you want to run "create(50, ...)" by queueing it, and the main thread dequeue the event and perform it.
Here is a basic example for creating moving balls in a second thread:
import threading
import Queue
import random
import math
import time
import Tkinter
random.seed(0)
class App:
def __init__(self, queue, width=400, height=300):
self.width, self.height = width, height
self.canvas = Tkinter.Canvas(width=width, height=height, bg='black')
self.canvas.pack(fill='none', expand=False)
self._oid = []
self.canvas.after(10, self.move)
self.queue = queue
self.canvas.after(50, self.check_queue)
def check_queue(self):
try:
x, y, rad, outline = self.queue.get(block=False)
except Queue.Empty:
pass
else:
self.create_moving_ball(x, y, rad, outline)
self.canvas.after(50, self.check_queue)
def move(self):
width, height = self.width, self.height
for i, (oid, r, angle, speed, (x, y)) in enumerate(self._oid):
sx, sy = speed
dx = sx * math.cos(angle)
dy = sy * math.sin(angle)
if y + dy + r> height or y + dy - r < 0:
sy = -sy
self._oid[i][3] = (sx, sy)
if x + dx + r > width or x + dx - r < 0:
sx = -sx
self._oid[i][3] = (sx, sy)
nx, ny = x + dx, y + dy
self._oid[i][-1] = (nx, ny)
self.canvas.move(oid, dx, dy)
self.canvas.update_idletasks()
self.canvas.after(10, self.move)
def create_moving_ball(self, x=100, y=100, rad=20, outline='white'):
oid = self.canvas.create_oval(x - rad, y - rad, x + rad, y + rad,
outline=outline)
oid_angle = math.radians(random.randint(1, 360))
oid_speed = random.randint(2, 5)
self._oid.append([oid, rad, oid_angle, (oid_speed, oid_speed), (x, y)])
def queue_create(queue, running):
while running:
if random.random() < 1e-6:
print "Create a new moving ball please"
x, y = random.randint(100, 150), random.randint(100, 150)
color = random.choice(['green', 'white', 'yellow', 'blue'])
queue.put((x, y, random.randint(10, 30), color))
time.sleep(0) # Effectively yield this thread.
root = Tkinter.Tk()
running = [True]
queue = Queue.Queue()
app = App(queue)
app.create_moving_ball()
app.canvas.bind('<Destroy>', lambda x: (running.pop(), x.widget.destroy()))
thread = threading.Thread(target=queue_create, args=(queue, running))
thread.start()
root.mainloop()

How to rotate a polygon on a Tkinter Canvas?

I am working to create a version of asteroids using Python and Tkinter. When the left or right arrow key is pressed the ship needs to rotate. The ship is a triangle on the Tkinter canvas. I am having trouble coming up with formula to adjust the coordinates for the triangle. I believe it has something to do with sin and cos, though I am not exactly sure. So far I have two classes one for the ship and the other for the game. In the ship class I have callback methods for the key presses. Any help would be greatly appreciated. Thanks.
Ship Class
import math
class Ship:
def __init__(self,canvas,x,y,width,height):
self.canvas = canvas
self.x = x - width/2
self.y = y + height/2
self.width = width
self.height = height
self.x0 = self.x
self.y0 = self.y
self.x1 = self.x0 + self.width/2
self.y1 = self.y0-self.height
self.x2 = self.x0 + self.width
self.y2 = self.y0
self.ship = self.canvas.create_polygon((self.x0, self.y0, self.x1, self.y1, self.x2, self.y2), outline="white", width=3)
def changeCoords(self):
self.canvas.coords(self.ship,self.x0, self.y0, self.x1, self.y1, self.x2, self.y2)
def rotateLeft(self, event=None):
# Should rotate one degree left.
pass
def rotateRight(self, event=None):
# Should rotate one degree right.
self.x0 = self.x0 -1
self.y0 = self.y0 - 1
self.x1 = self.x1 + 1
self.y1 = self.y1 + 1
self.x2 = self.x2 - 1
self.y2 = self.y2 + 1
self.changeCoords()
Game Class
from Tkinter import *
from ship import *
class Game:
def __init__(self, gameWidth, gameHeight):
self.root = Tk()
self.gameWidth = gameWidth
self.gameHeight = gameHeight
self.gameWindow()
self.ship = Ship(self.canvas, x=self.gameWidth/2,y=self.gameHeight/2, width=50, height=50)
self.root.bind('<Left>', self.ship.rotateLeft)
self.root.bind('<Right>', self.ship.rotateRight)
self.root.mainloop()
def gameWindow(self):
self.frame = Frame(self.root)
self.frame.pack(fill=BOTH, expand=YES)
self.canvas = Canvas(self.frame,width=self.gameWidth, height=self.gameHeight, bg="black", takefocus=1)
self.canvas.pack(fill=BOTH, expand=YES)
asteroids = Game(600,600)
First of all, you need to rotate around a center of the triangle. The centroid would probably work best for that. To find that, you can use the formula C = (1/3*(x0 + x1 + x2), 1/3*(y0 + y1 + y2)), as it's the average of all points in the triangle. Then you have to apply the rotation with that point as the center. So it'd be something like this...
import math
class Ship:
def centroid(self):
return 1 / 3 * (self.x0 + self.x1 + self.x2), 1 / 3 * (self.y0 + self.y1 + self.y2)
def __init__(self, canvas, x, y, width, height, turnspeed, acceleration=1):
self._d = {'Up':1, 'Down':-1, 'Left':1, 'Right':-1}
self.canvas = canvas
self.width = width
self.height = height
self.speed = 0
self.turnspeed = turnspeed
self.acceleration = acceleration
self.x0, self.y0 = x, y
self.bearing = -math.pi / 2
self.x1 = self.x0 + self.width / 2
self.y1 = self.y0 - self.height
self.x2 = self.x0 + self.width
self.y2 = self.y0
self.x, self.y = self.centroid()
self.ship = self.canvas.create_polygon((self.x0, self.y0, self.x1, self.y1, self.x2, self.y2), outline="white", width=3)
def changeCoords(self):
self.canvas.coords(self.ship,self.x0, self.y0, self.x1, self.y1, self.x2, self.y2)
def rotate(self, event=None):
t = self._d[event.keysym] * self.turnspeed * math.pi / 180 # the trig functions generally take radians as their arguments rather than degrees; pi/180 radians is equal to 1 degree
self.bearing -= t
def _rot(x, y):
#note: the rotation is done in the opposite fashion from for a right-handed coordinate system due to the left-handedness of computer coordinates
x -= self.x
y -= self.y
_x = x * math.cos(t) + y * math.sin(t)
_y = -x * math.sin(t) + y * math.cos(t)
return _x + self.x, _y + self.y
self.x0, self.y0 = _rot(self.x0, self.y0)
self.x1, self.y1 = _rot(self.x1, self.y1)
self.x2, self.y2 = _rot(self.x2, self.y2)
self.x, self.y = self.centroid()
self.changeCoords()
def accel(self, event=None):
mh = int(self.canvas['height'])
mw = int(self.canvas['width'])
self.speed += self.acceleration * self._d[event.keysym]
self.x0 += self.speed * math.cos(self.bearing)
self.x1 += self.speed * math.cos(self.bearing)
self.x2 += self.speed * math.cos(self.bearing)
self.y0 += self.speed * math.sin(self.bearing)
self.y1 += self.speed * math.sin(self.bearing)
self.y2 += self.speed * math.sin(self.bearing)
self.x, self.y = self.centroid()
if self.y < - self.height / 2:
self.y0 += mh
self.y1 += mh
self.y2 += mh
elif self.y > mh + self.height / 2:
self.y0 += mh
self.y1 += mh
self.y2 += mh
if self.x < -self.width / 2:
self.x0 += mw
self.x1 += mw
self.x2 += mw
elif self.x > mw + self.width / 2:
self.x0 -= mw
self.x1 -= mw
self.x2 -= mw
self.x, self.y = self.centroid()
self.changeCoords()
I made some changes to the controls that make the game a bit more like Asteroids, by the way. (Didn't implement firing, though. I may have gotten more into this than I expected, but I'm not going to do everything. Also, there's a bit of a problem when you try to use multiple movement keys at once, but that's due to the way Tk does event handling. It wasn't designed for gaming, so you'd have to fiddle around a fair bit to get that working properly with Tk/Tkinter.)
from tkinter import *
from ship import *
class Game:
def __init__(self, gameWidth, gameHeight):
self.root = Tk()
self.gameWidth = gameWidth
self.gameHeight = gameHeight
self.gameWindow()
self.ship = Ship(self.canvas, x=self.gameWidth / 2,y=self.gameHeight / 2, width=50, height=50, turnspeed=10, acceleration=5)
self.root.bind('<Left>', self.ship.rotate)
self.root.bind('<Right>', self.ship.rotate)
self.root.bind('<Up>', self.ship.accel)
self.root.bind('<Down>', self.ship.accel)
self.root.mainloop()
def gameWindow(self):
self.frame = Frame(self.root)
self.frame.pack(fill=BOTH, expand=YES)
self.canvas = Canvas(self.frame,width=self.gameWidth, height=self.gameHeight, bg="black", takefocus=1)
self.canvas.pack(fill=BOTH, expand=YES)
asteroids = Game(600,600)
As an aside, you might want to use properties to allow for easier handling of the points and such.

Categories

Resources