Python tkinter growing call stack? - python

So I'm making a program which calls a function to do some calculations, and there are buttons to press to change the inputs and recalculate.
It works great, except if a button is pressed before the calculations are finished, the new values are calculated and outputted before returning back to the previous values. Basically, the program does what I want, except it returns to the first calculations after button press and completing second calculations (all the variable values return).
A general schematic of the problem:
1. Root mainloop
2. Values inputted
3. Press 'Go'
4. Calculations printed to screen (Monty Carlo sim by the way)
5. Press button to change input values
6. New calculations printed to screen
7. Once new ones finish, calculations for old variable values return until completion
Is there a way to prevent python from returning to the previous variable values like this? My hope is that there is a way to return to the mainloop so that the calculations on the screen stay correct. Thanks!
Edit:
Here's the code (Sorry it's a bit long and this is my first time with tkinter):
from tkinter import *
from tkinter import ttk
from random import *
from statistics import *
from math import *
'''
Prompt user for: number attacking with, number of defenders at each space
Output: n approaching 100000 and error range approaching .3%, probabilities of next roll, probabilities of winning at each spot, average number of pieces lost & stddev,buttons for decreasing defender/attacker
'''
def reset():
attacker_entry.delete(0,END)
for i in range(len(spaces_entry)):
spaces_entry[i].delete(0,END)
update_buttons()
def update_buttons(*args):
buttons = [attacker_2,attacker_1,split_button,defender_1,defender_2]
try:
if int(attacker_entry.get()) > 1:
for button in buttons:
button['state'] = ['normal']
elif int(attacker_entry.get()) == 1:
attacker_2['state'] = ['disabled']
split_button['state'] = ['disabled']
elif int(attacker_entry.get()) == 0:
for button in buttons:
button['state'] = ['disabled']
return
if int(space1_entry.get()) > 1:
defender_2['state'] = ['normal']
elif int(space1_entry.get()) == 1:
defender_2['state'] = ['disabled']
split_button['state'] = ['disabled']
except:
for button in buttons:
button['state'] = ['disabled']
def subtract(label,*args):
if label == "both":
label = "att def 1"
end = int(label[-1:])
if "att" in label:
attacker_amount.set(int(attacker_entry.get()) - end)
if "def" in label:
space1.set(int(space1_entry.get()) - end)
if int(space1_entry.get()) == 0:
attacker_amount.set(int(attacker_entry.get()) - 1)
for i in range(len(spaces)):
try:
spaces[i].set(int(spaces_entry[i+1].get()))
except:
spaces[i].set("")
win_avgs[i].set("")
pieces_left[i].set("")
most_likely[i].set("")
space10.set("")
update_buttons()
go()
def check_if_multiple(list1,list2):
if len(list1)>1 and len(list2)>1:
ret_val = 2
else:
ret_val = 1
return ret_val
def set_rolls(total,limit):
list = []
for i in range(total):
if len(list) < limit:
list.append(randrange(1,7))
return list
def go(*args):
update_buttons()
try:
attacker_total = int(attacker_entry.get())
except:
return
defender_pieces_list = []
for entry in spaces_entry:
try:
new_val = int(entry.get())
if new_val == 0:
None
else:
defender_pieces_list.append(new_val)
except:
None
defender_total_spaces = len(defender_pieces_list)
attacker_total_original = attacker_total
total_trials = 10000
defender_losses = 0
attacker_losses = 0
first_round_defender_total_wins = 0
first_round_attacker_total_wins = 0
first_round_split_total = 0
space1_succ,space2_succ,space3_succ,space4_succ,space5_succ,space6_succ,space7_succ,space8_succ,space9_succ,space10_succ = [],[],[],[],[],[],[],[],[],[]
space1_all,space2_all,space3_all,space4_all,space5_all,space6_all,space7_all,space8_all,space9_all,space10_all = [],[],[],[],[],[],[],[],[],[]
succ_list = [space1_succ,space2_succ,space3_succ,space4_succ,space5_succ,space6_succ,space7_succ,space8_succ,space9_succ,space10_succ]
all_list = [space1_all,space2_all,space3_all,space4_all,space5_all,space6_all,space7_all,space8_all,space9_all,space10_all]
for trial in range(total_trials):
if trial%20 == 0:
for i in range(0,defender_total_spaces):
try:
win_avgs[i].set(round(((len(succ_list[i]))/trial)*100,1))
pieces_left[i].set(str(round(mean(all_list[i]),2)))
most_likely[i].set(mode(all_list[i]))
root.update()
except:
None
attacker_total = attacker_total_original
first_round = True
for i in range(defender_total_spaces):
defender_total = defender_pieces_list[i]
while defender_total>0 and attacker_total > 0:
defender_win = False
attacker_win = False
defender_rolls_list = set_rolls(defender_total,2)
attacker_rolls_list = set_rolls(attacker_total,3)
if len(attacker_rolls_list) == 1:
defender_rolls_list = [randrange(1,7)]
for j in range(check_if_multiple(defender_rolls_list,attacker_rolls_list)):
if max(defender_rolls_list)>=max(attacker_rolls_list):
attacker_total += -1
defender_win = True
else:
defender_total += -1
attacker_win = True
attacker_rolls_list.remove(max(attacker_rolls_list))
defender_rolls_list.remove(max(defender_rolls_list))
if first_round == True:
if defender_win == True and attacker_win == True:
first_round_split_total += 1
elif attacker_win == True:
first_round_attacker_total_wins += 1
elif defender_win == True:
first_round_defender_total_wins += 1
first_round = False
if defender_total == 0:
succ_list[i].append(attacker_total)
all_list[i].append(attacker_total)
if attacker_total == 1:
attacker_total == -1
if attacker_total == 0:
all_list[i].append(attacker_total)
attacker_total += -1
for i in range(0,defender_total_spaces):
try:
win_avgs[i].set(round(((len(succ_list[i]))/trial)*100,1))
pieces_left[i].set(str(round(mean(all_list[i]),2))+"("+str(round(stdev(all_list[i]),1))+")")
most_likely[i].set(mode(all_list[i]))
except:
None
height = 450
width = 600
shape = str(width) + "x" + str(height)
root = Tk()
root.title("Risk Probability Calculator")
root.geometry(shape)
content = ttk.Frame(root)
content.grid(column=0, row=0, sticky=(N, W, E, S))
content.columnconfigure(0, weight=1)
content.rowconfigure(0, weight=1)
title = ttk.Label(content, text="Risk Probability Calculator", relief="ridge", background="gray",font=("TkHeadingFont",20),anchor="center")
title.grid(column=1, row=1, columnspan=8, padx=3, pady=4,sticky=(N,W,E,S))
reset_button = ttk.Button(content, text="Reset", command=reset)
reset_button.grid(column=1, row=2,padx=3, pady=4, sticky=(N, W, E, S))
go_button = ttk.Button(content, text="Go", command=go)
go_button.grid(column=2, row=2,padx=3, pady=4, sticky=(N, W, E, S))
ttk.Label(content, text="Attacking with:").grid(column=1, row=3,padx=3, pady=4, sticky=(NW))
ttk.Label(content, text="Defending with:").grid(column=1, row=4,padx=3, pady=4, sticky=(NW))
for i in range(5,15):
text = "Space " + str(i-4) + ":"
ttk.Label(content, text=text).grid(column=1,padx=3, pady=4, row=i, sticky=(N,E,S))
attacker_amount = StringVar()
attacker_entry = ttk.Entry(content, textvariable=attacker_amount, width=4)
attacker_entry.grid(column=2, row=3,padx=3, pady=4, sticky=(N, W, S))
spaces = []
spaces_entry = []
for i in range(1, 11):
globals()['space'+str(i)] = StringVar()
spaces.append(globals()['space'+str(i)])
globals()['space'+str(i)+'_entry'] = ttk.Entry(content, textvariable=spaces[i-1], width=4)
globals()['space'+str(i)+'_entry'].grid(column=2, row=(i+4),padx=3, pady=4, sticky=(N, W, S))
spaces_entry.append(globals()['space'+str(i)+'_entry'])
attacker_2 = Button(content, text="Attacker -2",command=lambda: subtract("att 2"))
attacker_2.grid(column=4, row=2,padx=3, pady=4, sticky=(N,W,E,S))
attacker_1 = Button(content, text="Attacker -1",command=lambda: subtract("att 1"))
attacker_1.grid(column=5, row=2,padx=3, pady=4, sticky=(N,W,E,S))
split_button = Button(content, text="Split",command=lambda: subtract("both"))
split_button.grid(column=6, row=2,padx=3, pady=4, sticky=(N,W,E,S))
defender_1 = Button(content, text="Defender -1",command=lambda: subtract("def 1"))
defender_1.grid(column=7, row=2,padx=3, pady=4, sticky=(N,W,E,S))
defender_2 = Button(content, text="Defender -2",command=lambda: subtract("def 2"))
defender_2.grid(column=8, row=2,padx=3, pady=4, sticky=(N,W,E,S))
ttk.Separator(content,orient="vertical").grid(column=3,row=2,rowspan=15,padx=3, pady=4,sticky=(N,W,E,S))
results_frame = ttk.Labelframe(content, text='Results:')
results_frame.grid(column=4, row=3, columnspan=5, rowspan=12,padx=3, pady=4, sticky=(N,W,E,S))
pane = ttk.Panedwindow(results_frame, orient='horizontal')
pane.grid(column=1,row=1,columnspan=5,padx=3,sticky=(N,W,E,S))
pane1 = ttk.Labelframe(pane)
pane.add(pane1)
Label(pane1,text="% Win").grid(column=1,row=1,sticky=(N,W,E))
win_avgs = []
for i in range(1,11):
globals()['win_avg'+str(i)] = StringVar()
win_avgs.append(globals()['win_avg'+str(i)])
Label(pane1,textvariable=win_avgs[i-1]).grid(column=1,row=i+1,padx=4, pady=4,sticky=(N,W,S))
pane2 = ttk.Labelframe(pane)
pane.add(pane2)
Label(pane2,text="Pieces Left").grid(column=1,row=1,sticky=(N,W,E))
pieces_left = []
for i in range(1,11):
globals()['pieces_left'+str(i)] = StringVar()
pieces_left.append(globals()['pieces_left'+str(i)])
Label(pane2,textvariable=pieces_left[i-1]).grid(column=1,row=i+1,padx=4, pady=4,sticky=(N,W,S))
pane3 = ttk.Labelframe(pane)
pane.add(pane3)
Label(pane3,text="Most likely # of Pieces Left").grid(column=1,row=1,sticky=(N,W,E))
most_likely = []
for i in range(1,11):
globals()['most_likely'+str(i)] = StringVar()
most_likely.append(globals()['most_likely'+str(i)])
Label(pane3,textvariable=most_likely[i-1]).grid(column=1,row=i+1,padx=4, pady=4,sticky=(N,W,S))
root.mainloop()

