restarting a mainloop in tkinter - python-3.x

I'm new to Python and Tkinter. Here is a game I made for practice with GUIs. The problem I am having is when restarting a new game the code allows the loser to give the first guess. This is an unintended plus that I can't explain. My code is intended to give player 1 the first guess always, but that doesn't happen. I have done my due diligence and think the problem is in the transition between the reset_game method and the player_guess method.
Please describe why my code is not giving player1 the first guess when restarting the game.
If there are any comments as to format, structure, DRY issues, etc. I would appreciate those also.
''' 2 player guessing game with GUI'''
from tkinter import *
from random import randint
import tkinter.messagebox
# create window
root = Tk()
root.title('2 Player Guessing Game')
target_number = randint(1, 100)
message = StringVar()
current_player = ''
# reset target number for a new game
def reset_target_number():
global target_number
target_number = randint(1, 100)
# sets current player variable
def set_current_player(num):
global current_player
current_player = num
# gets player's guess and toggles turn back and forth. passes guess to game_play method
def player_guess():
if p1_entry.get():
print(target_number)
guess = int(p1_entry.get())
set_current_player(1)
game_play(guess)
p1_entry.delete(0, END)
p1_entry.config(state=DISABLED)
p2_entry.config(state=NORMAL)
p2_entry.focus_set()
#print(current_player + ' should be 1')
else:
guess = int(p2_entry.get())
set_current_player(2)
game_play(guess)
p2_entry.delete(0, END)
p2_entry.config(state=DISABLED)
p1_entry.config(state=NORMAL)
p1_entry.focus_set()
#print(current_player + ' should be 2')
# evaluates the guess and returns a message to the message_label. also, calls congrats method when a player has
# guessed correctly
def game_play(guess):
if guess == target_number:
display_message('Winner!')
congrats(current_player)
elif guess < target_number:
display_message('Too Low')
print(current_player, ' is low')
else:
display_message('Too High')
print(current_player, ' is high')
# displays appropriate message in message_label
def display_message(result):
message.set(result)
# opens message box to declare winner. Gives option to exit or play again
def congrats(player_num):
if player_num == 1:
winner = "Player 1"
else:
winner = 'Player 2'
answer = tkinter.messagebox.askquestion('Congratulations', winner + ' is the WINNER!! \n Would you like to play again?')
if answer == 'no':
root.quit()
else:
reset_game()
def reset_game():
reset_target_number()
message = StringVar()
set_current_player(0)
#print(target_number, message, current_player)
p2_entry.delete(0, END)
p1_entry.delete(0, END)
p2_entry.config(state=DISABLED)
#print('Did this run?')
p1_entry.config(state=NORMAL)
#print('Did this also run?')
p1_entry.focus_set()
# create instruction widgets
instruc_label = Label(root, text='Game Instructions')
instruc_label.grid(row=0, column=0, padx=2, pady=2, sticky=W)
instructions = Label(root, text="Players will take turns guessing the mystery number. The first player to guess correctlyl wins! The other player doesn't win: but that doesn't mean the other player is a loser. It just means that that player did not win this time. No amount of failures determines whether a person is a loser. It is a person's attitude and character towards their failures that determines a winner and a loser.", wraplength=490, justify=LEFT)
instructions.grid(row=1, column=0, columnspan=3, padx=5, pady=5)
# create game play widgets
p1_label = Label(root, text='Player 1')
p1_label.grid(row=2, column=0)
p1_entry = Entry(root, font='Helvetica 44 bold', fg='black', bg='lightyellow', relief=SUNKEN, width=4, state=NORMAL, justify=CENTER)
p1_entry.grid(row=3, column=0)
p1_entry.focus_set()
message_label = Label(root, textvariable=message, fg='white', bg='darkgreen', relief=GROOVE, height=10, width=20)
message_label.grid(row=2, column=1, rowspan=2)
p2_label = Label(root, text='Player 2')
p2_label.grid(row=2, column=2)
p2_entry = Entry(root, font='Helvetica 44 bold', fg='black', bg='lightyellow', relief=SUNKEN, width=4, state=DISABLED, justify=CENTER)
p2_entry.grid(row=3, column=2)
# create submit button to enter guesses
submit = Button(root, text='Submit', width=20, command=player_guess)
submit.grid(row=4, columnspan=3, pady=5, padx=5)
root.mainloop()
Mahalo!

