4
\$\begingroup\$

I am building this program in Python to generate 10 random arithmetic questions of either multiplication, addition or subtraction and then saves users scores to a .txt file. I just wanted some help condensing the code as I'm really stuck as of what to do with it.

How can I combine the function (e.g. multiplication, addition and subtraction) into one?

import random
import time
import sys

ans = 0   #variable to hold question answer
question = 0   #question number
user_score = 0  #user's score
userInput = int()   #where user enters the answer
lastName = str()   #holds last name
firstName = str()   #holds first name
form = str()    #holds user's form

def function(score,name):   #writes user's information to a .txt file
    sumOfStudent = (name + ' scored ' + str(user_score))
    classNameTxt = (className, '.txt.')
    f = open(className, 'a')
    f.write(sumOfStudent + form + '\n')
    f.close()

def multiplication():   #creates a multiplication question
    global ans
    numberOne, numberTwo  = random.randint(0,20), random.randint(0,20)
    print("What is" , numberOne , "*" , numberTwo)
    ans = (numberOne * numberTwo)

def subtraction():   #creates a subtraction question
    global ans
    numberOne, numberTwo  = random.randint(0,20), random.randint(0,20)
    print("What is" , numberOne , "-" , numberTwo)
    ans = (numberOne - numberTwo)

def addition():   #creates a addition question
    global ans
    numberOne, numberTwo  = random.randint(0,20), random.randint(0,20)
    print("What is" , numberOne , "+" , numberTwo)
    ans = (numberOne + numberTwo)

operation = [multiplication,subtraction,addition]   #holds all of the opperators
randOperation = random.choice(operation)    #chooses a random operator

lastName = input("Please enter your surname: ").title()
firstName = input("Please enter your first name: ").title()
className = input("Please enter your form: ").title()
print()

def main():   #main game loop - ask questions and checks it against answer, stops are a give amount of questions
    question = 0
    user_score = 0
    randOperation = random.choice(operation)

    while True:
        try:
            randOperation()
            randOperation = random.choice(operation)
            if question >= 10:
                break
            userInput = int(input("Enter the answer: "))
            if userInput == ans:
                print("Correct!" + "\n")
                user_score += 1
                question += 1
            else:
                print("Incorrect!" + "\n")
                question += 1
        except ValueError:
            print("I'm sorry that's invalid")
            question += 1

main()    #initializes the function

print(firstName, lastName , "you scored" , user_score , "out of 10")   #shows the user's score and name

user_name = firstName + ' ' + lastName
function(user_score,user_name)

def endMenu():
    while True:
        try:  
            options = int(input('''Press '1' to view users' scores,
            press '2' to restart the test,
            press '3' to exit the game,

            Enter option here: '''))
        except ValueError:
            print("I'm sorry that was invalid...")

        if options == 3:  #exits the game...
            sys.exit()

        elif options == 2:   #starts the game loop again because it's in a function
            main()

        elif options == 1:   #displays everything on the .txt file
            f = open('userScore.txt', 'r')
            print(f.read())
            print()
            endMenu()

        else:
            print("Sorry, I don't understand. Please try again...")
            print()
            endMenu()        

endMenu()
\$\endgroup\$
4
\$\begingroup\$

Besides the obvious code repetition that you mentioned, a few salient issues deserve mentioning:

  • Your main() function is not really the main code of your program. Rather, you have some free-floating code outside of any function, and the main function is actually called endMenu(), which is surprising.
  • The endMenu() function is improperly recursive. The while True loop should suffice.
  • You use global variables a lot. While firstName and lastName are somewhat excusable (they are entered once by the user, and never change subsequently), you really shouldn't use globals for transient state like ans, question, etc.
  • function is the least-informative name possible for a function.

To address those issues, as well as the code repetition you mentioned, I'd write the program like this:

import operator
import random

OPERATIONS = [
    ('+', operator.add),
    ('-', operator.sub),
    ('*', operator.mul),
]

def random_question(binary_operations, operand_range):
    """Generate a pair consisting of a random question (as a string)
    and its answer (as a number)"""
    op_sym, op_func = random.choice(binary_operations)
    n1 = random.randint(min(operand_range), max(operand_range))
    n2 = random.randint(min(operand_range), max(operand_range))
    question = '{} {} {}'.format(n1, op_sym, n2)
    answer = op_func(n1, n2)
    return question, answer

def quiz(number_of_questions):
    """Ask the specified number of questions, and return the number of correct
    answers."""
    score = 0
    for _ in range(number_of_questions):
        question, answer = random_question(OPERATIONS, range(0, 21))
        print('What is {}'.format(question))
        try:
            user_input = float(input("Enter the answer: "))
        except ValueError:
            print("I'm sorry that's invalid")
        else:
            if answer == user_input:
                print("Correct!\n")
                score += 1
            else:
                print("Incorrect!\n")
    return score

def identify_user():
    # TODO, as an exercise for you
    pass

def display_score(first_name, last_name, class_name):
    # TODO, as an exercise for you
    pass

def menu():
    # TODO, as an exercise for you
    pass

def main():
    first_name, last_name, class_name = identify_user()
    while True:
        menu_choice = menu()
        if menu_choice == 1:        # Display score
            display_score(first_name, last_name, class_name)

        elif menu_choice == 2:      # Run quiz
            QUESTIONS = 10
            score = quiz(QUESTIONS)
            print('{first_name} {last_name}, you scored {score} out of {QUESTIONS}'.format(**locals()))

        elif menu_choice == 3:      # Exit
            break

        else:
            print("Sorry, I don't understand. Please try again...")
            print()

if __name__ == '__main__':
    main()

Observe how there are no global variables: each function accepts parameters and returns specific output values.

\$\endgroup\$
2
\$\begingroup\$

Don't initialize variables like this:

userInput = int()
lastName = str()
firstName = str()
form = str()

This is equivalent and better:

userInput = 0
lastName = ''
firstName = ''
form = ''

When opening files, use with:

with open('userScore.txt', 'r') as f:
    print(f.read())
print()
endMenu()

This deals with safe closing of the file.

\$\endgroup\$

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