How can I repeat a def function in Canvas? - python

I want this def function to repeat itself as soon as the ball hits the ground (border of the window)
I tried using if, for loop and while:, but I wasn't able to make it work. I'm a beginner so maybe I'm just making stupid mistakes. Thanks for the help.
import tkinter
canvas = tkinter.Canvas(width=600, height=600)
canvas.pack()
def ball():
canvas.delete('all')
global y
canvas.create_oval(x - 5,y - 5,x + 5,y + 5, fill = 'red')
y = y + 2
if y < 600:
canvas.after(2, ball)
y = 0
x=300
ball()
TL;DR: I want to repeat a def function in Python after a certain event.

It sounds like you just want to reset the y value:
import tkinter
canvas = tkinter.Canvas(width=600, height=600)
canvas.pack()
def ball():
canvas.delete('all')
global y
canvas.create_oval(x - 5,y - 5,x + 5,y + 5, fill = 'red')
y = y + 2
if y >= 600:
y = 0 # Restart y from 0 again
canvas.after(2, ball)
y = 0
x=300
ball()

I implement a situation with python to show you how you can use the while loop and also using return for your functions to exit the loop after some event.
It's just an example for better understanding the answer. And do not use global variables in your code. It's just for the example.
a = 100
def count_down():
global a
a -= 1
if a > 0:
return True
return False
in_the_loop = True
while(in_the_loop):
in_the_loop = count_down()
print(a)
Maybe this snippet will give some idea about your case to how to exit from the base function by simply adding return to that.

Related

Changing colors on turtle graphics on key press

I was writing a code on turtle and I got stuck in changing colors "onkey", using colorsys (hsv_to_rgb) with everything set but hue, I declared a variable for it. It was going all well till launch, it only changes once. Here's the code:
from turtle import *
from colorsys import *
pencolor('blue')
pensize(2)
bgcolor('black')
default_size = 0
h = 0
def move_up():
sety(ycor() + 50)
def move_left():
setx(xcor() - 50)
def move_right():
setx(xcor() + 50)
def move_down():
sety(ycor() - 50)
def change_color():
n = 0.1
c = hsv_to_rgb((h+n), 1, 1)
color(c)
def call_movement():
onkey(move_up, 'Up')
onkey(move_left, "Left")
onkey(move_right, "Right")
onkey(move_down, "Down")
listen()
def color_change():
onkey(change_color, "+")
listen()
call_movement()
color_change()
done()
I tried using while and set limit to h = 1, it works but not how I want it, it cycles but I want it to go once store the changes on the variable for the next time I use the key, creating a sort of a partial cycle or so
You are not ever setting the value of h, so it never changes.
Fix:
n = 0.1
...
def change_color():
global h
h += n
c = hsv_to_rgb(h, 1, 1)
color(c)

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.

Tkinter: remove keyboard double-click prevention

I'm programming a python version of Asteroids (https://en.wikipedia.org/wiki/Asteroids_(video_game)) using Tkinter.
This is the piece of code that let the ship to move ahead:
def move(self, sx=0, sy=0, ms=2):
try:
self.root.after_cancel(self.m)
except AttributeError:
pass
ms += 1
if ms > 30:
return
self.parent.move(self.ship, sx, sy)
self.m = self.root.after(ms, lambda sx1=sx, sy1=sy, millisec=ms: self.move(sx1, sy1, ms))
And here there's the actual fuction that runs when Up Arrow is pressed:
def avanti(self, event):
self.s = -2.5
x = self.s * math.sin(math.radians(self.angle)) * -1
y = self.s * math.cos(math.radians(self.angle))
self.move(x, y)
When you press UpArrow multiple times, the code works pretty well, the only problem is that when you hold it down, the ship moves once, then there's a small break like 0.2s long, and then it starts going ahead regularly, until you release the key. I think that the computer prevents you to double-click worngly and then, when it sees that you actually want to, it removes this prevenction.
Is there a way to remove this block since the first press?
EDIT: You can actually change the typing delay on the entire computed by going on Keyboard -> typing delay but what I want to do is to remove the delay only on the python program.
You can use <Up> to set move_up = True and <KeyRelease-Up> to set move_up = False and then you can use after to run function which will check move_up and move object.
Working example - code from other question (about moving platform)
import tkinter as tk
# --- constants ---
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 600
CENTER_X = DISPLAY_WIDTH//2
CENTER_Y = DISPLAY_HEIGHT//2
# --- functions ---
# for smooth move of platform
def up_press(event):
global platform_up
platform_up = True
def up_release(event):
global platform_up
platform_up = False
def down_press(event):
global platform_down
platform_down = True
def down_release(event):
global platform_down
platform_down = False
def eventloop():
# move platform
if platform_up:
# move
canvas.move(platform, 0, -20)
# check if not leave canvas
x1, y1, x2, y2 = canvas.coords(platform)
if y1 < 0:
# move back
canvas.move(platform, 0, 0-y1)
if platform_down:
# move
canvas.move(platform, 0, 20)
# check if not leave canvas
x1, y1, x2, y2 = canvas.coords(platform)
if y2 > DISPLAY_HEIGHT:
# move back
canvas.move(platform, 0, -(y2-DISPLAY_HEIGHT))
root.after(25, eventloop)
# --- main ---
# - init -
root = tk.Tk()
canvas = tk.Canvas(root, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT)
canvas.pack()
# - objects -
platform = canvas.create_rectangle(CENTER_X-15, CENTER_Y-15, CENTER_X+15, CENTER_Y+15, fill="green")
platform_up = False
platform_down = False
root.bind('<Up>', up_press)
root.bind('<KeyRelease-Up>', up_release)
root.bind('<Down>', down_press)
root.bind('<KeyRelease-Down>', down_release)
# - mainloop -
root.after(25, eventloop)
root.mainloop()