In player_guess, winner decided in game_play.
If there is a winner, you are resetting the game with reset_game but your code continues after game_play returns and changes states of entries. game_play should be the last thing player_guess should do.
def player_guess():
if p1_entry.get():
....
game_play(guess)
else:
....
game_play(guess)
or just move game_play out of if-elses since you are calling it either way.
def player_guess():
if p1_entry.get():
....
guess = int(p1_entry.get())
else:
....
guess = int(p2_entry.get())
game_play(guess)

Related

Python tkinter, how to display the message one by one

I'm using tkinter (Python version 3.9) to build an application. In the application, I want the message can be shown one by one according to the progress of the program. However, my application now can only print all the messages together.
The example code is listed as below:
import time
import tkinter as tk
def start():
txt_edit.delete(1.0, tk.END)
for _ in range(10):
txt_edit.insert(tk.END, f"\nmessage should be printed out one by one")
# do something here
time.sleep(0.5)
# job done
window = tk.Tk()
window.title("User Interface")
window.rowconfigure(0, minsize=40, weight=1)
window.rowconfigure(1, minsize=200, weight=1)
window.columnconfigure(1, minsize=200, weight=1)
lbl_1 = tk.Label(master=window, text="Question: ")
lbl_2 = tk.Label(
master=window, text="How to print the text out one by one?", anchor="w"
)
lbl_1.grid(row=0, column=0, sticky="ns")
lbl_2.grid(row=0, column=1, sticky="nsew")
txt_edit = tk.Text(window, relief=tk.SUNKEN, bd=2)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(master=fr_buttons, text="Start", command=start)
btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
fr_buttons.grid(row=1, column=0, sticky="ns")
txt_edit.grid(row=1, column=1, sticky="nsew")
window.mainloop()
What is the solution to this problem? Thanks!
Calling time.sleep() from tkinter applications isn't a good idea. Your mainloop and time.sleep() are conflicting. Your program works fine, but the changes are not displayed.
The easiest solution is updating the screen. But as I said, you should avoid using time.sleep(). The following solution satisfies your question, though it freezes your program and will not work on a larger application.
def start():
global window
txt_edit.delete(1.0, tk.END)
for _ in range(10):
txt_edit.insert(tk.END, f"\nmessage should be printed out one by one")
# do something here
time.sleep(0.5)
window.update() # show changes
# job done
I would recommend dropping the time module. You can use a timer instead. Also, take into account the while loop. You might want to use multi-threading inside your applications. Here's a simple Timer object and implementation:
class Timer:
# --- Timer object ---
def __init__(self):
self.start_time = None
self.on = False
def start(self):
# start counting
self.start_time = time.time()
self.on = True
def value(self):
# --- return current value ---
if self.on:
return time.time() - self.start_time
else:
return 0
def stop(self):
# --- stop counting ---
self.__init__()
def start():
txt_edit.delete(1.0, tk.END)
msg_count = 0
timer = Timer()
timer.start()
while msg_count != 10:
window.update()
if timer.value() > 0.5:
txt_edit.insert(tk.END, f"\nmessage should be printed out one by one")
timer.start()
msg_count += 1
timer.stop()

Tkinter continuously loop main event

