[Tutor] Times Tables Program that constantly tells you that you are wrong!
Matthew Polack
matthew.polack at htlc.vic.edu.au
Thu Aug 23 00:47:49 EDT 2018
Hi Alan,
I'm working my way through some of the tips you provided and tried to use
the code given....but am getting an error at Line 75 in my code re: not
enough arguments for format string
_
return self.func(*args)
File "Timespicture.py", line 75, in checkanswer
Your current score is: %f""" % answer,score
TypeError: not enough arguments for format string
The instructions you game me were:
>
> Is there a way to simply combine all these commands with one txt.insert
and
> using commas?
Not with commas but you can use line breaks inside
a triple quoted string and just do a single insert.
In fact one of the improvements I'd suggest for
your code is to use string formatting to insert
the values into your strings before inserting them.
eg:
fail_str = """
Sorry, you got it wrong,
the correct answer was %d
Your current score is: %f""" % answer,score
txt.insert(1.0, fail_str, "center")
Can you see what is going wrong?
My code in that section has this:
def checkanswer():
txt.delete(0.0, 'end')
# as each variable changes outside of function...we need to make these
global.
global score
global mistakes
global total
# checks for what is written in answerbox using the 'get'
feature...makes it an integer.
response = int(answerbox.get())
wordScore = "Your score is now "
if response == answer:
score += 1
total += 1
result = "Great Job! "
root.update()
viewSC()
viewPercent()
viewTotal()
else :
total += 1
result = "Sorry...you made a mistake. \n "
# the \n above adds a line break.
mistakes += 1
viewMistakes()
viewTotal()
viewPercent()
fail_str = """
Sorry, you got it wrong,
the correct answer was %d
Your current score is: %f""" % answer,score
txt.insert(1.0, fail_str, "center")
Full code of whole program below....
Thanks!
- Matt
from tkinter import *
import random
# GLOBAL VARIABLES
# Created with a starting value.
answer = 0
score = 0
wrong = 0
mistakes = 0
total = 0
def makeproblem():
# access global answer...this is only required IF you need to update
the variable within the function.
# this happens below when answer is changed to number1 * number2
global answer
# erases all text in current text box.
txt.delete(0.0, 'end')
sentence = "Here is your problem "
# example of generating a random number.
number1 = random.randint(2,12)
number2 = random.randint(2,12)
answer = number1 * number2
# creates a justification command called centre. (Not too sure why this
is needed here...but not on the answer box elsewhere!)
center = txt.tag_config("center", justify="center")
txt.insert(0.0, sentence, "center")
txt.insert(2.2, number1, "center")
txt.insert(3.3, " x ", "center")
txt.insert(4.4, number2, "center")
def checkanswer():
txt.delete(0.0, 'end')
# as each variable changes outside of function...we need to make these
global.
global score
global mistakes
global total
# checks for what is written in answerbox using the 'get'
feature...makes it an integer.
response = int(answerbox.get())
wordScore = "Your score is now "
if response == answer:
score += 1
total += 1
result = "Great Job! "
root.update()
viewSC()
viewPercent()
viewTotal()
else :
total += 1
result = "Sorry...you made a mistake. \n "
# the \n above adds a line break.
mistakes += 1
viewMistakes()
viewTotal()
viewPercent()
fail_str = """
Sorry, you got it wrong,
the correct answer was %d
Your current score is: %f""" % answer,score
txt.insert(1.0, fail_str, "center")
'''
center = txt.tag_config("center", justify="center")
txt.insert(0.0, result, "center")
txt.insert(3.0, wordScore, "center")
txt.insert(8.1, score, "center")
# txt.insert(1,1, "Score is")
#txt.insert(3,3, score)
'''
'''
All of the above could be replaced with a single format string
and a single insert.
display = """
Here is your problem:
%d x %d
""" % (number1, number2)
txt.insert(1.0,display, "center")
'''
def about():
txt.delete(0.0, 'end')
instructions = "Here is how you play the game. Press generate
problem..and then enter your answer. Your score will be displayed below."
txt.insert(0.0,instructions)
root = Tk()
root.geometry("640x600+0+0")
root.title("Times Tables Game")
photo = PhotoImage(file="pool.gif")
label = Label(image=photo)
label.image = photo # keep a reference!
label.grid(row=0, columnspan=20)
# MENU SECTION
# These are included as an example menu structure...in many cases they
don't do much...but do feature instructions and a quit feature.
# create a toplevel menu
menubar = Menu(root)
# Just an example of printing hello to console for use in a menu item.
def hello():
print ("hello!")
# display the menu
root.config(menu=menubar)
# create a pulldown menu, and adds it to the menu bar
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", command=makeproblem)
filemenu.add_command(label="Save", command=makeproblem)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=root.quit)
menubar.add_cascade(label="File", menu=filemenu)
# create more pulldown menus
editmenu = Menu(menubar, tearoff=0)
editmenu.add_command(label="Cut", command=checkanswer)
editmenu.add_command(label="Copy", command=checkanswer)
editmenu.add_command(label="Paste", command=checkanswer)
menubar.add_cascade(label="Edit", menu=editmenu)
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", command=about)
menubar.add_command(label="Hello", command=hello)
menubar.add_cascade(label="Help", menu=helpmenu)
menubar.add_command(label="Quit", command=root.quit)
# Plain text labels at top of window.
timeslabel = Label(root, text="Times Tables Practice", fg="white",
bg="blue", font=("arial", 36, "bold"))
timeslabel.grid(columnspan=12, sticky='ew')
instruction = Label(root, text="Please click on the button to generate a
problem", fg="blue", bg="white", font=("arial", 16, "bold"))
instruction.grid(row=2, columnspan=20)
# Makes an entry box with the variable of 'answerbox'
answerbox = Entry(root, bg="grey", font=("arial", 24, "bold"), justify
="center")
answerbox.grid(row=15, columnspan=2, padx=0, pady=0, sticky=EW)
# Makes a button that generate the Times Tables problem
btn = Button(root, text="GENERATE PROBLEM", bg="blue", fg="white",
command=makeproblem)
btn.grid(row=11, columnspan=2, sticky=EW)
# Makes a button that checks the answer
btn = Button(root, text="CHECK ANSWER", bg="darkblue", fg="white",
command=checkanswer)
btn.grid(row=13, columnspan=2, sticky=EW)
#TEXT BOX AREA
#This important command creates the text box called 'txt'. This is used for
all the text output.
txt = Text(root, width=35, height=3, wrap=WORD, font=("arial", 20, "bold"))
txt.grid(row=12, columnspan=2, sticky=EW )
#Adds a blankline below answer box...but leaves top alignment alone. You
could use PAD...but that command does both sides. There may be another way
to achieve this.
blankline3 = Label(root, text = "", bg = "white")
blankline3.grid(row=17, columnspan=2, sticky='ew')
# SCORING LABELS AND RESULTS SECTION
# Places the labels and the results beside each other in column 0 and
column 1.
# Note: Each 'View' label grabs the starting score from the declarations at
the top. eg. score = 0
scorelab = Label(root, text="Score", bg="green", fg="white", font=("arial",
20, "bold"))
scorelab.grid(row=18, column=0, sticky=E)
scoreViewLab = Label(root, text=score, fg="black", bg="grey",
font=("arial", 20, "bold"))
scoreViewLab.grid(row=18, column=1, sticky=W)
totalLab = Label(root, text="Out of", bg="purple", fg="white",
font=("arial", 20, "bold"))
totalLab.grid(row=19, column=0, sticky=E)
totalViewLab = Label(root, text=total, fg="black", bg="grey",
font=("arial", 20, "bold"))
totalViewLab.grid(row=19, column=1, sticky=W)
mistakeslab = Label(root, text="Mistakes", bg="red", fg="white",
font=("arial", 20, "bold"))
mistakeslab.grid(row=20, column=0, sticky=E)
mistakesViewLab = Label(root, text=mistakes, fg="black", bg="grey",
font=("arial", 20, "bold"))
mistakesViewLab.grid(row=20, column=1, sticky=W)
percentlab = Label(root, text="Percentage", bg="aqua", fg="white",
font=("arial", 20, "bold"))
percentlab.grid(row=21, column=0, sticky=E)
percentViewLab = Label(root, text=0, fg="black", bg="grey", font=("arial",
20, "bold"))
percentViewLab.grid(row=21, column=1, sticky=W)
#SCORE UPDATE SECTION.
# Each of these functions allows the 4 scoring labels to be updated as
answers are deemed correct or incorrect with the if/else statement.
# The viewPercent function includes the calculation to update the
percentView label...shows an example of a calculation within a label.
def viewSC():
scoreViewLab["text"] = score
def viewTotal():
totalViewLab["text"] = total
def viewMistakes():
mistakesViewLab["text"] = mistakes
def viewPercent():
percentViewLab["text"] = score/total*100
'''
>>> s = "Here is a string with a rounded float: %.2f" % 42.3456789
>>> s
'Here is a string with a rounded float: 42.35'
'''
# This keeps the program running.
root.mainloop()
Matthew Polack | Teacher
[image: Emailbanner3.png]
Trinity Drive | PO Box 822
Horsham Victoria 3402
p. 03 5382 2529 m. 0402456854
e. matthew.polack at htlc.vic.edu.au
w. www.htlc.vic.edu.au
On Fri, Aug 17, 2018 at 8:18 PM, Alan Gauld via Tutor <tutor at python.org>
wrote:
> On 17/08/18 03:19, Matthew Polack wrote:
>
> > def viewPercent():
> > percentCalc = score/total*100
> > rounded = round(percentCalc, 2)
> > percentViewLab["text"] = rounded
>
> Since you only want the rounded value for display
> this is usually achieved using string formatting:
>
> >>> s = "Here is a string with a rounded float: %.2f" % 42.3456789
> >>> s
> 'Here is a string with a rounded float: 42.35'
>
> That doesn't change the value of the variable but changes how
> it is displayed. There are lots of other options in format
> strings to control justification, padding etc. You should
> definitely explore their capabilities. Just because you
> are using a GUI rather than a console doesn't mean the
> string methods are any less useful.
>
> > from tkinter import *
> > import random
> >
> > # GLOBAL VARIABLES
> > # Created with a starting value.
> > answer = 0
> > score = 0
> > wrong = 0
> > mistakes = 0
> > total = 0
> >
> > def makeproblem():
> > global answer
> >
> > txt.delete(0.0, 'end')
> > sentence = "Here is your problem "
> > number1 = random.randint(2,12)
> > number2 = random.randint(2,12)
> > answer = number1 * number2
> >
> > center = txt.tag_config("center", justify="center")
> >
> > txt.insert(0.0, sentence, "center")
> > txt.insert(2.2, number1, "center")
> > txt.insert(3.3, " x ", "center")
> > txt.insert(4.4, number2, "center")
>
> All of the above could be replaced with a single format string
> and a single insert.
>
> display = """
> Here is your problem:
>
> %d x %d
> """ % (number1, number2)
>
> txt.insert(1.0,display, "center")
>
> > def checkanswer():
> > txt.delete(0.0, 'end')
> >
> > global score
> > global mistakes
> > global total
>
> Its conventional (although not necessary) to put all
> globals at the very top of the function.
>
> > response = int(answerbox.get())
> > wordScore = "Your score is now "
> > if response == answer:
> > score += 1
> > total += 1
> > result = "Great Job! "
> > root.update()
> > viewSC()
> > viewPercent()
> > viewTotal()
> > else :
> > total += 1
> > result = "Sorry...you made a mistake. \n "
> > # the \n above adds a line break.
> > mistakes += 1
> > viewMistakes()
> > viewTotal()
> > viewPercent()
>
> Notice you display Total and Percent in both block
> but do it in an inconsistent order.
> If you took both of this display calls outside
> the if/else you only need to call them once and
> they will be in the same sequence for all cases.
>
> > center = txt.tag_config("center", justify="center")
> > txt.insert(0.0, result, "center")
> > txt.insert(3.0, wordScore, "center")
> > txt.insert(8.1, score, "center")
> > # txt.insert(1,1, "Score is")
> > #txt.insert(3,3, score)
>
> Again this could all be replaced with string
> formatting and a single insert()
>
> > def about():
> > txt.delete(0.0, 'end')
> >
> > instructions = "Here is how you play the game. Press generate
> > problem..and then enter your answer. Your score will be displayed below."
>
> If you use triple quoted strings you can have
> more text and lay it out using line breaks etc.
>
> > txt.insert(0.0,instructions)
>
>
> > root = Tk()
> > root.geometry("640x700+0+0")
> > root.title("Times Tables Game")
> >
> > # MENU SECTION
> > # These are included as an example menu structure...in many cases they
> > don't do much...but do feature instructions and a quit feature.
> >
> > # create a toplevel menu
> > menubar = Menu(root)
> >
> > # Just an example of printing hello to console for use in a menu item.
> > def hello():
> > print ("hello!")
> >
> > # display the menu
> > root.config(menu=menubar)
> >
> > # create a pulldown menu, and adds it to the menu bar
> > filemenu = Menu(menubar, tearoff=0)
> > filemenu.add_command(label="Open", command=makeproblem)
> > filemenu.add_command(label="Save", command=makeproblem)
> > filemenu.add_separator()
> > filemenu.add_command(label="Exit", command=root.quit)
> >
> > menubar.add_cascade(label="File", menu=filemenu)
> >
> > # create more pulldown menus
> > editmenu = Menu(menubar, tearoff=0)
> > editmenu.add_command(label="Cut", command=checkanswer)
> > editmenu.add_command(label="Copy", command=checkanswer)
> > editmenu.add_command(label="Paste", command=checkanswer)
> > menubar.add_cascade(label="Edit", menu=editmenu)
> >
> > helpmenu = Menu(menubar, tearoff=0)
> > helpmenu.add_command(label="About", command=about)
> > menubar.add_command(label="Hello", command=hello)
> > menubar.add_cascade(label="Help", menu=helpmenu)
> > menubar.add_command(label="Quit", command=root.quit)
>
> A bit odd adding a command after you add the cascade.
> Normally we do the cascade as the last item.
>
> > # Plain text labels at top of window.
> > timeslabel = Label(root, text="Times Tables Practice", fg="white",
> > bg="blue", font=("arial", 36, "bold"))
> > timeslabel.grid(columnspan=12, sticky='ew')
> > instruction = Label(root, text="Please click on the button to generate a
> > problem", fg="blue", bg="white", font=("arial", 16, "bold"))
> > instruction.grid(row=2, columnspan=20)
> >
> > # Makes an entry box with the variable of 'answerbox'
> > answerbox = Entry(root, bg="grey", font=("arial", 24, "bold"), justify
> > ="center")
> > answerbox.grid(row=15, columnspan=2, padx=0, pady=0, sticky=EW)
> >
> > # Makes a button that generate the Times Tables problem
> > btn = Button(root, text="GENERATE PROBLEM", bg="blue", fg="white",
> > command=makeproblem)
> > btn.grid(row=11, columnspan=2, sticky=EW)
> >
> > # Makes a button that checks the answer
> > btn = Button(root, text="CHECK ANSWER", bg="darkblue", fg="white",
> > command=checkanswer)
> > btn.grid(row=13, columnspan=2, sticky=EW)
> >
> > #TEXT BOX AREA
> > #This important command creates the text box called 'txt'. This is used
> for
> > all the text output.
> > txt = Text(root, width=35, height=8, wrap=WORD, font=("arial", 20,
> "bold"))
> > txt.grid(row=12, columnspan=2, sticky=EW )
>
> An interesting future option might be to eliminate this
> text box and put it in a dialog (along with the result labels)
> that would open to display the results in a separate window...
> Consider it homework :-)
>
> > #Adds a blankline below answer box...but leaves top alignment alone. You
> > could use PAD...but that command does both sides. There may be another
> way
> > to achieve this.
>
> You can do it in various ways including putting a label in a
> frame and then anchoring the label to the bottom of the
> frame... But an empty label will suffice here. Only GUI
> purists would object. The easiest way is probably just to
> insert a '\n' newline character at the start of the label
> text.
>
> > blankline3 = Label(root, text = "", bg = "white")
> > blankline3.grid(row=17, sticky='ew')
> >
> > # SCORING LABELS AND RESULTS SECTION
> > # Places the labels and the results beside each other in column 0 and
> > column 1.
> > # Note: Each 'View' label grabs the starting score from the declarations
> at
> > the top. eg. score = 0
> > scorelab = Label(root, text="Score", bg="green", fg="white",
> font=("arial",
> > 20, "bold"))
> > scorelab.grid(row=18, column=0, sticky=E)
> >
> > scoreViewLab = Label(root, text=score, fg="black", bg="grey",
> > font=("arial", 20, "bold"))
> > scoreViewLab.grid(row=18, column=1, sticky=W)
> >
> > totalLab = Label(root, text="Out of", bg="purple", fg="white",
> > font=("arial", 20, "bold"))
> > totalLab.grid(row=19, column=0, sticky=E)
> >
> > totalViewLab = Label(root, text=total, fg="black", bg="grey",
> > font=("arial", 20, "bold"))
> > totalViewLab.grid(row=19, column=1, sticky=W)
> >
> > mistakeslab = Label(root, text="Mistakes", bg="red", fg="white",
> > font=("arial", 20, "bold"))
> > mistakeslab.grid(row=20, column=0, sticky=E)
> >
> > mistakesViewLab = Label(root, text=mistakes, fg="black", bg="grey",
> > font=("arial", 20, "bold"))
> > mistakesViewLab.grid(row=20, column=1, sticky=W)
> >
> > percentlab = Label(root, text="Percentage", bg="aqua", fg="white",
> > font=("arial", 20, "bold"))
> > percentlab.grid(row=21, column=0, sticky=E)
> >
> >
> > percentViewLab = Label(root, text=0, fg="black", bg="grey",
> font=("arial",
> > 20, "bold"))
> > percentViewLab.grid(row=21, column=1, sticky=W)
> >
> >
> > #SCORE UPDATE SECTION.
> > def viewSC():
> > scoreViewLab["text"] = score
> >
> > def viewTotal():
> > totalViewLab["text"] = total
> >
> > def viewMistakes():
> > mistakesViewLab["text"] = mistakes
> >
> > def viewPercent():
> > percentViewLab["text"] = rounded
>
> You could replace all of these with a
> single function:
>
> def update_label(label,text):
> label['text'] = text
>
> And supply change the calls from, for example:
>
> viewTotal()
>
> to
>
> update_label(totalViewLab, total)
>
> And you then need a function to calculate
> the percent value:
>
> def percent_string(score, total):
> return ".2f" % (score/total *100)
>
>
> And you can now eliminate the percent global
> variable and call update_label with:
>
> update_label(percentViewLab, percent_string(score, total))
>
>
> ##### Additional teachers notes ####
>
> As a general comment while you can use globals for
> everything it considered bad practice. You should
> strive to minimise use of global variables (google
> global variables for lots of discussion about why).
> Its generally considered better to make your functions
> as independent as possible so that they take in values
> via parameters and return a single value (occasionally
> a tuple). As an example:
>
> # global var
> value = 42
>
> # define a function
> def double(val):
> return val * 2
>
> # Now call it
> value = double(value)
>
> So here we have not had to use the global keyword
> but our function reads the global variable as input
> and returns a new value which we assign to it.
>
> This relies on another good practice for functions
> namely to keep them small and only do a single task.
> If you find a single function updating lots of
> global variables that often indicates that you
> should have more than one function.
>
> In particular we try to separate display from
> calculation. So in your case the check answer
> function should really only check the answer
> and set a success or failure flag.
> You can then have a display result function
> that checks the flag and updates all the
> labels and text.
>
> Normally, I wouldn't highlight these issue to a
> beginner but since you are also a teacher I felt
> that you should be aware. They cross the boundary
> from pure coding to software design. Your code as
> it stands is acceptable for a beginner but in a
> year you will probably look at it and cringe
> slightly... But that is true with any new
> coding venture. :-)
>
> --
> Alan G
> Author of the Learn to Program web site
> http://www.alan-g.me.uk/
> http://www.amazon.com/author/alan_gauld
> Follow my photo-blog on Flickr at:
> http://www.flickr.com/photos/alangauldphotos
>
>
> _______________________________________________
> Tutor maillist - Tutor at python.org
> To unsubscribe or change subscription options:
> https://mail.python.org/mailman/listinfo/tutor
>
--
**Disclaimer: *Whilst every attempt has been made to ensure that material
contained in this email is free from computer viruses or other defects, the
attached files are provided, and may only be used, on the basis that the
user assumes all responsibility for use of the material transmitted. This
email is intended only for the use of the individual or entity named above
and may contain information that is confidential and privileged. If you are
not the intended recipient, please note that any dissemination,
distribution or copying of this email is strictly prohibited. If you have
received this email in error, please notify us immediately by return email
or telephone +61 3 5382 2529** and destroy the original message.*
More information about the Tutor
mailing list