7
\$\begingroup\$

I recently got feedback for a programming assignment that was positive but mentioned that 'some things could be done better', and I was wondering if anybody here might be able to give me a more specific idea of what could to be improved & why.

There's quite a bit of code, but as I'm at a very introductory level I'm hoping it won't be too difficult to pick out some basic concepts that need work. (Also, I'm not too worried about how the functionality of the program could be improved - that was informed by the guidelines of the assignment - I'm more looking for advice on how to improve the code for the functionality that is currently there).

The assignment was to create a food quiz GUI that reads data from a txt file, & a CLI admin that creates/edits that file.

CLI:

import json

# Repeatedly prompt for input until an integer is entered.
def inputInt(prompt):

    while True:

        try:
            answer = int(input(prompt))
        except ValueError:
            print('Please enter a number.')
            continue
        break

    return answer


# Repeatedly prompt for input until something (not whitespace) is entered.
def inputSomething(prompt):
    answer = input(prompt)

    while not answer.strip(): 
        answer = input('Please enter a non-whitespace value: ')

    return answer


# Open "data.txt" in write mode and write the data to it in JSON format.
def saveChanges(dataList):
    f = open('data.txt', 'w')
    json.dump(dataList, f, indent=4)
    f.close()

# If the file does not exist or does not contain JSON data, set "data" to an empty list instead.
try: 
    f = open('data.txt', 'r')
    data = json.load(f)
    f.close()
except (FileNotFoundError, ValueError):
    data = []

# Print welcome message, then enter the endless loop which prompts the user for a choice.
print('Welcome to the Food Quiz Admin Program.')

while True:
    print('Choose [a]dd, [l]ist, [s]earch, [v]iew, [d]elete or [q]uit.')
    choice = input('> ')

    if choice == 'a':

        print('Enter details for new menu item.')
        #Create new item and prompt for values
        item = {}
        item['name'] = inputSomething('Name: ')
        item['calories'] = inputInt('Calories: ')
        item['fat'] = inputInt('Fat: ')
        item['protien'] = inputInt('Protien: ')
        item['carbohydrates'] = inputInt('Carbohydrates: ')
        item['sodium'] = inputInt('Sodium: ')
        item['cholesterol'] = inputInt('Cholesterol: ')

        #Add item to data list and save changes
        data.append(item)
        saveChanges(data)


    elif choice == 'l':

        #Loop though and list the current menu items
        for i, item in enumerate(data):
            print(i, ': ', item)

    elif choice == 's':
        #Prompt for search term and convert to lowercase
        search = inputSomething('Search items by name: ').lower()

        #Loop through data and print items with search term in name
        for i, item in enumerate(data):
            if search in item['name'].lower():
                print(i, ': ', item)


    elif choice == 'v':

       while True:
           #Try to print item by index, reprompt if index is not valid
            try:
                indexItem = data[inputInt('Enter index of item to view: ')]

                print('{name}, Calories: {calories}, Fat: {fat} g, Protien: {protien} g, Carbohydrates: {carbohydrates} g, Cholesterol: {cholesterol} mg, Sodium: {sodium} mg'.format(**indexItem))              
            except IndexError:
                print('Invalid index. Please enter the index of an existing item.')
                continue

            break


    elif choice == 'd':

        while True:
            #Try to delete item by index, reprompt if index is not valid
            try:
               del data[inputInt(prompt)]

            except IndexError:
                print('Invalid index. Please enter the index of an existing item.')
                continue

            break

        saveChanges(data)             

    elif choice == 'q':
        # Quit the program
        print('Goodbye!')
        break


    else:
        # Print 'invalid choice' message
        print('Invalid choice.')

GUI:

import tkinter
import tkinter.messagebox
import tkinter.font
import json
import random


