2
\$\begingroup\$

Please give me some pointers on my first Python program!

Points of interest:

  • Within the calculate() function, I wanted to return correctAnswer in order to avoid using it as a global variable. When passing calculate to isCorrect(), I would get a TypeError: missing 1 required positional argument. After a ton of searching I wasn't able to avoid using the global variable.
  • This leads me to the point on global variables: I tried my darnest to avoid using them across the board but I must be missing something obvious that's preventing me from understanding how to do so.
  • I will be honest, I didn't place much time on the playAgain() function but not because I didn't want to--I just didn't have many ideas of what I wanted. Any pointers on a better "restart" button are welcomed.

    from tkinter import Entry
import tkinter as tk
import random as rand
import sys
import os

root = tk.Tk()
root.geometry("450x250+500+300")
root.title("Let's play Math!")

welcomeLabel = tk.Label(text="LET'S PLAY MATH!").pack()
startLabel = tk.Label(text='Select a math operation to start').pack()

def genRandom():
    a = rand.randrange(1,10,1)
    b = rand.randrange(1,10,1)

    return a, b

def calculate(method):

    global correctAnswer

    ranNumber = genRandom()

    if method == "Add":
        correctAnswer = int(ranNumber[0] + ranNumber[1])
        calcString = "+"
    elif method == "Sub":
        correctAnswer = int(ranNumber[0] - ranNumber[1])
        calcString = "-"

    QuestionLbl = tk.Label(text="What does {0} {1} {2} equal?".format(ranNumber[0], calcString, ranNumber[1]))
    QuestionLbl.place(x=0, y=125)

    global answerEntry
    answerEntry = Entry()
    answerEntry.place(x=300, y=125)

    isCorrectBtn = tk.Button(text="Check Answer", command=isCorrect)
    isCorrectBtn.place(x=300, y = 150)
def isCorrect():

    answerEntered = int(answerEntry.get())

    if answerEntered != correctAnswer:
        checkAnswerLbl = tk.Label(text="Let's try again.")
        checkAnswerLbl.place(x=150, y=125)
    elif answerEntered == correctAnswer:
        checkAnswerLbl = tk.Label(text="Hooray!")
        checkAnswerLbl.place(x=150, y=125)

    restartBtn = tk.Button(text="Play Again?", command=playAgain)
    restartBtn.place(x=300, y = 200)

def playAgain():
    python = sys.executable
    os.execl(python, python, * sys.argv)

addBtn = tk.Button(text="Addition", command=lambda: calculate("Add"))
addBtn.place(x=160, y = 60)
subBtn = tk.Button(text="Subtraction", command=lambda: calculate("Sub"))
subBtn.place(x=220, y=60)

tk.mainloop()

Through some research I've updated the following:

Thanks to @Shule, I've settled on using the code below. I added some comments of my own, moved some stuff around to try to make a bit more sense of what's going on, and also created the rightWrongTxt string. However, @Shule deserves most of the credit for making this code better.

import tkinter as tk
import random as rand

class Game:
    def __init__(self):
        # Initializing the widgets and the variables you want to access from multiple methods

    self.root = tk.Tk()

    #defining actual dimensions of the window
    self.root.geometry("450x250+500+300")

    #window title; does not require .pack()
    self.root.title("Let's play Math!")

    #LABELS
    #.pack() will display labels in the middle of the screen
    self.welcomeLbl = tk.Label(text="LET'S PLAY MATH!")
    self.welcomeLbl.pack()

    self.startLbl = tk.Label(text='Select a math operation to start')
    self.startLbl.pack()

    self.QuestionLbl = tk.Label()

    self.rightWrongLbl = tk.Label()
    self.rightWrongTxt = tk.StringVar()

    #BUTTONS
    self.addBtn = tk.Button(text="Addition", command=lambda: self.calculate("Add"))
    self.addBtn.place(x=150, y=60)

    self.subBtn = tk.Button(text="Subtraction", command=lambda: self.calculate("Sub"))
    self.subBtn.place(x=225, y=60)

    self.isCorrectBtn = tk.Button(text="Check Answer", command=self.isCorrect)

    self.restartBtn = tk.Button(text="Play Again?", command=self.playAgain)

    #INPUT BOX
    self.answerEntry = tk.Entry()
    self.answerEntry.focus_set()

    #ADDITIONAL VARIABLES
    self.correctAnswer = None