Tkinter and UnboundLocalError

I am trying to get the coordinates for each square on the board but the error UnboundLocalError: local variable 'x' referenced before assignment keeps showing up when I click on a square.
import tkinter
class RA:
def __init__(self):
self._columns = 8
self._rows = 8
self._root = tkinter.Tk()
self._canvas = tkinter.Canvas(master = self._root,
height = 500, width = 500,
background = 'green')
self._canvas.pack(fill = tkinter.BOTH, expand = True)
self._canvas.bind('<Configure>',self.draw_handler)
def run(self):
self._root.mainloop()
def draw(self):
self._canvas.create_rectangle(0,0,250,250,fill = 'blue',outline = 'white')
self._canvas.create_rectangle(250,250,499,499,fill = 'red',outline = 'white')
self._canvas.create_rectangle(499,499,250,250,fill = 'black',outline = 'white')
#
for c in range(self._columns):
for r in range(self._rows):
x1 = c * (column_width)#the width of the column
y1 = r * (row_height)
x2 = x1 + (column_width)
y2 = y1 + (row_height)
#3-5
def clicked(self,event: tkinter.Event):
x = event * x
y = event * y
rect = self._canvas.find_closest(x,y)[0]
coordinates = self._canvas.gettags(rect)
print(coordinates[0],coordinates[1])
def draw(self):
self._canvas.delete(tkinter.ALL)
column_width = self._canvas.winfo_width()/self._columns
row_height = self._canvas.winfo_height()/self._rows
for x in range(self._columns):
for y in range(self._rows):
x1 = x * column_width
y1 = y * row_height
x2 = x1 + column_width
y2 = y1 + row_height
r = self._canvas.create_rectangle(x1,y1,x2,y2,fill = 'blue')#,tag = (x,y))# added for the second time,
self._canvas.tag_bind(r,'<ButtonPress-1>',self.clicked)
self._canvas.create_rectangle(x1,y1,x2,y2)
self._canvas.bind('<Configure>',self.draw_handler)
def draw_handler(self,event):
self.draw()
r = RA()
r.run()
The problem is in these two lines:
def clicked(self,event: tkinter.Event):
x = event * x
You're using x on the right hand side of the expression, but x hasn't been defined anywhere. Also, you're going to have a problem because event is an object. Multiplying the object times some other number isn't likely going to do what you think it is going to do. Did you maybe intend to use event.x in the expression instead of event * x?
Getting the coordinates of the clicked item
Even though you specifically asked about the unbound local variable error, it appears you're attempting to get the coordinates of the item that was clicked on. Tkinter automatically gives the item that was clicked on the tag "current", which you can use to get the coordinates:
coordinates = self._canvas.coords("current")
From the official tk documentation:
The tag current is managed automatically by Tk; it applies to the
current item, which is the topmost item whose drawn area covers the
position of the mouse cursor (different item types interpret this in
varying ways; see the individual item type documentation for details).
If the mouse is not in the canvas widget or is not over an item, then
no item has the current tag.
This will happen with y as well. If you want x to be stored with the button, you'll need to create a Class for it, and store them as self.x and self.y. The clicked() event would be on the class itself, and then it will have access to it.
Looks like you used
x = event * x
y = event * y
when you probably wanted
x = event.x
y = event.y
The latter accesses the event object's x and y attributes instead of attempting to multiply it by an unbound variable.

