I have trouble trying to move an oval with TKinter - python

i begin to use TKinter and i try to make multiple boucing balls as a training exercise. When i create an unique oval and make it move, everything works but when i create a Ball class with a move method in it, the ball doesn't move at all. I receive no error message but it refuse to move.
"""
Initialisation
"""
from tkinter import *
import time
w_height = 600
w_width = 800
xspeed = 10
yspeed = 10
window = Tk()
window.geometry ("{}x{}".format(w_width, w_height))
window.title("Bouncing Balls")
canvas = Canvas(window, width = w_width - 50,height = w_height - 50, bg="black")
"""
If i create the ball like that and make it move it works fine
"""
ball1 = canvas.create_oval(10, 10, 50, 50, fill ="red")
while True:
canvas.move(ball1, xspeed, yspeed)
window.update()
time.sleep(0.05)
"""
But if i try to create the ball using a class it doesn't work anymore...
"""
class Ball:
def __init__(self, posx, posy, r):
canvas.create_oval(posx-r, posy-r, posx+r, posy+r, fill ="red")
def move(self, dx, dy):
canvas.move(self, dx, dy)
ball1 = Ball(50,50,10)
while True:
ball1.move(xspeed, yspeed)
window.update()
time.sleep(0.05)
I tought it would give the same result but in the first case the ball moves and in the second it doesn't and i can't figure out why.

In your code, canvas.create_oval() function returns an object that can be then moved my called canvas.move(object, ...) function. But as you can see you are passing self in the class method move.
def move(self, dx, dy):
canvas.move(*self*, dx, dy)
This is the instance of the class Ball, in this case ball1 variable, that you defined(actually reassigned) by doing ball1 = Ball(50, 50, 10).
To get this working change your class to this.
class Ball:
def __init__(self, posx, posy, r):
self.ball = canvas.create_oval(posx-r, posy-r, posx+r, posy+r, fill ="red")
def move(self, dx, dy):
canvas.move(self.ball, dx, dy)
Here you define a class field that will get what returns canvas.create_oval() function and then use it to move the object.

Related

Tkinter Canvas - Checking For Character Wall Collision

suppose I have code like the following. I want to black blob to end the game when it touched the frame, in other words, the wall. I actually am a beginner to Tkinter Canvas so if you give a code explanation too complex I can not understand it.
# Modules
from tkinter import *
# Classes
class Player:
def __init__(self, master=None):
self.master = master
self.x = 0 # To take care of movement in "x" direction.
self.y = 0 # To take care of movement in "y" direction.
self.canvas = Canvas(master) # Canvas object to create shape.
self.rectangle = self.canvas.create_rectangle(40, 40, 25, 25, fill="black") # Creating rectangle.
self.canvas.pack() # Packing the canvas.
self.movement() # Calling the movement method to move the rectangle.
def movement(self):
self.canvas.move(self.rectangle, self.x, self.y) # This is where the move() method is called.
"""
⬆ This moves the rectangle to x, y coordinates.
"""
self.canvas.after(100, self.movement) # Gives the character a cooldown time.
def left(self): # For motion in negative "x" direction.
self.x = -5
self.y = 0
def right(self): # For motion in positive "x" direction.
self.x = 5
self.y = 0
def down(self): # For motion in positive "y" direction.
self.x = 0
self.y = 5
def up(self): # For motion in negative "y" direction.
self.x = 0
self.y = -5
# Main Window
print("How To Play\nTry to not collid the frame.")
master = Tk()
master.title("Easy Game")
master.geometry("300x75")
master.resizable(True, True)
engine = Player(master)
master.bind("<KeyPress-Left>", lambda e: engine.left())
master.bind("<KeyPress-Right>", lambda e: engine.right())
master.bind("<KeyPress-Up>", lambda e: engine.up())
master.bind("<KeyPress-Down>", lambda e: engine.down())
# Mainloop
mainloop()
Thank you for further explanation.

How to add a function with if statements (key movement) in a class (python/pygame)?

