[Tutor] Times Tables Program that constantly tells you that you are wrong!

Alan Gauld alan.gauld at yahoo.co.uk
Fri Aug 17 06:18:18 EDT 2018


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




More information about the Tutor mailing list