I'm making a trivia game where data is from www.opentdb.com. I want it to continually loop through the main program after it tells you whether your answer is correct or not. (clicking an answer and shows green label but will stay at that screen). I would like it to wait a few seconds and have tried using .after(3000, main()) but do not know how to reset the screen and repeat.Im sorry if this sounds very confusing. Thank you for your assistance :)
from tkinter import *
import trivia
import html
t = trivia.Trivia()
root = Tk()
root.geometry("1366x768")
root.configure(bg='#4dc4ff')
root.iconbitmap("icon.ico")
root.title("Trivia Party!")
frame = Frame(root, bg='#4dc4ff')
class Buttons:
def __init__(self, text):
self.text = html.unescape(text) # remove html entities
self.correct_label = Label(root, text="Correct!", font=("Arial", 30), background="#98FB98", width=20, height=2) # green correct label
def reveal_answer(self):
if self.text == t.return_correct_answer(): # if its the right answer
self.correct_label.pack(pady=175)
frame.destroy()
else:
self.correct_label.config(text="Incorrect", background="#ff6347")
self.correct_label.pack(pady=175)
Label(root, background="#4dc4ff", text=("The correct answer was: " + t.return_correct_answer()),
font=("Arial", 40)).pack()
frame.destroy()
def create(self):
Button(frame, text=self.text, borderwidth=0, highlightbackground="#00abff", font=("Helvetica", 30),
command=self.reveal_answer).pack(side=LEFT, expand=YES)
frame.pack(fill=BOTH, expand=YES)
buttons = []
question = Label(frame, text=html.unescape(t.return_question()), font=("Arial", 25), height=3, bg="#00abff").pack(fill=X)
for i in t.all_answers: # t.all_answers is pulling all of the multiple choice answers ie "coke", "pepsi" "sprite"
buttons.append(Buttons(i).create()) # i is the individual answer eg "pepsi" and is making a button
root.mainloop()

Using Tkinter to disable entry with specified input

I would like to use Tkinter to be able to disable one entry if 'no' is selected from a drop down menu.
from tkinter import *
def disableEntry(entry):
entry.config(state='disable')
def allowEntry(entry):
entry.config(state='normal')
def main():
print("test")
root = Tk() #create a TK root window
root.title("Lunch and Learn") #Title of the window
L1 = Label(root, text = "Label 1").grid(row=0, column=0, padx=30, pady=(20,5))
L2 = Label(root, text = "Label 2").grid(row=1, column=0, pady=5)
var = StringVar()
E1 = Entry(root,bd =3)
E1.grid(row=0, column=1)
D1 = OptionMenu(root,var,"yes","no")
D1.grid(row=1,column=1)
if var.get() == 'no':
disableEntry(E1)
elif var.get() == 'yes':
allowEntry(E1)
B2 = Button(text = "Submit", command=main).grid(row=4, column=2)
root.mainloop()
the above code is a simple example of what i have tried. I have created two functions called 'disableEntry' and 'allowEntry' which should change the state of the entry box but they don't appear to do anything when i change the input of the drop down menu.
i dont know if i am approaching this the wrong way or if there is a standardized way to do this.
any help would be appreciated.
You need a way to check the state of the selection after it is changed. That can be achieved with adding a callback command to the OptionMenu widget.
You were checking the correct variable, but the point you were checking it at was before the screen window had even displayed.
from tkinter import Label, StringVar, OptionMenu, Entry, Tk, Button
# change the state of the Entry widget
def change_state(state='normal'):
E1.config(state=state)
def main():
print("test")
# callback function triggered by selecting from OptionMenu widget
def callback(*args):
if var.get() == 'no':
change_state(state='disable')
elif var.get() == 'yes':
change_state(state='normal')
root = Tk() #create a TK root window
root.title("Lunch and Learn") #Title of the window
L1 = Label(root, text="Label 1").grid(row=0, column=0, padx=30, pady=(20, 5))
L2 = Label(root, text="Label 2").grid(row=1, column=0, pady=5)
var = StringVar()
E1 = Entry(root, bd=3)
E1.grid(row=0, column=1)
D1 = OptionMenu(root, var, "yes", "no", command=callback)
D1.grid(row=1, column=1)
B2 = Button(text = "Submit", command=main).grid(row=4, column=2)
root.mainloop()

Python 3 tkinter destroy all child windows at exit of root