def genRandom(self):
    a = rand.randrange(1,10,1)
    b = rand.randrange(1,10,1)

    return a, b

def calculate(self, method):

    self.addBtn.config(state='disabled')
    self.subBtn.config(state='disabled')

    randNum = self.genRandom()

    if method == "Add":
        self.correctAnswer = int(randNum[0] + randNum[1])
        calcString = "+"
    elif method == "Sub":
        self.correctAnswer = int(randNum[0] - randNum[1])
        calcString = "-"

    #Question generation
    self.QuestionLbl.config(text="What does {0} {1} {2} equal?".format(randNum[0], calcString, randNum[1]))
    self.QuestionLbl.place(x=0, y=125)

    self.answerEntry.place(x=300, y=125)

    self.isCorrectBtn.place(x=300, y=150)

def isCorrect(self):
    try:
        answerEntered = int(self.answerEntry.get())
    except ValueError:
        self.rightWrongTxt.set("Non-numeric input. \nEnter numbers only.")
        self.rightWrongLbl.config(textvariable = self.rightWrongTxt)
        self.rightWrongLbl.place(x=150, y=125)

    if answerEntered != self.correctAnswer:
        self.rightWrongTxt.set("Let's try again.")
        self.rightWrongLbl.config(textvariable = self.rightWrongTxt)
        self.rightWrongLbl.place(x=150, y=125)
    elif answerEntered == self.correctAnswer:
        self.rightWrongTxt.set("Hooray!")
        self.rightWrongLbl.config(textvariable = self.rightWrongTxt)
        self.rightWrongLbl.place(x=150, y=125)

    self.restartBtn.place(x=300, y=200)

def playAgain(self):

    self.rightWrongLbl.config(text="Let's try again.")

    self.addBtn.config(state='active')
    self.subBtn.config(state='active')

    self.answerEntry.place_forget()
    self.answerEntry.delete(0, "end")

    self.QuestionLbl.place_forget()
    self.isCorrectBtn.place_forget()
    self.rightWrongLbl.place_forget()
    self.restartBtn.place_forget()

if __name__ == '__main__':
    game = Game()
    game.root.mainloop()
\$\endgroup\$
  • \$\begingroup\$ Thanks @Shule, when I first started this project I had done so. However, as I began to encounter issues and searching for answers I ended up not using a class. I still have that other code; I'll refactor and post(might take me a bit). Now I know. \$\endgroup\$ – Nahuatl_C137 Jan 23 '18 at 4:28
