# Math quiz for teachers & students - follow-up

I have been programming a maths quiz that can be used for teachers an I have have been trying to make the code as short as possible so it is easier to understand. If you have seen my previous post you maybe be wondering why I am re-posting as I already have received feedback, when trying to act on this feedback I seemed to have got stuck again.

In my code, there's a lot of prompting the user for an input and then checking to see if it's valid - so I have been trying to turn this into a function.

Now some of you maybe confused as why I am unable to do this as I have already done this with the variable get_bool_input. However, for this desired function, it needs to be used in a different context which is what I am unable to do.

import sys
import random

def get_bool_input(prompt=''):
while True:
val = input(prompt).lower()
if val == 'yes':
return True
elif val == 'no':
return False
else:
sys.exit("Not a valid input (yes/no is expected) please try again")

status = input("Are you a teacher or student? Press 1 if you are a student or 2 if you are a teacher")

if status == "1":
score=0
print ("Alright",name,"welcome to your maths quiz")

level_of_difficulty = int(input(("What level of difficulty are you working at?\n"
"Press 1 for low, 2 for intermediate "
"or 3 for high\n")))
if level_of_difficulty not in (1,2,3):
sys.exit("That is not a valid level of difficulty, please try again")

if level_of_difficulty == 3:
ops = ['+', '-', '*', '/']
else:
ops = ['+', '-', '*']

for question_num in range(1, 11):
if level_of_difficulty == 1:
number_1 = random.randrange(1, 10)
number_2 = random.randrange(1, 10)
else:
number_1 = random.randrange(1, 20)
number_2 = random.randrange(1, 20)

operation = random.choice(ops)
maths = round(eval(str(number_1) + operation + str(number_2)),5)
print('\nQuestion number: {}'.format(question_num))
print ("The question is",number_1,operation,number_2)

print("Correct")
score = score + 1
else:
print ("Incorrect. The actual answer is",maths)

if score >5:
print("Well done you scored",score,"out of 10")
else:
print("Unfortunately you only scored",score,"out of 10. Better luck next time")

class_number = input("Before your score is saved ,are you in class 1, 2 or 3? Press the matching number")
while class_number not in ("1","2","3"):
print("That is not a valid class, unfortunately your score cannot be saved, please try again")
class_number = input("Before your score is saved ,are you in class 1, 2 or 3? Press the matching number")
# I realised that previously if the user entered an invalid class they would have to start the quiz again from the beginning which is quite
else:
filename = (class_number + "txt")

with open(filename, 'a') as f:
f.write("\n" + str(name) + " scored " + str(score) +  " on difficulty level " + str(level_of_difficulty))

with open(filename, 'a') as f:
f = open(filename, "r")
lines = [line for line in f if line.strip()]
f.close()
lines.sort()

if get_bool_input("Do you wish to view previous results for your class"):
for line in lines:
print (line)
else:
sys.exit("Thanks for taking part in the quiz, your teacher should discuss your score with you later")

if status == "2":
class_number = input("Which classes scores would you like to see? Press 1 for class 1, 2 for class 2 or 3 for class 3")
if class_number not in (1,2,3):
sys.exit("That is not a valid class")
filename = (class_number + "txt")
with open(filename, 'a') as f:
f = open(filename, "r")
lines = [line for line in f if line.strip()]
f.close()
lines.sort()
for line in lines:
print (line)


### Helper functions to prompt

Your get_bool_input is not very friendly: it exits on invalid input. It would be better to let the user retry. For that, simply replace the sys.exit with print.

So it seems you need a lot of prompting, often for integer. So add another helper method:

def prompt_int(prompt=''):
while True:
val = input(prompt)
try:
return int(val)
except:
print("Not a valid number, please try again")


Or if you want to limit input to specific choices you can do something like:

def prompt_choice(prompt, choices):
while True:
val = input(prompt)
if val in choices:
return val
else:
print("Not a valid choice, please try again")


### Avoid duplicated code

You have some duplicated logic, in many places, for example:

if level_of_difficulty == 1:
number_1 = random.randrange(1, 10)
number_2 = random.randrange(1, 10)
else:
number_1 = random.randrange(1, 20)
number_2 = random.randrange(1, 20)


When you see repeated code like that, separate in your mind the parts that are the same and the parts that are different, and try to rewrite in a way that the same part is only written once, and replace the changing part with a variable. Like this:

if level_of_difficulty == 1:
max_number = 10
else:
max_number = 20
number_1 = random.randrange(1, max_number)
number_2 = random.randrange(1, max_number)


Apply this technique everywhere in your code.

### Working with files

What's going on here:

    with open(filename, 'a') as f:
f = open(filename, "r")
lines = [line for line in f if line.strip()]
f.close()
lines.sort()


Opening a file to append, then inside the with block open the same file for reading? That doesn't make any sense. With the nonsense removed:

with open(filename) as f:
lines = [line for line in f if line.strip()]
lines.sort()


### Poor naming

Many variable names don't describe their purpose well. For example "status" is hardly a good name for teacher-student choice. Perhaps "role" would be better.