SO i have been working on this for a while now and i just have no idea what i am doing wrong. I have a main window my 'root' but i also have a 'helpmenu'. right now, when you close the root helpmenu will remain opened if you opened it. i want helpmenu to close when the root closes.
i have tried to cutout all the lines of code from my program which i believed had no baring on the situation but forgive me if it is still long.
import atexit
from tkinter import *
root = Tk()
def quit():
helpmenu.destroy()
atexit.register(quit)
def helpmenu():
helpmenu = Tk()
helpmenu.geometry('800x600')
helpmenu.title('Help')
help_Label = Label(helpmenu, text='Welcome to the Boring Program!')
help_Label.pack()
class GUI(object):
def __init__(self, master):
self.master = master
def activateCheck1():
if c1Var.get() == 1: #whenever checked
c3.config(state=NORMAL)
elif c1Var.get() == 0: #whenever unchecked
c3.config(state=DISABLED)
def activateCheck2():
if c2Var.get() == 1: #whenever checked
c4.config(state=NORMAL)
elif c2Var.get() == 0: #whenever unchecked
c4.config(state=DISABLED)
c1Var = IntVar()
c2Var = IntVar()
c1_Label = Label(root, text="Select Which Edges to Program")
c1_Label.grid(row=2, columnspan=2)
c1 = Checkbutton(root, text="Left Edge", variable=c1Var, command=activateCheck1)
if parser.get('CONFIGURATION', 'LeftEdgeProgram') == '1':
c1.select()
c1.grid(row=3, column=0)
c2 = Checkbutton(root, text="Right Edge", variable=c2Var, command=activateCheck2)
if parser.get('CONFIGURATION', 'RightEdgeProgram') == '1':
c2.select()
c2.grid(row=3, column=1)
c3Var = IntVar()
c4Var = IntVar()
c3_Label = Label(root, text="Select Which Edges to Insert Glue and Dowels")
c3_Label.grid(row=4, columnspan=2)
c3 = Checkbutton(root, text="Left Edge", variable=c3Var)
if parser.get('CONFIGURATION', 'LeftEdgeDowel') == '1':
c3.select()
c3.grid(row=5, column=0)
c4 = Checkbutton(root, text="Right Edge", variable=c4Var)
if parser.get('CONFIGURATION', 'RightEdgeDowel') == '1':
c4.select()
c4.grid(row=5, column=1)
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Print", state='disabled')
filemenu.add_command(label="Save Configuration", command=save_config)
filemenu.add_command(label="Help", command=helpmenu)
filemenu.add_command(label="Restore Defaults", state='disabled')
filemenu.add_command(label="About", command=restore_config)
menubar.add_cascade(label="Options", menu=filemenu)
root.config(menu=menubar)
myGUI = GUI(root)
root.mainloop()
Firstly you are creating two instances of a tk() windows. Creating two tk() windows almost never should be done, this is because two tk windows cannot communicate with eachother. Instead you should use a TopLevel window.
You should also change your HelpMenu to a class object which inherits the TopLevel Class
This should look something like this:
class HelpMenu(Toplevel):
def __init__(self, master, *args, **kwargs):
super(HelpMenu, self).__init__(master, *args, **kwargs)
self.geometry('800x600')
self.title('Help')
help_Label = Label(self, text='Welcome to the Boring Program!')
help_Label.pack()
The only thing that now needs to be changed is the command for the help option in the toolbar
Something like this (notice I am using a lambda function to make this change):
filemenu.add_command(label="Help", command= lambda:HelpMenu(self.master) )
This will fix your problem, because when the master window of a TopLevel window is closed the TopLevel window is automatically closed.

Infinite Loop, not certain as to why