2
\$\begingroup\$
  • Make sure the focus is in a convenient place (in this case, it would be nice if it were always in the Entry widget; that way, we wouldn't have to click on it).
  • Your buttons are overlapping on my screen (the subtraction button begins before the addition button is finished).
  • If you could avoid the need to restart the program, that would be great (in some cases it might be important, but I don't think this is one of them). You can just show/hide and reset the widgets.
  • I would suggest consolidating the program into a class, for readability, organization, and ease of access. That way, you can define all your widgets in one place, and access them (as well as any needed variables) from everywhere in the class (via self, which is the first parameter of every method, although you can technically name it whatever you want instead of 'self'). You don't need any global variables this way. If you're new to object-oriented programming, you'll want to learn up on classes and objects to know more about how this works. It's just another way to organize your code to make things convenient. The __init__ method is the constructor (which executes when the game object is created.
  • Check to make sure the answer is an integer (if it only takes integers for input).
  • I didn't fix it in the code below, but you'll want to change some variable names for the sake of naming conventions. For instance, some of those are capitalized that shouldn't be. The code will still run, but it'll be weird for many people. You might want to check out PEP 8 for a big list of conventions (some of which I probably don't follow, but perhaps should, like the maximum line length).

There are other improvements for fine-tuning that I could probably suggest, but this is my answer for now. Here's the amended code:

import tkinter as tk
import random as rand

class Game:
    def __init__(self):
        #Initializing the widgets and the variables you want to access from multiple methods
        self.root = tk.Tk()
        self.root.geometry("450x250+500+300")
        self.frame = tk.Frame(self.root)
        self.frame.pack()
        self.correctAnswer=None
        self.root.title("Let's play Math!")
        self.root.welcomeLabel = tk.Label(text="LET'S PLAY MATH!").pack()
        self.startLabel = tk.Label(text='Select a math operation to start').pack()
        self.addBtn = tk.Button(text="Addition", command=lambda: self.calculate("Add"))
        self.addBtn.place(x=160, y = 60)
        self.subBtn = tk.Button(text="Subtraction", command=lambda: self.calculate("Sub"))
        self.subBtn.place(x=235, y=60)
        self.correctAnswer=None
        self.answerEntry = tk.Entry()
        self.answerEntry.focus_set()
        self.checkAnswerLbl = tk.Label(text="Let's try again.")
        self.restartBtn = tk.Button(text="Play Again?", command=self.playAgain)
        self.isCorrectBtn = tk.Button(text="Check Answer", command=self.isCorrect)
        self.QuestionLbl = tk.Label()
    def genRandom(self):
        a = rand.randrange(1,10,1)
        b = rand.randrange(1,10,1)
        return a, b
    def calculate(self, method):
        self.playAgain() #This is to clear the widgets if you press one of the buttons before you give a correct answer
        ranNumber = self.genRandom()
        if method == "Add":
            self.correctAnswer = int(ranNumber[0] + ranNumber[1])
            calcString = "+"
        elif method == "Sub":
            self.correctAnswer = int(ranNumber[0] - ranNumber[1])
            calcString = "-"
        self.QuestionLbl.config(text="What does {0} {1} {2} equal?".format(ranNumber[0], calcString, ranNumber[1]))
        self.QuestionLbl.place(x=0, y=125)
        self.answerEntry.place(x=300, y=125)
        self.isCorrectBtn.place(x=300, y = 150)
    def isCorrect(self):
        try:
            answerEntered=int(self.answerEntry.get())
        except ValueError:
            #If it's not an integer, you get a ValueError, which we catch to prevent the program from producing an error.
            #You could make another widget that displays some kind of response here when they put in input that doesn't compute, but I'm just making it so it doesn't respond to it here.
            return
        if answerEntered != self.correctAnswer:
            self.checkAnswerLbl.place(x=150, y=125)
        elif answerEntered == self.correctAnswer:
            self.checkAnswerLbl.config(text="Hooray!")
            self.checkAnswerLbl.place(x=150, y=125)
        self.restartBtn.place(x=300, y = 200)
    def playAgain(self):
        #We're hiding the appropriate widgets and clearing the text of the Entry widget.
        self.answerEntry.place_forget()
        self.answerEntry.delete(0, "end")
        self.restartBtn.place_forget()
        self.checkAnswerLbl.place_forget()
        self.isCorrectBtn.place_forget()
        self.QuestionLbl.place_forget()

if __name__ == '__main__': #This means, only run the following code if we're running this file directly (rather than from another module)
    game = Game() #We're creating an object called game that is an instance of the Game class.
    game.root.mainloop()
\$\endgroup\$
  • 1
    \$\begingroup\$ Placing all the widgets in one section makes everything so much easier, as well as, creating a class. I'm currently taking a more advance python course online which will hopefully cover classes more in depth. Looking through some of the other pointers. I will post the refactored code in a bit. Thank you so much! \$\endgroup\$ – Nahuatl_C137 Jan 24 '18 at 2:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.