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.