I am trying to take a text based game and make a GUI application with it. It is a number guessing game, where the computer chooses a number between 1 and 100. The user is expected to guess the correct number in the least amount of tries as possible.
My problem is that I am getting into an infinite loop when and I am not sure why or how to correct it.
The application is below:
# Guess My Number
#
# The computer picks a random number between 1 and 100
# The player tries to guess it and the computer lets the player know
# if the guess is too high, too low or right on the money
from tkinter import *
from random import randint
class Application(Frame):
""" A GUI Application for a number guessing game """
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.the_number = randint(1, 100)
self.guess_ent = Entry(self)
self.results = None
self.tries = 1
self.create_widgets()
def create_widgets(self):
Label(self,
text="Welcome to 'Guess My Number'"
).grid(row=0, column=0, columnspan=2, sticky=W+E+N+S)
Label(self,
text="I'm thinking of a number between 1 and 100"
).grid(row=1, column=0, columnspan=2, sticky=W+E+N+S)
Label(self,
text="Try to guess it in as few attempts as possible"
).grid(row=2, column=0, columnspan=2, sticky=W+E+N+S)
Label(self,
text="What is your guess: "
).grid(row=4, column=0, sticky=W)
self.guess_ent.grid(row=4, column=1, sticky=W)
Button(self,
text="Submit Guess",
command=self.guessing
).grid(row=5, column=0, sticky=W)
root.bind("<Return>", self.guessing_a)
self.results = Label(self, text="")
self.results.grid(row=6, column=0, sticky=W+E+N+S)
def guessing(self):
guess = int(self.guess_ent.get())
while guess != self.the_number:
if guess > self.the_number:
self.results.config(text="Lower")
self.guess_ent.delete(0, END)
else:
self.results.config(text="Higher")
self.guess_ent.delete(0, END)
self.tries += 1
if self.tries >= 10:
self.results.config(text="I'm sorry you couldn't guess the "
"number in the appropriate amount "
"of tries. The number I was "
"thinking of was: {}"
.format(self.the_number))
self.results.config(text="You guessed it!. "
"The number I was thinking of was: {}, "
"and it only took you {}, tries"
.format(self.the_number, self.tries))
def guessing_a(self):
self.guessing()
# main
root = Tk()
root.title("Guess My Number")
app = Application(root)
root.mainloop()
The last line of your code root.mainloop() means you already have a while loop in your program. So running a while loop inside an other one the way you did will lead to problems.
So the idea is to take an advantage of the main implicit while loop of your program and use it to increment the number of tries self.tries by one. This is in reality what is naturally when you used the command option of your button and the bind() method for Enter. I think you will understand the solution below easily.
Thus in the below program I modified only guessing() method and ... one last thing: do not forget to inject an event as a parameter to def guessing_a() method otherwise pressing on Enter will raise a TypeError exception.
Program
Here is the full program that works correctly both with pressing the button widget or the Enter keyboard button:
# Guess My Number
#
# The computer picks a random number between 1 and 100
# The player tries to guess it and the computer lets the player know
# if the guess is too high, too low or right on the money
from tkinter import *
from random import randint
class Application(Frame):
""" A GUI Application for a number guessing game """
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.the_number = randint(1, 100)
self.guess_ent = Entry(self)
self.results = None
self.tries = 1
self.create_widgets()
def create_widgets(self):
Label(self,
text="Welcome to 'Guess My Number'"
).grid(row=0, column=0, columnspan=2, sticky=W+E+N+S)
Label(self,
text="I'm thinking of a number between 1 and 100"
).grid(row=1, column=0, columnspan=2, sticky=W+E+N+S)
Label(self,
text="Try to guess it in as few attempts as possible"
).grid(row=2, column=0, columnspan=2, sticky=W+E+N+S)
Label(self,
text="What is your guess: "
).grid(row=4, column=0, sticky=W)
self.guess_ent.grid(row=4, column=1, sticky=W)
Button(self,
text="Submit Guess",
command=self.guessing
).grid(row=5, column=0, sticky=W)
root.bind("<Return>", self.guessing_a)
self.results = Label(self, text="")
self.results.grid(row=6, column=0, sticky=W+E+N+S)
def guessing(self):
if self.guess_ent.get() !='': # if there is a number in the entry widget. You can also improve this by checking if the entered text is a valid number
guess = int(self.guess_ent.get())
if guess > self.the_number:
self.results.config(text="Lower")
self.guess_ent.delete(0, END)
elif guess < self.the_number:
self.results.config(text="Higher")
self.guess_ent.delete(0, END)
else:
self.results.config(text="You guessed it!. "
"The number I was thinking of was: {}, "
"and it only took you {}, tries"
.format(self.the_number, self.tries))
self.tries += 1
if self.tries >= 10:
self.results.config(text="I'm sorry you couldn't guess the "
"number in the appropriate amount "
"of tries. The number I was "
"thinking of was: {}"
.format(self.the_number))
def guessing_a(self, event):
self.guessing()
# main
root = Tk()
root.title("Guess My Number")
app = Application(root)
root.mainloop()
You have a while loop that check while guess != self.number. But, where do you change guess? This condition will always be true -> infinite loop.
You want guess inside the while loop.
Also, in Tk you shouldn't use a while loop, unless you're using threads. This is going to block the GUI. Use the after method instead.

Resources