Is there a function to terminate after loops in tkinter? - python

I'm creating a programme using tkinter and python that tests your words per minute.
I've created a loop that starts when you hit the start game button to keep checking the words typed and to highlight words green if inputted correctly.
After a minute I want the loop to end and for labels to show the total words per minute and errors made. When the labels are shown they're stuck in a loop and keep creating labels over and over again.
I've tried a 'running' boolean to end it, I've tried adding a condition to stop the loop if a variable I've added exceeds 60. I've tried separating it and playing with indentation but nothing seems to be working.
Is there a function or something else that can be used to end the loop? I'm struggling to find a solution.
note: I've set the game to end after 10 seconds, not 60, so that it's easier to test out.
from text import text_list
from tkinter import *
window = Tk()
sample_text = (" ".join(text_list))
text_window = Text(window, height=10, width=99, wrap=WORD)
text_window.insert(INSERT, sample_text)
text_window.configure(state=DISABLED)
text_window.pack()
type_input = Entry(window, width=99)
type_input.pack()
def end_game():
global running
running = False
type_input.configure(state=DISABLED)
user_type = type_input.get()
users_input = user_type.split(' ')
WPM = 0
errors = 0
for words in users_input:
if words in text_list:
WPM += 1
else:
errors += 1
show_results(WPM, errors)
def show_results(result, mistakes):
final_wpm = Label(window, text=f"Time's up! Your typing speed is {result} words per minute.")
final_wpm.pack()
final_errors = Label(window, text=f'Total errors made: {mistakes}')
final_errors.pack()
time_spent = 0
def start_game():
global running
global time_spent
running = True
time_spent += 0.5
if running:
user_type = type_input.get()
users_input = user_type.split(' ')
for words in users_input:
if words in text_list:
countVar = StringVar()
pos = text_window.search(words, "1.0", stopindex="end", count=countVar)
end_point = pos + f"+{countVar.get()}c"
text_window.tag_configure("search", background="green")
text_window.tag_add("search", pos, end_point)
else:
continue
if time_spent < 10:
window.after(500, start_game)
window.after(1000 * 10, end_game)
running = False
start_button = Button(window, text='Start Game', command=start_game)
start_button.pack()
window.mainloop()

Related

Why exec function in python adds '.!' at the beginning of the text?