The short answer is that it's because you call root.update(). This not only redraws the screen, but processes any pending events. A good rule of thumb is "never call update" for this exact reason. Infrequently you simply must, but usually there's a better way to organize your code. You can sometimes get away with it if you do it in a function that only takes a few ms, but it looks like your function computes a lot of things. If a user presses a key or clicks a button and then you call update, tkinter is going to try to process that event before continuing.
I would recommend first just removing the call and see what happens. If the screen seems to freeze (which it might, given how much code you're trying to run), you can try replacing it with root.update_idletasks(). That won't process any click or button events, only a certain class of event such as "repaint the screen".

Related

tkinter GUI Text Box is not displaying all results similar to terminal output

I am trying to make a loan amortization table inside tkinter GUI using text box. However, all the loan amortization table is fully displayed in the terminal console as an output.
The tkinter text BOX output display's only the last line: Here is the full code:
import math
import tkinter as tk
from tkinter import *
def calcLoanPayment(p, r, n, t):
if p > 0 and r > 0 and n > 0 and t > 0:
clp = (p *(r/n)*(pow(1 + r/n, n * t)))/(pow(1 + r/n, n * t)-1)
clp = round(clp, 2)
return clp
else:
return -1
def runApp(): #This will bound the function runapp to the calcbtn.
print("running")
p = principalentry.get() #this will get value as a string
try:
p = float(p) #this will make cast it to float variable.
except:
p = -1
principalentry.delete(0, END) #this will clear the entry after calculation
r = interestrateentry.get()
try: #Try will help from crushing the program when wrong value entered
r = float(r)
except:
r = -1
interestrateentry.delete(0, END) #this will clear the entry after calculation
n = compoundintervalentry.get() #this will get value as a string
try:
n = int(n) #this will make cast it to float variable.
except:
n = -1
compoundintervalentry.delete(0, END) #this will clear the entry after calculation
t = durationentry.get()
try: #Try will help from crushing the program when wrong value entered
t = int(t)
except:
t = -1
durationentry.delete(0, END) #this will clear the entry after calculation
clp = calcLoanPayment(p,r,n,t)
print(clp)
paymentinterval = n*t
paymentinterval = int(paymentinterval)
startingBalance=p
endingBalance=p
for i in range(1, paymentinterval+1):
interestcharge =r/n*startingBalance
endingBalance=startingBalance+interestcharge-clp
result ="\t\t"+str(i)+"\t\t" +str(round (startingBalance, 1)
)+"\t\t"+str(round (interestcharge, 1))+"\t\t"+str(round (clp, 1)
)+"\t\t"+str(round (endingBalance, 1))
startingBalance = endingBalance
print(result)
finaloutput = result
output.config(state="normal")
output.delete(0.0, 'end')
output.insert(END, finaloutput)
output.config(state="disabled")
#Frontend maincode Startshere:
#sketch the design of the user interface first.
root = tk.Tk()
root.config(bg="#F8B135")
root.state("zoomed")
#There are 3 steps to build event programming
#Step 1: construct the widgets
#step 2: configure the widget to make it nicer
#srep 3: place or pack the widget in the root window
title = Label(root, text = "Loan Payment Calculator", font = 'bold')
title.config(fg='white', bg="#28348A")
title.pack(fill = BOTH)
principallabel = Label(root, text="Principal: ")
principallabel.config(anchor = W, font = 'bold', bg="#F8B135")
principallabel.place(x=50, y=30)
principalentry = Entry(root)
principalentry.config(bg="#ffffff")
principalentry.place(x=400, y=30)
interestratelabel = Label(root, text="Interest Rate: enter as Decimal (eg.2% as 0.02) ")
interestratelabel.config(anchor = W, font = 'bold', bg="#F8B135")
interestratelabel.place(x=50, y=70)
interestrateentry = Entry(root)
interestrateentry.config(bg="#ffffff")
interestrateentry.place(x=400, y=70)
compoundintervallabel = Label(root, text="Compound Interval: ")
compoundintervallabel.config(anchor = W, font = 'bold', bg="#F8B135")
compoundintervallabel.place(x=50, y=110)
compoundintervalentry = Entry(root)
compoundintervalentry.config(bg="#ffffff")
compoundintervalentry.place(x=400, y=110)
durationlabel = Label(root, text="Duration: ")
durationlabel.config(anchor = W, font = 'bold', bg="#F8B135")
durationlabel.place(x=50, y=150)
durationentry = Entry(root)
durationentry.config(bg="#ffffff")
durationentry.place(x=400, y= 150)
output = Text(root)
output.config(width= 150, height = 100, bg="white", state="disabled", borderwidth=2, relief= "groove", wrap='word')
output.place(x=50, y=230)
calcbtn = Button(root, text= "Calculate", font = 'bold', highlightbackground="#3E4149")
calcbtn.config(fg='#28348A',command=runApp)
calcbtn.place(x=50, y=190)
root. mainloop()
file.close()`
I couldn't figure out How to display the whole output like the terminal did on the tkinter textbox output function. your help is much appreciated.
There are 2 small changes to the code in the function runApp()
output.config(state="normal")
# output.delete(0.0, 'end') # This line deletes the last entry in the text box. Remove this
output.insert(END, finaloutput + '\n') # Add a newline here. Generates the desired 'tabular' output
output.config(state="disabled")

Updating Tkinter text with label

I'm trying to make a GUI version of a drinking game, which involves throwing dice. I 'throw the dice' virtually, but want to display the outcome on the screen. I thought using Label() would be most useful, but i'm having trouble updating that text (the throw_text variable) when a new dice throw is done.
Instead of the text updating on the screen, a new text appears below the previous one with the new throw.
I don't want a cascading stream of text, but just one singular text that updates at the dice being thrown i.e. when the 'Roll!' button is pressed.
I've seen people do it using the Stringvar(), however i must be doing it wrong as it does not have an effect on the appearance.
from tkinter import *
dim = [900, 600]
root = Tk()
root.geometry(f"{dim[0]}x{dim[1]}")
root.title("Mex")
root.iconbitmap("dice.ico")
lbl = Label(root, font=("Times", 200))
num = ['\u2680', '\u2681', '\u2682', '\u2683', '\u2684', '\u2685']
d1 = None
d2 = None
throw_txt = StringVar()
def roll_dice():
global d1, d2
d1 = random.choice(num)
d2 = random.choice(num)
lbl.config(text=f"{d1}{d2}")
lbl.pack()
lbl.place(x=dim[0] / 3.8, y=50)
print("Throw:", check_throw(convert_symbol()))
throw_txt = str(check_throw(convert_symbol()))
txt = Label(root, text=throw_txt)
txt.pack(side=TOP)
def convert_symbol():
d1_num = None
d2_num = None
for n in num:
if n == d1:
d1_num = num.index(n) + 1
if n == d2:
d2_num = num.index(n) + 1
if d2_num > d1_num:
throw = f"{d2_num}{d1_num}"
else:
throw = f"{d1_num}{d2_num}"
return throw
def check_throw(t):
num_t = int(t)
if num_t == 21:
return "Mex"
elif num_t % 11 == 0:
return f"Koning: {int(num_t / 11 * 100)}"
elif num_t % 31 == 0:
return "Slok uitdelen"
else:
return str(num_t)
roll_btn = Button(root, text="Roll!", width=10, command=roll_dice)
roll_btn.config(font=("Bahnschrift", 20))
roll_btn.pack()
roll_btn.place(x=dim[0] / 2.5, y=25)
roll_btn = Button(root, text="Convert", width=10, command=convert_symbol)
roll_btn.config(font=("Bahnschrift", 20))
roll_btn.pack()
roll_btn.place(x=dim[0] / 2.5, y=500)
root.mainloop()
You could use the configure attribute instead of storing the text of the Label widget into a separate variable.
Take a quick look at this code:
from tkinter import *
root = Tk()
root.title("my game")
def change_text():
mylabel.configure(text="You just hit the button!")
mylabel = Label(root, text="Hit the button pls")
mylabel.grid(column=0, row=1, sticky='w')
mybutton = Button(root, text="Click me", command=change_text)
mybutton.grid(column=0, row=2, sticky='w')
root.mainloop()

Entry.delete in Tkinter not clearing

I tried making a simple number checker that shows if the number is correct or wrong.
After some hastily written prototype code, I can't get the fields to clear.
I dug through many questions regarding this topic, but couldn't find an answer.
My attempt is in show_result()
What am I doing wrong?
It should accept the numbers, and with the last number; verify. If it's correct, show accepted-image, else show denied-image and clear Entries after wrong entry. And after 3 seconds remove the image.
I suspect the num_limit() function messes something up, but I can't figure what.
All other files can be found here: https://bytebitten.stackstorage.com/s/JeRD33P92Us4a1Li
from tkinter import *
import time
#####
# Variables
code_check = 0
codeLength = 4
list3 = 123, 456
list4 = 2345, 3452
#####
# System
root = Tk()
root.title('Code Verifier')
root.attributes('-fullscreen', True)
root.bind('<Escape>',lambda e: root.quit())
#####
# Functions
# Input limiter function (WIP)
def num_limit(p, this_field):
global codeLength
print("Entry" + this_field + ": " + p)
this_field = int(this_field)
entries = [entry1, entry2, entry3, entry4]
next_field = entries[this_field]
# Check if it's a number
if p.isdigit():
if this_field < codeLength:
# Set focus on next field
next_field.focus_set()
pass
else:
code_checker()
return True
else:
# Check if it's Backspace or del/home/end/PgUp/PgDn
if p!="\x08" and p!="":
return False
else:
if this_field < codeLength:
next_field.focus_set()
else:
return True
def code_checker():
global code_check
global codeLength
print("== Code Check ==")
enteredCode = ''
enteredCode += entry1.get()
enteredCode += entry2.get()
enteredCode += entry3.get()
if codeLength == 3:
if enteredCode in list3:
code_check = 1
else:
code_check = 2
if codeLength == 4:
enteredCode += entry4.get()
if enteredCode in list4:
code_check = 1
else:
code_check = 2
show_result()
def show_result():
global code_check
print("== Show Result ==")
print(code_check)
# If code is correct
if code_check == 1:
# Image code accepted
main_canvas.create_image(width/2, 750, anchor=N, image=codeAccepted)
# If code is wrong
elif code_check == 2:
# Image code denied
main_canvas.create_image(width/2, 750, anchor=N, image=codeDenied)
# Clear fields
entry1.delete(0, END)
entry2.delete(0, END)
entry3.delete(0, END)
entry4.delete(0, END)
# Set focus to first field
entry1.focus_set()
else:
pass
#####
# Screen items
# Load images
codeDenied = PhotoImage(file='Assets/code_denied.png')
codeAccepted = PhotoImage(file='Assets/code_accepted.png')
# Set fullscreen canvas
main_canvas = Canvas(root, bg="#000000", bd=0, highlightthickness=0)
main_canvas.pack(fill="both", expand=True)
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
# Input num limiter command
vcmd = root.register(func=num_limit)
# 3-6 single-digit input fields
entry1 = Entry(root, validate='key', validatecommand=(vcmd, '%P', 1), font=("Helvetica", 100), fg="#000000", bg="#ffffff", width=1, bd=0)
entry2 = Entry(root, validate='key', validatecommand=(vcmd, '%P', 2), font=("Helvetica", 100), fg="#000000", bg="#ffffff", width=1, bd=0)
entry3 = Entry(root, validate='key', validatecommand=(vcmd, '%P', 3), font=("Helvetica", 100), fg="#000000", bg="#ffffff", width=1, bd=0)
entry4 = Entry(root, validate='key', validatecommand=(vcmd, '%P', 4), font=("Helvetica", 100), fg="#000000", bg="#ffffff", width=1, bd=0)
# Entry position adjust
field1 = -100
field2 = 0
field3 = 100
field4 = 150
# Entry placing
if codeLength == 3:
entry_window1 = main_canvas.create_window(width+field1, 500, anchor=N, window=entry1)
entry_window2 = main_canvas.create_window(width+field2, 500, anchor=N, window=entry2)
entry_window3 = main_canvas.create_window(width+field3, 500, anchor=N, window=entry3)
elif codeLength == 4:
field1 = field1-50
field2 = field2-50
field3 = field3-50
entry_window1 = main_canvas.create_window(width/2+field1, 500, anchor=N, window=entry1)
entry_window2 = main_canvas.create_window(width/2+field2, 500, anchor=N, window=entry2)
entry_window3 = main_canvas.create_window(width/2+field3, 500, anchor=N, window=entry3)
entry_window4 = main_canvas.create_window(width/2+field4, 500, anchor=N, window=entry4)
else:
pass
#####
root.mainloop()
Inside num_limit(), you should check whether the content of Entry is empty or not, if it is empty return True. Without this validation, you cannot delete content of the Entry as empty string is not a valid input.
Also you called code_checker() before the validation function returns, so you will get empty string from Entry4 inside code_checker(). Use after() to schedule the execution of code_checker() after the validation.
Finally, list3 and list4 are list of integers, but enteredCode is string, so the checking result will always be 2. Convert enteredCode to integer before checking.
Below are modified num_limit() and code_checker():
def num_limit(p, this_field):
global codeLength
### check for empty string
if p == "": return True
print("Entry" + this_field + ": " + p)
this_field = int(this_field)
entries = [entry1, entry2, entry3, entry4]
if this_field < codeLength:
next_field = entries[this_field]
# Check if it's a number
if p.isdigit():
if this_field < codeLength:
# Set focus on next field
next_field.focus_set()
else:
# schedule execution of code_checker() after validation
root.after(100, code_checker)
return True
else:
# Check if it's Backspace or del/home/end/PgUp/PgDn
if p!="\x08" and p!="":
return False
else:
if this_field < codeLength:
next_field.focus_set()
else:
return True
def code_checker():
global code_check
global codeLength
print("== Code Check ==")
enteredCode = ''
enteredCode += entry1.get()
enteredCode += entry2.get()
enteredCode += entry3.get()
if codeLength == 3:
if int(enteredCode) in list3: # convert enteredCode to integer
code_check = 1
else:
code_check = 2
if codeLength == 4:
enteredCode += entry4.get()
if int(enteredCode) in list4: # convert enteredCode to integer
code_check = 1
else:
code_check = 2
show_result()

How to avoid resetting a value by pressing a button again

The buttonGuess runs numRandom function when it's pressed and it also runs remainingAttemps function. The problem is if user presses the buttonGues, Attemps value is reasigned again.
import tkinter
import random
window = tkinter.Tk()
window.geometry('600x500')
x = random.randint(1,10)
remainingTime = True
Attempts = 4
def countdown (time_left):
global remainingTime
if remainingTime == True:
lblCrono.configure(text = str(time_left))
if time_left > 0:
time_left = time_left - 1
window.after(1000, countdown, time_left)
else:
remainingTime = False
lblCrono.configure(text = 0)
return remainingTime, gameOver()
else:
return
def numRamdom():
global Attempts
numWritten = int(entryWriteNumber.get())
if numWritten > x:
lblClue.configure(text = 'Its a smaller number')
return remainingAttempts(Attempts)
if numWritten < x:
lblClue.configure(text = 'Its a bigger number')
return remainingAttempts(Attempts)
if numWritten == x:
lblClue.configure(text = 'Congratulations ;)')
remainingTime = False
return remainingTime, countdown(0)
def gameOver():
if remainingTime == False and Attempts != 0:
lblClue.configure(text = '¡Time\'s up!')
else:
lblClue.configure(text = 'No attempts')
def remainingAttempts(countAtempts):
Attempts = countAtempts
if Attempts == 0:
return remainingTime, countdown(0), Attempts, gameOver()
else:
Attempts = Attempts - 1
entryWriteNumber = tkinter.Entry(window)
entryWriteNumber.grid(column = 0, row = 1, padx = 10, pady = 10)
lblNumber = tkinter.Label(window, text = 'Number', font = 'Comic 13 bold')
lblNumber.grid(column = 0, row = 0, padx = 10, pady = 10, sticky = tkinter.W )
buttonGuess = tkinter.Button(window, text = 'Guess', bg = 'light grey', padx = 20, command = numRamdom)
buttonGuess.grid(column = 0, row = 2, sticky = tkinter.W, padx = 10, pady = 10)
lblClue = tkinter.Label(window, text = 'Clue', font = 'Comic 13 bold')
lblClue.grid(column = 0, row = 3, padx = 10, pady = 10, sticky = tkinter.W )
lblCrono = tkinter.Label(window, text = '', bg = 'white', fg = 'red', font = 'Comic 20', padx = 50, pady = 5)
lblCrono.grid(column = 1, row = 5, sticky = tkinter.S, padx = 100, pady = 150)
countdown(30)
window.mainloop()
It's all much easier to manage when you get rid of everything that either isn't doing anything or is unnecessary. All your returns are doing nothing. Creating a remainingTime variable when you have a counter is unnecessary. Giving your widgets and functions a bunch of complicated and/or misleading names, isn't helping. You were calling countdown(0) which calls gameOver() and then calling gameOver().
You never put something on row=4, but you put your timer on row=5. This is visibly no different than putting it on 4. You had very repetitive grid options so I homogenized them in a dict and used that dict as **kwargs. Writing your arguments like this -> func(arg1 = value1, arg2 = value2, ...) has no benefit. There's no reason to keep a reference to lblNumber or buttonGuess. You never modify or further reference either, in any way. If you don't specify a column, tkinter will assume you mean column=0. If you don't specify a row, tkinter will assume you mean 1 row greater than the total current rows, regardless of column. Importing tkinter without an alias just gives you more to type.
Below is my edit of your game based on everything I just wrote.
import tkinter as tk
import random
root = tk.Tk()
root.geometry('600x500')
x = random.randint(1,10)
remaining = 4
def countdown(time_left):
global process
chrono['text'] = str(time_left)
if time_left:
process = root.after(1000, countdown, time_left-1)
else:
gameOver()
def check():
global remaining
n = int(guess.get())
if n == x:
gameOver(True)
return
else:
clue['text'] = f'Its a {"smaller" if n > x else "bigger"} number'
remaining -= 1
if not remaining:
gameOver()
def gameOver(win=False):
root.after_cancel(process)
if not win:
clue['text'] = '¡Time\'s up!' if remaining else 'No attempts remain'
else:
clue['text'] = 'Congratulations ;)'
grid = dict(padx=10, pady=10, sticky=tk.W)
tk.Label(root, text='Number', font='Comic 13 bold').grid(**grid)
guess = tk.Entry(root)
guess.grid(**grid)
tk.Button(root, text='Guess', bg='light grey', padx=20, command=check).grid(**grid)
clue = tk.Label(root, text='Clue', font='Comic 13 bold', width=20, anchor='w')
clue.grid(**grid)
chrono = tk.Label(root, text='', bg='white', fg='red', font='Comic 20', padx=50, pady=5)
chrono.grid(column=1, **grid)
countdown(30)
root.mainloop()

self.gridrange=8 is showing as a tuple when I want it to be an integer

#!/usr/bin/env python
import tkinter as tk
from tkinter import ttk
import math
import random
#positions are stored in label and number format
LARGE_FONT= ("Verdana", 12)
positions = ['label'+str(i)+str(j) for i in range(8) for j in range(8)]
class Application(tk.Frame):
global positions
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid(sticky=tk.N+tk.S+tk.E+tk.W)
#The entry screen
#label = tk.Label(self, text="""Begin.""", font=LARGE_FONT)
#label.grid(row=0, column=0, pady=10)
#label = tk.Label(self, text="""Exit.""", font=LARGE_FONT)
#label.grid(row=0, column=1, pady=10)
#button1 = ttk.Button(self, text="Start Game", command=self.start)
#button2 = ttk.Button(self, text="Quit Game", command=quit)
#button1.grid(row=1, column=0, pady=10)
#button2.grid(row=1, column=1, pady=1)
#def start(self):
#self.createWidgets()
self.master = master
app = tk.Frame(self.master, bg="yellow")
app.grid_rowconfigure(0, weight=1)
app.grid_columnconfigure(1, weight=1)
app.grid_columnconfigure(0, weight=1)
#app.pack(fill="both", expand=True)
# instructions and fonts
self.instructions = "Find the hidden treasure!\n\nUse the arrow keys to select where to look, then press Enter to check. \
There is a 50/50 chance you will be told the distance from the treasure. Keep hunting until you find it. Good luck!"
# create instructions widget
self.info = tk.Text(app, padx=10, pady=10,width=15,bd=0, height=19, bg="yellow")
self.info.insert(1.0,self.instructions)
self.info.grid(row=0,column=0,sticky='N'+'E'+'S'+'W')
# create island widget
self.island = tk.Text(app, bg="cyan", padx=40, pady=40, width=15, height=9, bd=0)
self.island.insert(1.0, "ready")
self.island.grid(row=0,column=1, stick='N'+'E'+'S'+'W', rowspan=3)
# restart button
#self.restart_b = tk.Button(app, text="Restart", bg="red", command=self.begin)
#self.restart_b.grid(row=2, column=0, pady=20)
# score labels and fields
self.score_lbl = tk.Label(app, text="Guesses: 0", bg="yellow")
self.score_lbl.grid(row=1, column=0)
#keep track og gold
self.gold_lbl = tk.Label(app, text="Gold: 0", bg="yellow")
self.gold_lbl.grid(row=3, column=0)
#gold and bandit positions
self.gridrange = 8
self.numchest = 10
self.bandits = 3
self.winscore = 100
# set keydown handler
root.bind("<Key>", self.key_pressed)
# best score variable
self.best_score = 0
# begin game
#self.begin()
#print self.treasure_pos
self.mainMenu()
def mainMenu(self):
self.t = tk.Toplevel(self.master, backgroun='red')
self.t.wm_title("Main Menu")
self.l = tk.Label(self.t, text="Welcome to Treasure Hunt", background='red')
self.l.pack(side="top", fill="both", expand=True, padx=100, pady=100)
self.t.lift(self.master)
#self.t.geometry('640x480+0+0')
self.play = tk.Button(self.t, text="play", bg="purple", command=self.letsplay)
self.play.pack(side='left', expand=True)
self.ops = tk.Button(self.t, text="options", bg="yellow", command=self.ops)
self.ops.pack(side='left', expand=True)
def ops(self):
self.opwin = tk.Toplevel(self.master, backgroun='red')
self.opwin.wm_title("Option Menu")
self.opl = tk.Label(self.opwin, text="Welcome to Treasure Hunt", background='red')
self.opl.pack(side="top", fill="both", expand=True, padx=100, pady=100)
self.opwin.lift()
#self.opwin.geometry('640x480+0+0')
self.appp = tk.Button(self.opwin, text="Apply", bg="purple", command=self.letsplay)
self.appp.pack(side='left', expand=True)
self.gridops = tk.Listbox(self.opwin, selectmode='SINGLE')
self.gridops.pack()
for i in range(6, 14):
self.gridops.insert(tk.END, i)
self.gridrange = self.gridops.curselection()
def letsplay(self):
self.t.destroy()
#self.opwin.destroy()
#self.begin()
self.createWidgets()
root.lift()
def createWidgets(self):
#print (self.gridrange)
root.lift()
root.after_cancel(self.tick)
self.matrix = [["#" for col in range(self.gridrange)] for row in range(self.gridrange)]
self.current_pos = [0,0]
self.treasure_pos = []
self.bandit_pos = []
#times treasure has been found
self.treasures_won = []
self.last_treasure = []
self.gold_found = 0
for i in range(0, self.numchest):
self.gold_xy = self.get_pos()
self.treasure_pos.append(self.gold_xy)
for i in self.treasure_pos:
print (i)
print (len(self.treasure_pos))
for i in range (0, self.bandits):
self.bandit_xy = self.get_pos()
self.bandit_pos.append(self.bandit_xy)
for i in self.bandit_pos:
print (i)
print (len(self.bandit_pos))
#self.treasure_pos = [0,0]
#print self.treasure_pos
self.sett()
top=self.winfo_toplevel()
self.entry_frame = tk.Frame(self)
self.entry_frame.grid(row=0, column=0,rowspan=1,columnspan=8,sticky=tk.N+tk.E+tk.W)
self.pos_frame = tk.Frame(self)
self.pos_frame.grid(row=1, column=0,columnspan=8,sticky=tk.N+tk.S+tk.E+tk.W)
self.entry_frame.rowconfigure(0,weight=2)
screenx=self.pos_frame.winfo_width()
screeny=self.pos_frame.winfo_height()
for i in range(8):
top.rowconfigure(i,weight=1)
top.columnconfigure(i,weight=1)
self.pos_frame.columnconfigure(i,weight=1)
self.pos_frame.rowconfigure(i,weight=1)
self.entry_frame.columnconfigure(i,weight=1)
self.rowconfigure(i,weight=1)
self.columnconfigure(i,weight=1)
self.label_no_of_moves=tk.Label(self.entry_frame,text="MOVES : ")
self.label_no_of_moves.grid(row=0,column=0,sticky=tk.N+tk.S+tk.E+tk.W)
self.moves=tk.StringVar()
self.number_of_moves=tk.Entry(self.entry_frame,textvariable=self.moves)
self.number_of_moves.grid(row=0,column=1,sticky=tk.N+tk.S+tk.E+tk.W)
self.label_direction=tk.Label(self.entry_frame,text="DIRECTION : ")
self.label_direction.grid(row=0,column=2,sticky=tk.N+tk.S+tk.E+tk.W)
#Assume Direction to be U D L R you can also use listbox here but i m not using it for saving time
self.dir=tk.StringVar()
self.direction=tk.Entry(self.entry_frame,textvariable=self.dir)
self.direction.grid(row=0,column=3,sticky=tk.N+tk.S+tk.E+tk.W)
self.direction=tk.Button(self.entry_frame,text='GO',command=self.gomoves)
self.direction.grid(row=0,column=4,sticky=tk.N+tk.S+tk.E+tk.W)
for i in range(8):
for j in range(8):
x='label'+str(i)+str(j)
self.x=tk.Label(self.pos_frame,bg="#"+"F"+str(5*(1+i))[0]+str(4*(i+1))[0]+str(6*(j+2))[0]+str(3*(j+1))[0]+"7",text=str(i)+str(j))
self.x.grid(row=i,column=j,sticky=tk.N+tk.S+tk.E+tk.W)
#For updating the grid if window size is changed
self.bind('<Configure>',self.update)
#For initial Player position
self.start_position=str('label'+generate_initial_position())
self.current_position = self.start_position
print (self.current_position)
#initial position removed from positions(avaliable positions)
print (positions)
positions.remove(str(self.start_position))
#selecting treasure chest from remaining positions
self.treasure_chest_positions=random.sample(positions,10)
#removing treasures chest position from positions(avaliable positions)
for i in positions[:]:
if i in self.treasure_chest_positions:
positions.remove(i)
print (self.treasure_chest_positions)
#selecting bandits position from positions(avaliable positions)
self.bandit_positions =random.sample(positions,5)
def sett(self):
self.blink = False
self.guesses = 0
self.end_tick = False
self.tick()
def get_pos(self):
self.used = False
xy = random.randrange(self.gridrange), random.randrange(self.gridrange)
if xy in self.treasure_pos or xy in self.bandit_pos:
self.used = True
while self.used == True:
xy = (random.randrange(self.gridrange), random.randrange(self.gridrange))
if xy not in self.treasure_pos or xy in self.bandit_pos:
self.used = False
return xy
else:
return xy
def gomoves(self,event=None):
print ('GO MOVES')
#Validate Moves so that the values of moves should lie inside the positions avaliable
print (self.moves.get())
print (self.dir.get())
#On moving update the current position variable
#Please deal with the position written in format label(row_no)(column_no) like label01 for row=0 and column=1 it will not be that difficult
current_row= int(self.current_position[5:6])
current_column=int(self.current_position[6:7])
print (current_row , current_column)
#now update the position based on moves if moves are invalid then pop up tkDialogBox search a little on google you will get help from there
#self.current_position =
def key_pressed(self, event):
if event.keysym == "Right" and self.current_pos[1] < 7:
self.current_pos[1] += 1
print("right")
elif event.keysym == "Left" and self.current_pos[1] > 0:
self.current_pos[1] -= 1
print("left")
elif event.keysym == "Up" and self.current_pos[0] > 0:
self.current_pos[0] -= 1
print("up")
elif event.keysym == "Down" and self.current_pos[0] < 7:
self.current_pos[0] += 1
print("down")
elif event.keysym == "Return":
self.process_guess()
self.display_grid()
self.matrix = [["#" for col in range(8)] for row in range(8)] # is here the best place for this?
def check(self):
if self.current_pos_tuple in self.treasures_won:
self.counts = Counter(self.treasures_won)
print (self.counts)
print (self.counts)[self.current_pos_tuple]
if self.current_pos_tuple in self.last_treasure:
self.gold_found -= 10
self.treasures_won.remove(self.current_pos_tuple)
self.guesses -= 1
self.last_treasure.remove(self.current_pos_tuple)
print (self.last_treasure)
if self.counts[self.current_pos_tuple] > 3:
self.treasure_pos.remove(self.current_pos_tuple)
self.bandit_pos.append(self.current_pos_tuple)
print ('someone was here waiting for me')
self.gold_found -= 10
print (self.gold_found)
if self.gold_found >= self.winscore:
print ('you win')
if len(self.treasure_pos) <= 0:
print ('you lose')
def process_guess(self):
self.guesses += 1
self.score_lbl.config(text="Guesses: " + str(self.guesses))
self.current_pos_tuple = tuple(self.current_pos)
print (self.current_pos_tuple)
if self.current_pos_tuple in self.treasure_pos:
self.check()
print ('i think i see something shiney')
self.gold_found += 10
print (self.gold_found)
self.treasures_won.append(self.current_pos_tuple)
self.last_treasure.append(self.current_pos_tuple)
self.end_tick = True
self.gold_lbl.config(text="Gold: " + str(self.gold_found))
self.matrix[self.current_pos_tuple[0]][self.current_pos_tuple[1]] = "$"
self.display_grid()
elif self.current_pos_tuple in self.bandit_pos:
print ('something looks suspisious')
self.gold_found = 0
#if not (self.current_pos[0] == self.treasure_pos[0] and self.current_pos[1] == self.treasure_pos[1]):
#print "NOT HERE"
else:
randinteger = random.randrange(10)
dist = int(round(math.sqrt((self.current_pos[0] - self.treasure_pos[randinteger][0]) ** 2 + (self.current_pos[1] - self.treasure_pos[randinteger][1]) ** 2)))
self.matrix[self.current_pos[0]][self.current_pos[1]] = str(dist)
self.display_grid()
print (' i cant seem to find anything')
self.end_tick = True
def display_grid(self):
'''Displays current visual game state'''
self.island.delete(1.0, tk.END)
m_str = ""
for row in range(len(self.matrix)):
m_str += (" ".join(self.matrix[row]) + "\n")
self.island.insert(1.0, m_str)
def finish(self):
self.matrix[self.treasure_pos[self.current_pos_tuple][0]][self.treasure_pos[self.current_pos_tuple][1]] = "$"
self.display_grid()
self.island.insert(tk.END, " + 10 Gold!")
self.sett()
def update(self,event=None):
print (event.num)
screenx=self.pos_frame.winfo_width()
screeny=self.pos_frame.winfo_height()
for i in range(8):
for j in range(8):
x='label'+str(i)+str(j)
if x in self.treasure_chest_positions:
try:
self.x=tk.Label(self.pos_frame,bg="#"+"F"+str(5*(1+i))[0]+str(4*(i+1))[0]+str(6*(j+2))[0]+str(2*(j+1))[0]+"7",text='Treasure')
self.x.grid(row=i,column=j,sticky=tk.N+tk.S+tk.E+tk.W)
except:
print("Treasure error")
elif x in self.bandit_positions:
self.x=tk.Label(self.pos_frame,bg="#"+"F"+str(5*(1+i))[0]+str(4*(i+1))[0]+str(6*(j+2))[0]+str(2*(j+1))[0]+"7",text='Bandit')
self.x.grid(row=i,column=j,sticky=tk.N+tk.S+tk.E+tk.W)
elif x==self.current_position:
self.x=tk.Label(self.pos_frame,bg="#"+"F"+str(5*(1+i))[0]+str(4*(i+1))[0]+str(6*(j+2))[0]+str(2*(j+1))[0]+"7",text='Current')
self.x.grid(row=i,column=j,sticky=tk.N+tk.S+tk.E+tk.W)
else:
self.x=tk.Label(self.pos_frame,bg="#"+"F"+str(5*(1+i))[0]+str(4*(i+1))[0]+str(6*(j+2))[0]+str(2*(j+1))[0]+"7",text=str(i)+str(j))
self.x.grid(row=i,column=j,sticky=tk.N+tk.S+tk.E+tk.W)
def tick(self):
'''timer for blinking cursor'''
if self.blink == False:
self.matrix[self.current_pos[0]][self.current_pos[1]] = "#"
elif self.blink == True:
self.matrix[self.current_pos[0]][self.current_pos[1]] = " "
self.blink = not self.blink
self.display_grid()
if not self.end_tick:
root.after(300, self.tick)
else:
self.sett()
def generate_initial_position():
pos=str(random.randint(0,7))+str(random.randint(0,7))
return pos
root = tk.Tk()
app = Application(root)
#app.master.title('Game')
root.mainloop()
I have a big problem because the code does not want to function while self.gridrange=8 is a tuple it needs to be an integer and I dont know what more you can do to make it an integer
This is the error
()
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Program Files\Python35\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:\Users\Zero Davila\Desktop\new.py", line 110, in letsplay
self.createWidgets()
File "C:\Users\Zero Davila\Desktop\new.py", line 123, in createWidgets
self.matrix = [["#" for col in range(self.gridrange)] for row in range(self.gridrange)]
TypeError: 'tuple' object cannot be interpreted as an integer
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh() This is just so I could post on stack the h's
Your previous posts were incorrect. You did not assign self.gridrange as
self.gridrange = 8
You assign it as:
self.gridops = tk.Listbox(self.opwin, selectmode='SINGLE')
self.gridops.pack()
for i in range(6, 14):
self.gridops.insert(tk.END, i)
self.gridrange = self.gridops.curselection()
If you read through the information for Listbox.curselection() or the actual source code, you'll find that it returns a tuple. Hence your problem.
However, in the future, please create a Minimal, Complete, and Verifiable Example to help in debugging. It would have helped immensely.

Categories