Calling a function within a function causing problems in execution - python-3.x

I am building a snake game using Tkinter only, and I managed to make a working game. The code works perfectly, until I added a start menu to show up before the game. To do this, I encased all my functions within one function called play(), and when I run the code, a menu shows up as expected, and when clicked play the window closes and the game itself shows up.
However, the keybinds for left/right/up/down to control the snake no longer works. I have checked that I have called the correct functions within my mainloop, and I have also tried moving it outside of the mainloop but it throws a referenced before assigned error.
Is there a logic error that I am missing? I am a beginner to Python and the Tkinter module so that is quite likely. Here is my code (where I suspect the problem is):
def play():
#some other functions including moveSnake and placeFood
def leftKey(event):
global direction
direction = "left"
def rightKey(event):
global direction
direction = "right"
def upKey(event):
global direction
direction = "up"
def downKey(event):
global direction
direction = "down"
snake = []
snakeSize = 15
snake.append(canvas.create_rectangle(snakeSize,snakeSize, snakeSize * 2, snakeSize * 2, fill="white", outline = ""))
score = 0
txt = "Score:" + str(score)
scoreText = canvas.create_text(width/2, 20, fill="white", font="Helvetica 20 bold", text=txt)
canvas.bind("<Left>", leftKey)
canvas.bind("<Right>", rightKey)
canvas.bind("<Up>", upKey)
canvas.bind("<Down>", downKey)
canvas.focus_set()
direction = "right"
placeFood()
moveSnake()
window.mainloop()
#new window/start menu
window1 = Tk()
window1.geometry('600x800')
window1.title("Snake")
#play button
playButton = Button(window1,text="Play", command = play)
playButton.pack(side=TOP)
#leaderboard button
leaderboardButton = Button(window1,text="Leaderboard", command = viewleaderboard)
leaderboardButton.pack(side=TOP)
#settings button
playButton = Button(window1,text="Settings", command = viewsettings)
playButton.pack(side=TOP)
window1.mainloop() ```

I managed to fix it without needing to create a class. I moved my keybinds to my main script, right below I created window1. Then I reassigned the bind to the 'window', opposed to 'canvas'. The purpose being that the keybinds were not being called to the correct place. I also add global score and global direction, to avoid it being unrecognised. This fixed my code, though I'm sure it can be optimised much further. Hope it helps out another newbie like me!
** To clarify, 'window' is my original window in which the game lies in and is defined in a function which I have not listed, it is separate to window1 **
global score
score = 0
txt = "Score:" + str(score)
scoreText = canvas.create_text(width/2, 20, fill="white", font="Helvetica 20 bold", text=txt)
window.bind("<Left>", leftKey)
window.bind("<Right>", rightKey)
window.bind("<Up>", upKey)
window.bind("<Down>", downKey)
window.focus_set()
global direction
direction = "right"

Related

Tkinter: displaying label with automatically updated variable

I know there are a lot of questions about this subject, but after long researches I didn't find any that could solve my problem.
I'm trying to display with a label (using tkinter) a variable that I get from the I²C bus. The variable is therefore updated very regularly and automatically. The rest of the window should stay available for the user.
For now, the only way I found to display the label with the updated variable and to keep the rest of the window available for the user is to do so:
window = tk.Tk()
window.title("Gestionnaire de périphériques")
window.minsize(1024,600)
labelValThermo = tk.Label(a_frame_in_the_main_window,text = "")
labelValThermo.grid(row = 1, column = 1)
while True:
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
window.update()
time.sleep(0.75)
The variable that comes from the I²C and got updated is mcp.get_hot_junction_temperature
The fact is that I know it's not the best way to force the update in an infinite loop. This should be the role of the mainloop(). I found out that the after() method could solve my problem but I don't know how to run it. I tried the following code that didn't work:
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
labelValThermo.after(500,displayThermoTemp)
window = tk.Tk()
labelValThermo = tk.Label(thermoGraphFrame,text = "")
labelValThermo.after(500, displayThermoTemp)
labelValThermo.grid(row = 1, column = 1)
window.mainloop()
Does anyone have the right syntax ?
How To use after() ?
The after() calls the function callback after the given delay in ms. Just define it inside the given function and it'll run just like a while loop till you call after_cancel(id) .
Here is an example:
import tkinter as tk
Count = 1
root = tk.Tk()
label = tk.Label(root, text=Count, font = ('', 30))
label.pack()
def update():
global Count
Count += 1
label['text'] = Count
root.after(100, update)
update()
root.mainloop()
Update your function with this and call it once before mainloop().
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
labelValThermo.after(500,displayThermoTemp)
# 100ms = 0.1 secs
window(100, displayThermoTemp)
after has the following syntax:
after(delay_ms, callback=None, *args)
You need to place the command in your callback function and update the window on which the widget is (in your case the labelValThermo looks to be on the root window:
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
root.after(500,displayThermoTemp)

free line drawing on tkinter canvas

I was trying to understand the following piece of Tkinter code that allows a user to freely draw on the canvas using the computer's mouse. I was however unable to understand what the following line of code is actually doing.
prev = move_event
here is the complete code ...
from tkinter import *
master = Tk()
canvas = Canvas(master, width=600, height=300, bg='white')
canvas.pack(padx=20, pady=20)
def click(click_event):
global prev
prev = click_event
def move(move_event):
global prev
canvas.create_line(prev.x, prev.y, move_event.x, move_event.y, width=2)
prev = move_event # what does this do ?
canvas.bind('<Button-1>', click)
canvas.bind('<B1-Motion>', move)
mainloop()
In the click function, a global variable is used to store the initial mouse click. The same variable is used in the move function as a reference to the starting point of the line being drawn. As the user moves the mouse the move function gets called repeatedly and the drawing continues from the last previous point (prev = move_event). When the user releases the mouse click and then re-clicks, the process begins again with the click function storing the initial point.

Why does tkinter sometimes work without mainloop in Python 3?

Here's code for a little game. You're supposed to hit buttons that say 'click' to score points and avoid hitting buttons that say 'clack' or 'cluck' which cause you to lose points. The first weird thing is that the program works fine under IDLE even though it doesn't include a call to mainloop. The second is that it stops working if we add the following line at the bottom:
input('Hit enter to continue')
No game window appears, even after we hit enter. Adding a different line at the end like
print('hello')
does not have this effect.
Can anyone explain to me what's going on? I know that GUI programs used to run under IDLE's own mainloop, but that was a long time ago in Python 2 and definitely wasn't generally true under Python 3, at least last time I checked.
from tkinter import *
import random
score = 0
root = Tk()
scoreFrame = Frame(root)
scoreFrame.pack(expand = YES, fill = BOTH)
scoreLabel = Label(scoreFrame)
scoreLabel.pack()
def showScore():
scoreLabel['text'] = 'Score: {0}'.format(score)
scoreLabel.pack()
clickFrame = Frame(root)
clickFrame.pack(side = BOTTOM, expand = YES, fill = BOTH)
def changeLabels():
for button in buttons:
button['text'] = random.choice(['click', 'clack', 'cluck'])
button['bg'] = buttonDefaultColor
root.after(1500, changeLabels)
def makeButton():
button = Button(clickFrame)
def cmd():
global score
if button['bg'] == buttonDefaultColor:
if button['text'] == 'click':
score += 10
button['bg'] = 'light green'
else:
score -= 10
button['bg'] = 'light yellow'
button['command'] = cmd
button.pack(side = LEFT, expand = YES, fill = BOTH)
return button
buttons = [makeButton() for i in range(5)]
buttonDefaultColor = buttons[0]['bg']
changeLabels()
showScore()
Added: The first three comments all suggest that IDLE is running the event loop for me, making mainloop unnecessary but (1) I remember clearly when this suddenly stopped being true some years back (not that IDLE stopped running mainloop, but that GUI programs running under it did not need to run mainloop themselves) and (2) no one has explained why the input statement at the end breaks the program.

Stopping, restarting and changing variables in a thread from the main program (Python 3.5)

I'm very new to threading am and still trying to get my head around how to code most of it. I am trying to make what is effectively a text editor-type input box and so, like every text editor I know, I need a cursor-bar thing to indicate the location at which the text is being typed to. Thus I also want to be able to flicker/blink the cursor, which i thought would also prove good practice for threading.
I have a class cursor that creates a rectangle on the canvas based on the bounding box of my canvas text, but I then need to change it's location as more characters are typed; stop the thread and instantaneously hide the cursor rectangle when the user clicks outside of the input box; and lastly restart the thread/a loop within the thread (once again, sharing a variable) - the idea here being that the cursor blinks 250 times and after then, disappears (though not necessary, I thought it would make a good learning exercise).
So assuming that I have captured the events needed to trigger these, what would be the best way to go about them? I have some code, but I really don't think it will work, and just keeps getting messier. My idea being that the blinking method itself was the thread. Would it be better to make the whole class a thread instead? Please don't feel restricted by the ideas in my code and feel free to improve it. I don't think that the stopping is working correctly because every time I alt+tab out of the window (which i have programmed to disengage from the input box) the Python shell and tkinter GUI stop responding.
from tkinter import *
import threading, time
class Cursor:
def __init__(self, parent, xy):
self.parent = parent
#xy is a tuple of 4 integers based on a text object's .bbox()
coords = [xy[2]] + list(xy[1:])
self.obj = self.parent.create_rectangle(coords)
self.parent.itemconfig(self.obj, state='hidden')
def blink(self):
blinks = 0
while not self.stop blinks <= 250:
self.parent.itemconfig(self.obj, state='normal')
for i in range(8):
time.sleep(0.1)
if self.stop: break
self.parent.itemconfig(self.obj, state='hidden')
time.sleep(0.2)
blinks += 1
self.parent.itemconfig(self.obj, state='hidden')
def startThread(self):
self.stop = False
self.blinking = threading.Thread(target=self.blink, args=[])
self.blinking.start()
def stopThread(self):
self.stop = True
self.blinking.join()
def adjustPos(self, xy):
#I am not overly sure if this will work because of the thread...
coords = [xy[2]] + list(xy[1:])
self.parent.coords(self.obj, coords)
#Below this comment, I have extracted relevant parts of classes to global
#and therefore, it may not be completely syntactically correct nor
#specifically how I initially wrote the code.
def keyPress(e):
text = canvas.itemcget(textObj, text)
if focused:
if '\\x' not in repr(e.char) and len(e.char)>0:
text += e.char
elif e.keysym == 'BackSpace':
text = text[:-1]
canvas.itemconfig(textObj, text=text)
cursor.adjustPos(canvas.bbox(textObj))
def toggle(e):
if cursor.blinking.isAlive(): #<< I'm not sure if that is right?
cursor.stopThread()
else:
cursor.startThread()
if __name__=="__main__":
root = Tk()
canvas = Canvas(root, width=600, height=400, borderwidth=0, hightlightthickness=0)
canvas.pack()
textObj = canvas.create_text(50, 50, text='', anchor=NW)
root.bind('<Key>', keyPress)
cursor = Cursor(canvas, canvas.bbox(textObj))
#Using left-click event to toggle thread start and stop
root.bind('<ButtonPress-1', toggle)
#Using right-click event to somehow restart thread or set blinks=0
#root.bind('<ButtonPress-3', cursor.dosomething_butimnotsurewhat)
root.mainloop()
If there is a better way to do something written above, please also tell me.
Thanks.

I'm new to coding. A while loop in Python won't work properly, but has no errors

I got this code from a book called "Python for Kids," by Jason Briggs. This code was ran in Python 3.4.3. I don't have any outside coding experience outside from this book. I tried multiple possibilities to fix this and looked online, but I couldn't find anything that would work or help my problem. If you have new code or edits for this code, that would be helpful to me continuing to learn Python.
from tkinter import *
import random
import time
class Game:
def __init__(self):
self.tk = Tk()
self.tk.title("Mr. Stick Man Races for The Exit")
self.tk_resizable(0, 0)
self.tk.wm_attributes("-topmost", 1)
self.canvas = Canvas(self.tk, width=500, height=500, highlightthickness=0)
self.canvas.pack()
self.tk.update()
self.canvas_height = 500
self.canvas_width = 500
self.bg = PhotoImage(file="Wallpaper.gif")
w = self.bg.width()
h = self.bg.height()
for x in range(0, 5):
for y in range(0, 5):
self.canvas.create_image(x * w, y * h, image=self.bg, anchor='nw')
self.sprites = []
self.running = True
def mainloop(self):
while 1:
if self.running == True:
for sprite in self.sprites:
sprites.move()
self.tk.update_idletasks()
self.tk.update()
time.sleep(0.01)
g = Game()
g.mainloop()
This code was supposed to make a window with a wallpaper I created in Gimp to fill the window. When I ran the code, nothing happened and no errors appeared. What I need help on is making a window with my wallpaper appear. If you can help, can you give me an explanation with code. I'm sorry if my mistakes are obvious.
These two statements need to be all the way to the left, with no indentation:
g = Game()
g.mainloop()
The code class Game: creates a class, which can be thought of as a recipe for how to create a game. It does not actually create the game, it only provides code to create the game.
In order to actually create the game -- called instantiatiation -- you need to call Game as if it was a function. When you do g = Game() you are actually creating the game object and saving a reference to it. Unless you do this, the game will never be created. Thus, to create a instance of the game, you must define it in one step (class Game()) and create it in another (g = Game())
Warning, there are other problems in the code. This answers your specific question, but you need to fix the indentation of the def mainloop statemen, and there may be other issues.
The biggest problem is that this simply isn't the right way to do animation in Tkinter. It might be ok as a learning tool, but ultimately this is simply not proper usage of tkinter. I don't know if this code is straight from the book or if this is code you're trying on your own, but tkinter simply isn't designed to work this way. You shouldn't be creating your own mainloop function because tkinter has one that is built in.
To fix the mainloop issue, remove the existing mainloop function, and add this in its place (properly indented under class Game():
def mainloop(self):
# start the animation
self.animate()
# start the event loop
self.tk.mainloop()
def animate(self):
if self.running == True:
for sprite in self.sprites:
sprites.move()
self.tk.after(10, self.animate)

Resources