I am currently working on a hangman game using tkinter in python.
When I click a button of the letter and it is in the word that we are guessing it should show the letter. But when I click the button this problem is popping up:
This example is only with one button. People say that this problem is because of the mainloop(), but i have no idea how to fix it.
from tkinter import *
from tkinter import messagebox
from generate_word import word
#DEFAULT VALUES
score = 0
count = 0
win_count = 2
WINDOW_BG = '#e5404e'
WINDOW_SIZE = '1200x870+300+80'
FONT = ('Arial', 40)
from tkinter import *
from tkinter import messagebox
from generate_word import word
#DEFAULT VALUES
score = 0
count = 0
win_count = 2
WINDOW_BG = '#e5404e'
WINDOW_SIZE = '1200x870+300+80'
FONT = ('Arial', 40)
#this is an example with only one button
buttons = [['b1','a',80,740]]
#Creating window and configurating it
window = Tk()
window.geometry(WINDOW_SIZE)
window.title('Hangman')
window.config(bg = WINDOW_BG)
#generates all of the labels for the word
def gen_labels_word():
label = Label(window, text = " ", bg = WINDOW_BG, font = FONT)
label.pack( padx = 40,pady = (500,100),side = LEFT)
label1 = Label(window, text = word[0], bg = WINDOW_BG, font = FONT)
label1.pack( padx = 41,pady = (500,100),side = LEFT)
x = 21
for var in range(1,len(word)):
exec('label{}=Label(window,text="_",bg=WINDOW_BG,font=FONT)'.format(var))
exec('label{}.pack(padx = {}, pady = (500,100), side=LEFT)'.format(var,x))
x += 1
exec('label{} = Label(window, text = "{}", bg = WINDOW_BG, font = FONT)'.format(len(word),word[-1]))
exec('label{}.pack( padx = {},pady = (500,100),side = LEFT)'.format(len(word), x+1))
# ---------------------------------------------------------------------------------------------------------------------------------------------------
gen_labels_word()
#----------------------------------------------------------------------------------------------------------------------------------------------------
#letters icons(images)
#hangman (images)
hangman = ['h0','h1','h2','h3','h4','h5','h6']
for var in hangman:
exec(f'{var}=PhotoImage(file="{var}.png")')
han = [['label0','h0'],['label1','h1'],['label2','h2'],['label3','h3'],['label4','h4'],['label5','h5'],['label6','h6']]
for p1 in han:
exec('{}=Label(window, bg = WINDOW_BG ,image={})'.format(p1[0],p1[1]))
exec('label0.place(x = 620,y = 0)')
for var in letters:
exec(f'{var}=PhotoImage(file="{var}.png")')
for var in buttons:
exec(f'{var[0]}=Button(window,bd=0,command=lambda: game_brain("{var[0]}","{var[1]}"),bg = WINDOW_BG,font=FONT,image={var[1]})')
exec('{}.place(x={},y={})'.format(var[0],var[2],var[3]))
def game_brain(button, letter):
global count,win_count,score
exec('{}.destroy()'.format(button))
if letter in word:
for i in range(1,len(word)):
if word[i] == letter:
win_count += 1
exec(f'label{i}.config(text="{letter}")')
if win_count == len(word):
score += 1
messagebox.showinfo('GOOD JOB, YOU WON!\n GOODBYE!')
window.destroy()
else:
count += 1
exec('label{}.destroy()'.format(count-1))
exec('label{}.place(x={},y={})'.format(count,620,0))
if count == 6:
messagebox.showinfo('GAME OVER','YOU LOST!\nGOODBYE!')
window.destroy()
def EXIT():
answer = messagebox.askyesno('ALERT','Do you want to exit the game?')
if answer == True:
window.destroy()
e1 = PhotoImage(file = 'exit.png')
ex = Button(window,bd = 0,command = EXIT,bg = WINDOW_BG,font = FONT,image = e1)
ex.place(x=1050,y=20)
#-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
window.mainloop()
Why exec function in python adds '.!' at the beginning of the text?
The exec function isn't doing that. Tkinter by default names all of its widgets with a leading exclamation point. When you print out a widget, it has to be converted to a string. For tkinter widgets, the result of str(some_widget) is the widget's name.
You can see this quite easily without exec:
import tkinter as tk
root = tk.Tk()
label = tk.Label(root)
print(label)
The above will print something like .!label. If you create a second label it will be .!label2, a third will be .!label3 and so on.
On an unrelated note, you shouldn't be using exec to create widgets. It makes the code very hard to understand and debug. If you want to create widgets in a loop, add them to a dictionary or list instead of dynamically creating variables with exec.
For example:
labels = {}
for var in range(1,len(word)):
label = Label(window,text="_",bg=WINDOW_BG,font=FONT)
label.pack(padx=500, pady=100)
labels[var] = label
With that, you can later reference the widgets as labels[1], labels[2], etc.
You should do the same thing with the images, or anything else that you create in a loop and want to keep track of.

Validate function calls a other function too many times