class ProgramGUI:

    def __init__(self):
        #Create main window
        self.main = tkinter.Tk()
        self.main.title('Food Quiz!')
        self.main.geometry('450x225')
        self.main.configure(bg='#5c9ead')

        self.font1 = tkinter.font.Font(family='Helvetica', size=18, weight='bold')
        self.font2 = tkinter.font.Font(family='Courier', size=14, weight='bold')

        #Load data from data.txt, show error message & terminate if file does not exist or is invalid
        try:
            self.__f = open('data.txt', 'r')
            self.data = json.load(self.__f)
            self.__f.close()

        except (FileNotFoundError, ValueError):
            tkinter.messagebox.showerror('Oops!', 'Missing/Invalid file.')
            self.main.destroy()
            return

        #Define components, initiliase score and label variables 
        self.components = ['calories', 'fat', 'cholesterol', 'sodium', 'carbohydrates', 'protien']
        self.qsAsked = 0
        self.qsCorrect = 0
        self.name1 = tkinter.StringVar()
        self.name2 = tkinter.StringVar()
        self.componentLab = tkinter.StringVar()

        #Create frames
        self.nameBox = tkinter.Frame(self.main, bg='#5c9ead')
        self.questionBox = tkinter.Frame(self.main, bg='#5c9ead')
        self.buttonBox = tkinter.Frame(self.main, bg='#5c9ead')

        #Create buttons and labels
        tkinter.Label(self.nameBox, textvariable=self.name1, font=self.font1, bg='#5c9ead', fg='#EFBC9B').pack(side='left')
        tkinter.Label(self.nameBox, text='vs', font=self.font2, bg='#5c9ead', fg='#fffdf7').pack(side='left')
        tkinter.Label(self.nameBox, textvariable=self.name2, font=self.font1, bg='#5c9ead', fg='#EFBC9B').pack(side='left')
        tkinter.Label(self.questionBox, text='Which one has more...', font=self.font2, bg='#5c9ead', fg='#fffdf7').pack()
        tkinter.Label(self.questionBox, textvariable=self.componentLab, font=self.font1, bg='#5c9ead', fg='#EFBC9B').pack()
        tkinter.Button(self.buttonBox, textvariable=self.name1, font=self.font2, bg='#EFBC9B', fg='#FFFDF7', command=lambda: self.checkAnswer('left')).pack(side='left')
        tkinter.Button(self.buttonBox, text='Roughly Equal', font=self.font2, bg='#EFBC9B', fg='#FFFDF7', command=lambda: self.checkAnswer('middle')).pack(side='left', padx=5)
        tkinter.Button(self.buttonBox, textvariable=self.name2, font=self.font2, bg='#EFBC9B', fg='#FFFDF7',command=lambda: self.checkAnswer('right')).pack(side='left')

        self.nameBox.pack(pady=10)
        self.questionBox.pack(pady=10)
        self.buttonBox.pack(pady=20)

        #Show first question
        self.showQuestion()

        #Start main loop
        tkinter.mainloop()

    def showQuestion(self):
        #Create/update question attributes
        self.items = random.sample(self.data, 2)
        self.component = random.choice(self.components)

        #Update labels
        self.name1.set(self.items[0].get('name'))
        self.name2.set(self.items[1].get('name'))
        self.componentLab.set(self.component)

    def checkAnswer(self, clickedButton):
        #Update question counter and get values to compare
        self.qsAsked += 1
        self.val1 =  self.items[0].get(self.component)
        self.val2 =  self.items[1].get(self.component)
        #Define left button check (left > right), right button check (right > left) and middle button check (both values are within 10% of each other, assuming val1 is > 0)
        self.buttons = {'left': self.val1 > self.val2, 'right': self.val2 > self.val1, 'middle': self.val1 * 10 / 11 <= self.val2 <= self.val1 * 11 / 10}

        #Select check, update correct counter and display appropriate message 
        if self.buttons.get(clickedButton):
            self.qsCorrect +=1
            self.scoreMsg = 'Your score so far is ' + str(self.qsCorrect) + '/' + str(self.qsAsked)
            tkinter.messagebox.showinfo('Correct!', 'You got it right. Your score so far is ' + str(self.qsCorrect) + '/' + str(self.qsAsked))

        else:
            tkinter.messagebox.showerror('Incorrect!', 'You got it wrong. Your score so far is ' + str(self.qsCorrect) + '/' + str(self.qsAsked))

        #Generate new question
        self.showQuestion()

# Create an object of the ProgramGUI class to begin the program.
gui = ProgramGUI()
\$\endgroup\$
2
  • \$\begingroup\$ I would add a if __name__ == "__main__" and add your main loop to that. Also there is no need to use exceptions to check for invalid indices, these can be checked explicitly. \$\endgroup\$
    – evan.oman
    Dec 11, 2016 at 18:40
  • \$\begingroup\$ You cannot remove the code after the post receives an upvoted answer and can no longer be removed manually. I've locked this post to prevent another code removal. If you wish to remove your name from this post, you may flag for moderator intervention to request it. \$\endgroup\$
    – Jamal
    Nov 7, 2017 at 3:30

1 Answer 1

2
\$\begingroup\$

Very nice! You're off to a strong start.

In your CLI code, I'd put the code for each option in its own function, rather than having all of that in the main block. It makes it a lot easier to follow, and wouldn't require much work.

Also, FYI, 'protien' is spelled 'protein'.

\$\endgroup\$

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