Swinging pendulum does not work

I am having a little trouble with this project. I have to create a pendulum using key handles and the code I have for the key's up and down don't seem to be working. "up" is suppose to make the pendulum go faster and "down" makes it go slower. This is the code that I have so far. can somebody please help.
from tkinter import * # Import tkinter
import math
width = 200
height = 200
pendulumRadius = 150
ballRadius = 10
leftAngle = 120
rightAngle = 60
class MainGUI:
def __init__(self):
self.window = Tk() # Create a window, we may call it root, parent, etc
self.window.title("Pendulum") # Set a title
self.canvas = Canvas(self.window, bg = "white",
width = width, height = height)
self.canvas.pack()
self.angle = leftAngle # Start from leftAngle
self.angleDelta = -1 # Swing interval
self.delay = 200
self.window.bind("<Key>",self.key)
self.displayPendulum()
self.done = False
while not self.done:
self.canvas.delete("pendulum") # we used delete(ALL) in previous lab
# here we only delete pendulum object
# in displayPendulum we give the tag
# to the ovals and line (pendulum)
self.displayPendulum() # redraw
self.canvas.after(self.delay) # Sleep for 100 milliseconds
self.canvas.update() # Update canvas
self.window.mainloop() # Create an event loop
def displayPendulum(self):
x1 = width // 2;
y1 = 20;
if self.angle < rightAngle:
self.angleDelta = 1 # Swing to the left
elif self.angle > leftAngle:
self.angleDelta = -1 # Swing to the right
self.angle += self.angleDelta
x = x1 + pendulumRadius * math.cos(math.radians(self.angle))
y = y1 + pendulumRadius * math.sin(math.radians(self.angle))
self.canvas.create_line(x1, y1, x, y, fill="blue", tags = "pendulum")
self.canvas.create_oval(x1 - 2, y1 - 2, x1 + 2, y1 + 2,
fill = "red", tags = "pendulum")
self.canvas.create_oval(x - ballRadius, y - ballRadius,
x + ballRadius, y + ballRadius,
fill = "green", tags = "pendulum")
def key(self,event):
print(event.keysym)
print(self.delay)
if event.keysym == 'up':
print("up arrow key pressed, delay is",self.delay)
if self.delay >10:
self.delay -= 1
if event.keysym == 'Down':
print("Down arrow key pressed,delay is",self.delay)
if self.delay < 200:
self.delay += 1
if event.keysym=='q':
print ("press q")
self.done = True
self.window.destroy()
MainGUI()
The root of the problem is that the event keysym is "Up" but you are comparing it to the all-lowercase "up". Naturally, the comparison fails every time. Change the if statement to if event.keysym == 'Up':
Improving the animation
In case you're interested, there is a better way to do animation in tkinter than to write your own infinite loop. Tkinter already has an infinite loop running (mainloop), so you can take advantage of that.
Create a function that draws one frame, then have that function arrange for itself to be called again at some point in the future. Specifically, remove your entire "while" loop with a single call to displayFrame(), and then define displayFrame like this:
def drawFrame(self):
if not self.done:
self.canvas.delete("pendulum")
self.displayPendulum()
self.canvas.after(self.delay, self.drawFrame)
Improving the bindings
Generally speaking, your code will be marginally easier to manage and test if you have specific bindings for specific keys. So instead of a single catch-all function, you can have specific functions for each key.
Since your app supports the up key, the down key, and the "q" key, I recommend three bindings:
self.window.bind('<Up>', self.onUp)
self.window.bind('<Down>', self.onDown)
self.window.bind('<q>', self.quit)
You can then define each function to do exactly one thing:
def onUp(self, event):
if self.delay > 10:
self.delay -= 1
def onDown(self, event):
if self.delay < 200:
self.delay += 1
def onQuit(self, event):
self.done = True
self.window.destroy()

Categories

Resources