Hi i am writing simple typing sped in test in python using tkinter for gui, but i ran into a problem. I want the text in label to change every time the user writes the tenth sentence in entry widget, but when i wrote tenth sentence text in label is changing for example when this word has 5 letters text is changing 5 times. I know why it's hapenning but i don't know how to fix it. thanks.
import tkinter as tk
from tkinter.constants import END
import tkinter.font as font
import random, sys
start = False
# configure tkinter windows
root = tk.Tk()
root.geometry("800x600")
# set window as not resizable
root.resizable(height=False, width=False)
root.title("typing speed test")
# create fonts
myFont = font.Font(size=30)
txtFont = font.Font(size=40)
#this font is needed to entry widget
midFont = font.Font(size=20)
def get_words():
"""
this function open's file and returns
list with content from it
"""
filename = "words.txt"
try:
with open(filename, 'r') as f:
contents = f.read()
#return 10 random words from list
num = random.randrange(0, 990)
contents = contents.split()
return contents[num:num+10]
except FileNotFoundError:
sys.exit("file not founded")
def countdown(count):
"""
this function create timer in tkinter
window
"""
global start
start = True
# change text and font in label
countdown_label['font'] = myFont
countdown_label['text'] = count
if count > 0:
# call countdown again after 1000ms (1s)
root.after(1000, countdown, count-1)
if count <= 10:
# change color of texts
countdown_label["fg"] = "#f00"
if count == 0:
# when count equal 0 disable entry widget
e.config(state='disabled')
#create label
textlabel = tk.Label(root, width=60, height=10, font=midFont, text = "siema")
#add text to label
textlabel.config(text=get_words())
def onValidate(P):
"""
Tkinter validate function
"""
global words
words = P.split()
if P.isalpha() and start == False:
# start counting when user starts
countdown(100)
if len(words) % 10 == 0 :
textlabel.config(text=get_words())
return True
def comapreLists():
"""
This function compare two
lists and return set with
word(s) if it exist in l0
lists
"""
#this list stores words from entry widget
l0 = words
#this list stores words from file
l2 = get_words()
# compare lists
dn = set(l0).intersection(l2)
# return values
return dn
#it's button created only for debug
b = tk.Button(root, text='COMP', command=comapreLists)
#register validate func
vcmd = (root.register(onValidate), '%P')
#create label for timer
countdown_label = tk.Label(root)
#create entry window
e = tk.Entry(validate="key", validatecommand=vcmd,
width=30, font=myFont, state='normal')
#pack elements
countdown_label.pack()
textlabel.pack()
b.pack()
e.place(anchor='center', x=399, y=500)
root.mainloop()
You can use another global variable to store the current number of words entered, then update textLabel only if current number of words not equal to saved one.
last_cnt = 0
def onValidate(P):
"""
Tkinter validate function
"""
global words, last_cnt
if P.isalpha() and start == False:
# start counting when user starts
countdown(100)
words = P.split()
cnt = len(words)
if cnt and (cnt % 10 == 0) and (cnt != last_cnt):
textlabel.config(text=get_words())
last_cnt = cnt
return True

Text Game to Tkinter GUI [duplicate]

This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 1 year ago.
I'm attempting to make a GUI for my text game (similar to a pokemom game)
In summary the code works like:
import random
hp = 100
for i in range (0,4):
element = input("Enter attack: ")
list.append(element)
attack1 = list[0]
attack2 = list[1]
attack3 = list[2]
attack4 = list[3]
while(True):
cmd = input(">")
If cmd.lower() == attack1 ... :
if cmd.lower() == bite:
hp -= 10
Now I tried doing a gui on Tkinter through buttons but it seems I can't get a grasp of it
from tkinter import *
#4 buttons
bite = 0
root = Tk()
def click(hp):
hp -= 10
myLabel = Label(text=f"{hp} hp remaining left.")
myLabel.grid(row = 5, column = 5)
def click2():
bite = 0
hp = 50
hp -= 20
myLabel = Label(text=f"{hp} hp remaining left.")
myLabel.grid(row = 5, column = 5)
bite += 1
myButton = Button(root, text=f"hello {bite}/5", command = click(hp))
myButton2 = Button(root, text=f"Presssss {bite}/5", command = click2)
myButton3 = Button(root, text="Presssss")
myButton4 = Button(root, text="Presssss")
myButton.grid(row = 0, column = 1)
myButton2.grid(row = 1, column = 1)
myButton3.grid(row = 1, column = 2)
myButton4.grid(row = 0, column = 2)
root.mainloop()
And this just presents a constant value "90, 30" due to the variables being included on the function, but whenever I'm trying to put it into an argument like
hp = 100
def click(hp):
hp -= 10
button1 = Button(root, text = "try", command = click(100))
It just returns the value 90, 30 way before the button is clicked. When hp is used as an arg, it is saying undefined.
It does so because click() function is called as soon as window is created.
To prevent it you can use lambda :
myButton = Button(root, text=f"hello {bite}/5", command = lambda: click(hp))

"name is not defined" inside function inside condition