I need to program a game in pygame similar to pong, but with 1 player. Although there is one paddle and one ball, I'm required to make a class for the paddle and for the ball. I created the paddle class, drawn it, but I have problem with implementing the if statements for movement. Here's what I tried:
class Paddle():
def __init__(self,x,y,width,height,color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def Draw(self, screen,):
pygame.draw.rect(window, self.color, [self.x,self.y,self.width,self.height])
def Keys(self, y, height):
keys = pygame.key.get_pressed()
if keys [pygame.K_UP]:
self.y -= 1
if keys [pygame.K_DOWN]:
self.y += 1
and then I added a separate function for the objects from the classes:
def Objects():
paddle = Paddle(1150,250,20,100,black)
paddle.Draw(window)
paddle.Keys(250,100)
Again I want to add key movement in the class (since all paddles should have the same function). I should also mention that I'm not getting any error, but it doesn't work.
You must create the instance of Paddle before the application loop. Pass the object to the Objects function:
def Objects(surf, paddle):
paddle.Draw(surf)
paddle.Keys(250,100)
my_paddle = Paddle(1150,250,20,100,black)
while True:
# [...]
Objects(window, my_paddle)
# [...]

Create a Racing Car in Python, Why is my car not racing?

I am working on a homework problem for my Python course that is supposed to display a car (got that) and then the car is supposed to simulate driving across the window until it reaches the end, and then restarting at the beginning. I have a race function that is supposed to be doing this but it's not doing anything. I'm having a hell of a time with classes and tkinter. I know that I need the value of x to be incremented each time it loops and updates so that the car looks like its moving, I'm just not sure how to implement that. Is my boolean not written correctly? I've tried changing things around a few different ways with the function but I can't seem to get it to do anything, so I must be missing something.
UPDATE: I have moved the race method into the class, and called it within the constructor. I put a print statement to get the value of x in the race method and its showing that x is being incremented correctly, but when I run the program my car disappears. So it's updating the value of x as it should, but its not displaying the graphic.
Code updated below
Any suggestions are appreciated!
# Import tkinter
from tkinter import *
# Set the height and Width of the window
width = 800
height = 800
# Create race car class with canvas as the argument
class RacingCar(Canvas):
# Constructor
def __init__(self, master, width, height):
# Constructor
Canvas.__init__(self, master, width = width, height = height)
# Create x and y variables for starting position
self.x = 10
self.y = 40
# Display the car
self.display_car()
self.race()
# Function to display car
def display_car(self):
# Delete original car
self.delete("car")
# Create first wheel
self.create_oval(self.x + 10, self.y - 10, self.x + 20,\
self.y, fill = "black", tags = "car")
# Create the second wheel
self.create_oval(self.x + 30, self.y - 10, self.x + 40,\
self.y, fill = "black", tags = "car")
# Create the body
self.create_rectangle(self.x, self.y - 20, self.x + 50,\
self.y - 10, fill = "green", tags = "car")
# Create the roof
self.create_polygon(self.x + 10, self.y - 20, self.x + 20,\
self.y - 30, self.x + 30, self.y - 30,\
self.x + 40, self.y - 20, fill = "green",\
tags = "car")
def race():
while True:
if self.x < width:
self.x += 2
else:
self.x = 0
self.after(88)
self.update()
window = Tk()
window.title("Racing Car")
racecar = RacingCar(window, width = 240, height = 50 )
racecar.pack()
window.mainloop()
This might not get it done (so I make this answer a community wiki), but I suggest you move the race method in the class:
class RaceCar(Canvas):
# Your current code
def race(self):
while True:
if self.x < width:
self.x += 2
else:
self.x = 0
self.after(88)
self.update()

Tkinter .after() not responding to keypress fast enough

I am trying to animate a ball, that is be moving forward. In addition, if the user presses up or down button, it should move respectively.
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class Movement:
def __init__(self, _frame, _canvas):
self.frame = _frame
self.canvas = _canvas
self.x = 10
self.y = 150
self.count = 0
self.update()
self.frame.bind_all("<Up>", lambda event: self.move_up())
self.frame.bind_all("<Down>", lambda event: self.move_down())
pass
def move_up(self):
self.y -= 10
pass
def move_down(self):
self.y += 10
pass
def update(self):
canvas.delete("obj")
self.count += 10
x2 = self.x + 50
y2 = self.y + 50
self.canvas.create_oval(self.x + self.count, self.y, x2 + self.count, y2, fill="red", tag="obj")
root.after(1000, self.update)
pass
root = tk.Tk()
width = 400
height = 400
canvas = tk.Canvas(root, width=width, height=height)
frame = tk.Frame(root, width=width, height=height)
if __name__ == '__main__':
Movement(frame, canvas)
canvas.grid()
root.mainloop()
There's an issue, though. Given the fact that the ball is moving every 1 second, it doesn't respond to the key press fast enough. As a result, if you click the up kick, the screen is updating after one second.
I know there's a .move() function inside Tkinter. However, due to many reason I cannot use that.
My main concern is the interval it take for the screen to be updated.
From what I understand you want to be able to keep the timer moving forward consistent but also be able to move up and down at any point even outside of the timer.
to accomplish this I moved the timer to its own function and allowed the up down functions to call the object destroy/create function.
I have updated you code to reflect that.
Take a look at this code and let me know if it helps.
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class Movement:
def __init__(self, _frame, _canvas):
self.frame = _frame
self.canvas = _canvas
self.x = 10
self.y = 150
self.count = 0
self.object_timer()
self.frame.bind_all("<Up>",lambda event: self.move_up())
self.frame.bind_all("<Down>",lambda event: self.move_down())
def move_up(self):
self.y -= 10
self.update()
def move_down(self):
self.y += 10
self.update()
def update(self):
canvas.delete("obj")
x2 = self.x + 50
y2 = self.y + 50
self.canvas.create_oval(self.x + self.count, self.y, x2 + self.count, y2, fill="red", tag="obj")
def object_timer(self):
self.update()
self.count += 10
root.after(1000, self.object_timer)
root = tk.Tk()
width = 400
height = 400
canvas = tk.Canvas(root, width=width, height=height)
frame = tk.Frame(root, width=width, height=height)
if __name__ == '__main__':
Movement(frame, canvas)
canvas.grid()
root.mainloop()
It's all just math. You can call update twice as much, and move the ball half as much if you want to keep the ball moving at the same speed while reacting to keypresses faster.
For example, instead of moving 10 pixels every second, have it move 1 pixel every tenth of a second. The end result is still 10 pixels every second. The animation will be smoother, and it will react to keypresses much faster.
By the way, there's no need to delete and recreate the oval every iteration. It's possible to move items on the canvas. Deleting and recreating will eventually cause performance problems do to how the canvas is implemented internally. Each object you create gets a new unique id, and the canvas will slow down when it has a large number of ids. The canvas does not re-use an id.
I would also recommend against using lambda. Unless you need it, it adds complexity without adding any value. Just add an extra parameter to move_up and move_down:
self.frame.bind_all("<Up>",self.move_up)
self.frame.bind_all("<Down>", self.move_down)
...
def move_up(self, event=None):
...
def move_down(self, event=None):
...

Hide Turtle Window?

I am generating diagrams in Turtle, and as part of my program, I identify certain coordinates from my diagrams. I would like to be able to hide the complete turtle window, since I only care about the coordinates, is that possible?
Edit:
QUESTION 2:
This isn't really an answer, but a few other questions.
I got my program working to some extent, if you run it in IDLE and type "l" it will give you the list with the coordinates.
import Tkinter
import turtle
from turtle import rt, lt, fd # Right, Left, Forward
size = 10
root = Tkinter.Tk()
root.withdraw()
c = Tkinter.Canvas(master = root)
t = turtle.RawTurtle(c)
t.speed("Fastest")
# List entire coordinates
l = []
def findAndStoreCoords():
x = t.xcor()
y = t.ycor()
x = round(x, 0) # Round x to the nearest integer
y = round(y, 0) # Round y to the nearest integer
# Integrate coordinates into sub-list
l.append([x, y])
def hilbert(level, angle):
if level == 0:
return
t.rt(angle)
hilbert(level - 1, -angle)
t.fd(size)
findAndStoreCoords()
t.lt(angle)
hilbert(level - 1, angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, angle)
t.lt(angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, -angle)
t.rt(angle)
The problem is that Turtle is so SLOW! Is there any package that is just like Turtle but can do commands much faster?
I reimplemented the turtle class as suggested by thirtyseven. It is consistent with the api. (i.e. when you turn right in this class, it is the same as turning right in turtle.
This does not implement all the methods in the api, only common ones. (And the ones you used).
However, it's short and fairly straightforward to extend. Also, it keeps track of all of the points it has been to. It does this by adding an entry to pointsVisited every time you call forward, backward, or setpos (or any of the aliases for those functions).
import math
class UndrawnTurtle():
def __init__(self):
self.x, self.y, self.angle = 0.0, 0.0, 0.0
self.pointsVisited = []
self._visit()
def position(self):
return self.x, self.y
def xcor(self):
return self.x
def ycor(self):
return self.y
def forward(self, distance):
angle_radians = math.radians(self.angle)
self.x += math.cos(angle_radians) * distance
self.y += math.sin(angle_radians) * distance
self._visit()
def backward(self, distance):
self.forward(-distance)
def right(self, angle):
self.angle -= angle
def left(self, angle):
self.angle += angle
def setpos(self, x, y = None):
"""Can be passed either a tuple or two numbers."""
if y == None:
self.x = x[0]
self.y = y[1]
else:
self.x = x
self.y = y
self._visit()
def _visit(self):
"""Add point to the list of points gone to by the turtle."""
self.pointsVisited.append(self.position())
# Now for some aliases. Everything that's implemented in this class
# should be aliased the same way as the actual api.
fd = forward
bk = backward
back = backward
rt = right
lt = left
setposition = setpos
goto = setpos
pos = position
ut = UndrawnTurtle()
Yes, this is possible. The simplest way is to instantiate a root Tkinter window, withdraw it, and then use it as the master window for a RawTurtle's Canvas instance.
Example:
import Tkinter
import turtle
root=Tkinter.Tk()
root.withdraw()
c=Tkinter.Canvas(master=root)
t=turtle.RawTurtle(c)
t.fd(5)
print t.xcor() # outputs 5.0
Unfortunately, this still initiates the graphics system, but no window will appear.
The problem is that Turtle is so SLOW! Is there any package that is
just like Turtle but can do commands much faster?
Yes, turtle can. If we add a TurtleScreen to the tkinter implementation, and use it's tracer() functionality, we can speed things up more than turtle's speed() method. And we can simplify the code greatly by tossing the customizations and simply use turtle's own begin_poly(), end_poly() and get_poly() methods:
from tkinter import Tk, Canvas
from turtle import TurtleScreen, RawTurtle
SIZE = 10
def hilbert(level, angle):
if level == 0:
return
turtle.right(angle)
hilbert(level - 1, -angle)
turtle.forward(SIZE)
turtle.left(angle)
hilbert(level - 1, angle)
turtle.forward(SIZE)
hilbert(level - 1, angle)
turtle.left(angle)
turtle.forward(SIZE)
hilbert(level - 1, -angle)
turtle.right(angle)
root = Tk()
root.withdraw()
canvas = Canvas(master=root)
screen = TurtleScreen(canvas)
screen.tracer(False) # turn off turtle animation
turtle = RawTurtle(screen)
turtle.begin_poly() # start tracking movements
hilbert(5, 90)
turtle.end_poly() # end tracking movements
print(turtle.get_poly())
This prints all the points in a level 5 Hilbert curve in about 1/3 of a second on my system. Your posted code toke nearly 9 seconds to output a level 4.

Categories