When I run the code below, and press the 'go' button, I get the error:
NameError: name 'be' is not defined
...on the following line in the countdown function:
if be >= 0:
Even if I add global be in the function countdown, I get the same error.
import tkinter as tk
def set1():
global be
if be1 is not '':
be = int(en.get())
def countdown():
global be
if be >= 0:
mins, secs = divmod(be, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
label['text'] = timer
root.after(1000, countdown)
be -= 1
root = tk.Tk()
label = tk.Label(root, text = '00:00', font = 'Helvetica 25')
label.pack()
en = tk.Entry(root)
en.pack()
be1 = en.get()
tk.Button(root, text = 'set', height = 3, width = 20, command = lambda: set1()).pack()
tk.Button(root, text = 'go', height = 3, width = 20, command = lambda: countdown()).pack()
root.mainloop()
Why doesn't global be resolve the problem?
The reason is clear from the error message: you need to define be as a global variable. Note that the global statement does not define the variable, it merely links the local references to that name with an existing global variable. But if it doesn't exist, there is nothing to match it with.
So you should define be as a global variable, like:
be = 0
There are a few other issues and things to improve:
be1 is read from the input only once, when the script starts, not when the user clicks a button. So its value will always remain the empty string. Instead, you should read the input into be1 when the "set" button is clicked, and so the variable be1 can just be a local variable in that set1 function, not global as it is now.
Don't use is not (or is) when comparing a variable with a string literal. So you should have:
if be1 != '':
It is better to already display the updated start-time when the user clicks the "set" button, even before the user clicks "go" -- otherwise they don't see the effect of clicking "set".
Since you already have code for displaying the timer in the countdown function, it would be good to avoid code duplication and make a new function just for doing the displaying.
So taking all that together you would get:
import tkinter as tk
be = 0 # define as global here
def display(): # new function to avoid code repetition
global be
mins, secs = divmod(be, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
label['text'] = timer
def set1():
global be
be1 = en.get() # local variable, reading fresh input value
if be1 != '': # don't use "is not" with a literal
be = int(be1)
display()
def countdown():
global be
if be >= 0:
display()
root.after(1000, countdown)
be -= 1
root = tk.Tk()
label = tk.Label(root, text = '00:00', font = 'Helvetica 25')
label.pack()
en = tk.Entry(root)
en.pack()
tk.Button(root, text = 'set', height = 3, width = 20, command = lambda: set1()).pack()
tk.Button(root, text = 'go', height = 3, width = 20, command = lambda: countdown()).pack()
root.mainloop()

Keep track of score

I am trying to make a quiz that shows the user the name of the state and they have to correctly state the capital. Everything works fine, except for keeping track of the user's score. I have tried to change around the score portion of the code, but nothing is working! I think the problem is somewhere in the nextCapital() function, but then again I could be wrong. I am new to python and all of this is a little overwhelming. I would really appreciate the help!
import tkinter
import random
capitals={"Washington":"Olympia","Oregon":"Salem",\
"California":"Sacramento","Ohio":"Columbus",\
"Nebraska":"Lincoln","Colorado":"Denver",\
"Michigan":"Lansing","Massachusetts":"Boston",\
"Florida":"Tallahassee","Texas":"Austin",\
"Oklahoma":"Oklahoma City","Hawaii":"Honolulu",\
"Alaska":"Juneau","Utah":"Salt Lake City",\
"New Mexico":"Santa Fe","North Dakota":"Bismarck",\
"South Dakota":"Pierre","West Virginia":"Charleston",\
"Virginia":"Richmond","New Jersey":"Trenton",\
"Minnesota":"Saint Paul","Illinois":"Springfield",\
"Indiana":"Indianapolis","Kentucky":"Frankfort",\
"Tennessee":"Nashville","Georgia":"Atlanta",\
"Alabama":"Montgomery","Mississippi":"Jackson",\
"North Carolina":"Raleigh","South Carolina":"Columbia",\
"Maine":"Augusta","Vermont":"Montpelier",\
"New Hampshire":"Concord","Connecticut":"Hartford",\
"Rhode Island":"Providence","Wyoming":"Cheyenne",\
"Montana":"Helena","Kansas":"Topeka",\
"Iowa":"Des Moines","Pennsylvania":"Harrisburg",\
"Maryland":"Annapolis","Missouri":"Jefferson City",\
"Arizona":"Phoenix","Nevada":"Carson City",\
"New York":"Albany","Wisconsin":"Madison",\
"Delaware":"Dover","Idaho":"Boise",\
"Arkansas":"Little Rock","Louisiana":"Baton Rouge"}
score=0
timeleft=30
print("This program will launch a capital quiz game.")
input1 = input("What difficulty would you like to play: easy, normal, or hard?\n")
if input1.lower() == "easy":
seconds = 90
timeleft = seconds
elif input1.lower() == "normal":
seconds = 60
timeleft = seconds
elif input1.lower() == "hard":
seconds = 30
timeleft = seconds
def startGame(event):
#if there's still time left...
if timeleft == seconds:
#start the countdown timer.
countdown()
#run the function to choose the next colour.
nextCapital()
if timeleft == 0:
endlabel = tkinter.Label(root, text="The time is up!\nYour score is: " + str(score) +" out of 50", font=('Helvetica', 12))
endlabel.pack()
e.pack_forget()
#function to choose and display the next colour.
def nextCapital():
#use the globally declared 'score' and 'play' variables above.
global score
global timeleft
#if a game is currently in play...
if timeleft > 0:
#...make the text entry box active.
e.focus_set()
randchoice = random.choice(list(capitals.keys()))
answer = capitals.get(randchoice)
if answer.lower() == randchoice.lower():
score = score+1
#### #this deletes the random choice from the dictionary
#### del capitals[randchoice]
#clear the text entry box.
e.delete(0, tkinter.END)
#this updates the random choice label
label.config(text=str(randchoice))
#update the score.
scoreLabel.config(text="Score: " + str(score))
#a countdown timer function.
def countdown():
#use the globally declared 'play' variable above.
global timeleft
#if a game is in play...
if timeleft > 0:
#decrement the timer.
timeleft -= 1
#update the time left label.
timeLabel.config(text="Time left: " + str(timeleft))
#run the function again after 1 second.
timeLabel.after(1000, countdown)
#create a GUI window.
root = tkinter.Tk()
#set the title.
root.title("Capital Quiz")
#set the size.
root.geometry("500x250")
#add an instructions label.
instructions = tkinter.Label(root, text="Brush up your geography skills!", font=('Helvetica', 12))
instructions.pack()
#add a score label.
scoreLabel = tkinter.Label(root, text="Press enter to start" + str(score), font=('Helvetica', 12))
scoreLabel.pack()
#add a time left label.
timeLabel = tkinter.Label(root, text="Time left: " + str(timeleft), font=('Helvetica', 12))
timeLabel.pack()
#prompt label
promptLabel = tkinter.Label(root, text= "Enter the capital of: ", font=('Helvetica', 12))
promptLabel.pack()
#add a label that will hold print the prompt
label = tkinter.Label(root, font=('Helvetica', 60))
label.pack()
#add a text entry box for typing in colours.
e = tkinter.Entry(root)
#run the 'startGame' function when the enter key is pressed.
root.bind('<Return>', startGame)
e.pack()
#set focus on the entry box.
e.focus_set()
#start the GUI
root.mainloop()
randchoice is one of the keys in the capitals dict, i.e. a State.
answer is one of the values in the capitals dict, i.e. a Capital
You then compare the lowercase versions of randchoice and answer and increment the score if they are equal. But clearly they will never be equal (one is a State, one is a Capital). So your score won't be updated properly.
Not an answer, but a bit of advice: eschew all global variables, wrap things into an object with clearly defined, short methods. You will have much easier time working with and reasoning about the code.
Consider this skeleton of a class:
class Quiz(object):
def __init__(self, difficulty, capitals_dict):
self.score = 0
self.capitals = capitals
self.difficulty = ...
self.right_answer = None
def getNextQuestion(self):
# Choose a capital and assign it as the current right answer
...
self.right_answer = ...
return self.right_answer # to show it to user
def checkAnswer(user_answer):
if user_answer == self.right_answer:
self.score = ...
return True
else:
...
def isGameOver(self):
return len(self.capitals) == 0
I guess it's clear enough how to use a class like this, and reasonably clear how to implement it.

Categories