14
\$\begingroup\$

I started my Python Journey almost a week ago from now. I had been following a Youtube tutorial earlier, but switched to Harvard's CS50P instead. I decided on making a little Quiz Game with my programming knowledge so far. I know this is a really inefficient way to make one, but I honestly don't know how I could have made it more efficient (I am still fascinated how people ONLY make functions and run a SINGLE main function to run the complete code). I am not yet aware of many important things in Python like Classes as well, hence I didn't use them. Can someone guide me if this code is:

  1. Good?
  2. How would you rate it in terms of efficiency?
  3. How is my progress so far (~8-10 days of learning)?
from time import sleep

score = 0

def fq():
    sleep(1)
    question(statement,options,correct)

def question(statement,options,correct):
    print(statement)
    sleep(2)
    answer = input(options).lower().strip()
    check(answer,correct)


def check(answer,correct):
    sleep(1)
    if answer == correct:
        print("Correct!")
        global score
        score += 1
    else:
        print("Incorrect!")

print("Welcome to the Quiz Game!")
choice = input("If you want to play, enter in 'yes', otherwise enter 'no': ").lower().strip()
while choice != "yes" and choice != "no":
    choice = input("Invlaid choice, if you want to play, enter in 'yes', otherwise enter 'no': ").lower().strip() 
if choice == "yes":
    print("Great, lets continue!")
    sleep(1)
else:
    print("We'll play some time later!")
    quit()
print("There will be 10 questions, Good Luck")
sleep(2)

statement = "Q1: Hitler party which came into power in 1933 is known as?"
options = "Enter A for Labour Party, B for Nazi Party, C for Ku-Klux-Clan, D for Democratic Party: "
correct = "b"
fq()

statement = "Q2: In which year of First World War Germany declared war on Russia and France?"
options = "Enter A for 1914, B for 1915, C for 1916, D for 1917: "
correct = "a"
fq()
statement = "Q3: Joule is the SI unit of?"
options = "Enter A for Temperature, B for Pressure, C for Energy, D for Current: "
correct = "c"
fq()

statement = "Q3: The famous wonder of the world is named, The Leaning Tower of _____"
options = "Enter A for Siena, B for Pisa, C for Rome, D for Milan: "
correct = "b"
fq()

statement = "Q4: Which country is known as the 'Land of Rising Sun'?"
options = "Enter A for Japan, B for New Zealand, C for China, D for USA: "
correct = "a"
fq()

statement = "Q5: Which continent has the highest number of countries?"
options = "Enter A for Asia, B for Europe, C for South America, D for Africa: "
correct = "d"
fq()

statement = "Q6: How many rings appear on the Olympics Flag?"
options = "Enter A for 1, B for 3, C for 5, D for 7: "
correct = "c"
fq()

statement = "Q7: Which Planet is the 3rd closest to the Sun?"
options = "Enter A for Earth, B for Mars, C for Jupiter, D for Venus: "
correct = "a"
fq()

statement = "What is the largest planet in out Solar System?"
options = "Enter A for Uranus, B for Saturn, C for Jupiter, D for Mercury: "
correct = "c"
fq()

statement = "What planet is nearest to The Earth?"
options = "Enter A for Venus, B for Mercury, C for Mars, D for Pluto: "
correct = "b"
fq()

statement = "How many sides does a Heptagon have?"
options = "Enter A for 5, B for 6, C for 7, D for 8: "
correct = "c"
fq()

print("You have successfully answered all the questions.")
sleep(2)
print("Lets see what your score is!")
sleep(2)
if score >= 5:
    print(f"Your score is {score} out of 10. Well done!")
else:
    print(f"Your score is {score} out of 10. You could do better!")
\$\endgroup\$
1
  • 3
    \$\begingroup\$ And any program that gets finished and does what it should is a better learning exercise compared to an over engineered piece of art that never gets finished because there are always parts to make better. Keep up the learning spirit \$\endgroup\$
    – Zibelas
    Jun 21 at 7:55

3 Answers 3

16
\$\begingroup\$

Welcome to your journey in programming and in Python, I think that these 2 main concepts will help you improve to the next level:

Functions as computations with an output rather than control flow jumps

Your functions:

def fq():
    sleep(1)
    question(statement,options,correct)

def question(statement,options,correct):
    print(statement)
    sleep(2)
    answer = input(options).lower().strip()
    check(answer,correct)

do something and then call another function. This is conceptually similar to a goto statement to jump the execution flow to another point. Rather you could rewrite the question function as:

def question(statement,options,correct):
    print(statement)
    sleep(2)
    answer = input(options).lower().strip()
    return answer

As such it is easier to think about and test this function in isolation.

Data structures

You do not need classes to organize your data, you organize your data as a list of (statement, options, correct) triples:

game_show_data = [("Q1: Hitler party which came into power in 1933 is known as?",
"Enter A for Labour Party, B for Nazi Party, C for Ku-Klux-Clan, D for Democratic Party: ",
"b"), ( .... ) ] # add the other data like this.

Afterwards you can use the nice Python loops to handle the game show logic:

for statement, options, correct in game_show_data:
    handle_game_show_question(statement, options, correct)

As a next step you could build a Gameshow class, including this logic about handling data structures and maybe also file reading and writing (save highscore) and more, have fun!

Addendum about efficency

In a user-interface-centric program like this efficiency does not matter at all as the majority of the time will be spent waiting for the user. Concerns about efficiency should be taken into account in mathematical simulation / graphic rendering not user interface design.

\$\endgroup\$
12
\$\begingroup\$

How is my progress so far (~8-10 days of learning)?

Progress is non-linear, and technical learning is a long, long road (years-scale, not days-scale). So it's impossible to assess like this, but "keep it up".

How would you rate it in terms of efficiency?

That's not the right question to ask: run-time or memory efficiency are irrelevant for a program like this. Instead work on maintainability, legibility and correctness.

Otherwise:

sleep() in a console program is a pet peeve of mine and offers nothing to the user. I would drop these.

Avoid globals - score certainly doesn't need to be one.

Consider using a very simple NamedTuple or dataclass to represent each question uniformly.

Do not bake in your Qx: to your prompt, nor your A for, B for. These should all be generated. Among other things this will fix an error of a repeated Q3 and missing Q: prefixes from your last few questions.

Do not hard-code 10; you in fact have 11 questions.

Typo in Invlaid.

Consider showing the correct answer after the user has answered incorrectly.

Not a review of code, but "What planet is nearest to The Earth?" is a meaningless question to ask since the distance always changes depending on true anomaly at the time. You could instead ask Which planet's orbit is nearest to that of Earth?

Use your Google skills (or feel free to ask in the comments); techniques I have demonstrated in the code below that could benefit you:

  • ask_all being an iterator function yielding 1 for every increase in score to be summed
  • PEP484 type hints
  • immutable classes
  • a __main__ guard

Suggested

from string import ascii_uppercase
from typing import NamedTuple, Iterator


class Question(NamedTuple):
    statement: str
    options: tuple[str, ...]
    correct: str

    @property
    def prompt(self) -> str:
        return (
            'Enter '
            + ', '.join(
                f'{letter} for {option}'
                for letter, option in zip(ascii_uppercase, self.options)
            )
            + ': '
        )

    def check(self, answer: str) -> bool:
        return answer.lower() == self.correct

    
QUESTIONS = (
    Question(
        statement='Hitler party which came into power in 1933 is known as?',
        options=('Labour Party', 'Nazi Party', 'Ku-Klux-Clan', 'Democratic Party'),
        correct='b',
    ),
    Question(
        statement='In which year of First World War Germany declared war on Russia and France?',
        options=('1914', '1915', '1916', '1917'),
        correct='a',
    ),
    Question(
        statement='Joule is the SI unit of?',
        options=('Temperature', 'Pressure', 'Energy', 'Current'),
        correct='c',
    ),
    Question(
        statement='The famous wonder of the world is named, The Leaning Tower of _____',
        options=('Siena', 'Pisa', 'Rome', 'Milan'),
        correct='b',
    ),
    Question(
        statement='Which country is known as the "Land of Rising Sun"?',
        options=('Japan', 'New Zealand', 'China', 'USA'),
        correct='a',
    ),
    Question(
        statement='Which continent has the highest number of countries?',
        options=('Asia', 'Europe', 'South America', 'Africa'),
        correct='d',
    ),
    Question(
        statement='How many rings appear on the Olympics Flag?',
        options=('1', '3', '5', '7'),
        correct='c',
    ),
    Question(
        statement='Which Planet is the 3rd closest to the Sun?',
        options=('Earth', 'Mars', 'Jupiter', 'Venus'),
        correct='a',
    ),
    Question(
        statement='What is the largest planet in our Solar System?',
        options=('Uranus', 'Saturn', 'Jupiter', 'Mercury'),
        correct='c',
    ),
    Question(
        statement='What planet is nearest to The Earth?',
        options=('Venus', 'Mercury', 'Mars', 'Pluto'),
        correct='b',
    ),
    Question(
        statement='How many sides does a Heptagon have?',
        options=('5', '6', '7', '8'),
        correct='c',
    ),
)


def ask_all() -> Iterator[int]:
    for i, question in enumerate(QUESTIONS, 1):
        print(f'\nQ{i}: {question.statement}')
        answer = input(question.prompt)
        if question.check(answer):
            print('Correct!')
            yield 1
        else:
            print(f'Incorrect! The answer was {question.correct.upper()}.')
            yield 0


def main() -> None:
    n = len(QUESTIONS)
    print(
        'Welcome to the Quiz Game!'
        f'\nThere will be {n} questions. Good luck!'
    )

    score = sum(ask_all())

    print(
        '\nYou have successfully answered all the questions.'
        "\nLet's see what your score is!"
        f'\nYour score is {score} out of {n}.',
        end=' '
    )

    if score >= n//2:
        print(f'Well done!')
    else:
        print(f'You could do better!')


if __name__ == '__main__':
    main()

Output

Welcome to the Quiz Game!
There will be 11 questions. Good luck!

Q1: Hitler party which came into power in 1933 is known as?
Enter A for Labour Party, B for Nazi Party, C for Ku-Klux-Clan, D for Democratic Party: c
Incorrect! The answer was B.

Q2: In which year of First World War Germany declared war on Russia and France?
Enter A for 1914, B for 1915, C for 1916, D for 1917: c
Incorrect! The answer was A.

Q3: Joule is the SI unit of?
Enter A for Temperature, B for Pressure, C for Energy, D for Current: c
Correct!

Q4: The famous wonder of the world is named, The Leaning Tower of _____
Enter A for Siena, B for Pisa, C for Rome, D for Milan: c
Incorrect! The answer was B.

Q5: Which country is known as the "Land of Rising Sun"?
Enter A for Japan, B for New Zealand, C for China, D for USA: c
Incorrect! The answer was A.

Q6: Which continent has the highest number of countries?
Enter A for Asia, B for Europe, C for South America, D for Africa: c
Incorrect! The answer was D.

Q7: How many rings appear on the Olympics Flag?
Enter A for 1, B for 3, C for 5, D for 7: c
Correct!

Q8: Which Planet is the 3rd closest to the Sun?
Enter A for Earth, B for Mars, C for Jupiter, D for Venus: c
Incorrect! The answer was A.

Q9: What is the largest planet in our Solar System?
Enter A for Uranus, B for Saturn, C for Jupiter, D for Mercury: c
Correct!

Q10: What planet is nearest to The Earth?
Enter A for Venus, B for Mercury, C for Mars, D for Pluto: c
Incorrect! The answer was B.

Q11: How many sides does a Heptagon have?
Enter A for 5, B for 6, C for 7, D for 8: c
Correct!

You have successfully answered all the questions.
Let's see what your score is!
Your score is 4 out of 11. You could do better!
\$\endgroup\$
2
  • 3
    \$\begingroup\$ Very good answer, maybe a few words (or links to resources) on iterators (such as the ask_all function) could improve the answer and make it more approachable to beginners. \$\endgroup\$
    – Caridorc
    Jun 19 at 22:34
  • 1
    \$\begingroup\$ Any blocking implementation of sleep() is bad practice in 99.99% of situations IMO. And not just in Python! Deliberately blocking the execution of a program is not a feature, it's a design flaw. Your car doesn't completely shut down every time it needs you to provide input, so why would you block a program for user input? \$\endgroup\$ Jun 21 at 11:03
3
\$\begingroup\$

8-10 days aren't much and it gets the job done without any obvious errors so yeah that's ok.

The efficiency of the program is mostly a concern if you're doing stuff that is computationally heavy and where the operations itself take in the order of magnitude of seconds or longer. Your operations are so fast that you added sleep functions, presumably to not pressure the user with an instant response. So that's not a primary concern here. The slowest part in the program is probably the user entering their answer.

In terms of what you can improve. Well if you write the same thing over and over again like this:

statement = "Q1: Hitler party which came into power in 1933 is known as?"
options = "Enter A for Labour Party, B for Nazi Party, C for Ku-Klux-Clan, D for Democratic Party: "
correct = "b"
fq()

then there's probably a more elegant way using a loop or a function. Now as all questions are unique you'll inevitably have a lot of writing. But you can already identify which parts are unique and which aren't. So for example:

"Enter A for Labour Party , B for Nazi Party , C for Ku-Klux-Clan , D for Democratic Party**:** "

the highlighted elements occur in each question the rest is unique. So you can already use those as template:

"Enter A for",AnswerA,", B for", AnswerB, ", C for", AnswerC, ", D for", AnswerD: "

and fill in the rest by variables. If you're fancy you can already use the python format or f-string for that:

f"Enter A for {AnswerA}, B for {AnswerB}, C for {AnswerC}, D for {AnswerD:} "

just an f before the string and variables in {} are integrated into the text. Same thing for The Question where you could use the Q1: As Template or even just the f"Q{questionNumber}:". And then you could already wrap that in a function using the question and the 4 answer options as parameters.

Which would get around this nasty construct:

 def fq():
    sleep(1)
    question(statement,options,correct)

Which relies on manipulation of global variables. First of all your function names should be descriptive so that you know from the name alone what it's supposed to be doing. If they are longer you should also add a doc string explaining it further. But back to the global variable problem. The thing is you can think of functions as smaller programs within a program and ideally they are self-sufficient and encapsulated. This makes them easier to understand and to debug. Like think of what it would mean if anywhere in your 13 questions you made one typo idk writing optiosn instead of option. Then your code would not complain as python doesn't care how you name your variables but your function would still have a value for that variable. So it would display a wrong set of answers. And those kinds of errors that don't tell you that something is wrong, but simply not work as expected are notoriously hard to find and fix. So with very few exceptions functions should only use variables that are defined within the function or passed to the function by parameters. So if anything goes wrong you know it's happening within the function not somewhere else in your code. Your project is already 100 lines of code so the bigger that gets the harder it is to keep track of what's going on.

As others have pointed out when you learn about data structures and objects you'll probably find better ways to handle the questions. Also if you check out the file input/output you could keep your code cleaner by separating your data from your code. So you'd write the questions in a file, read the file and use that to make question objects and your program would only handle those. That would cut the lines of code in your file by more than 50% and you could extend the list of questions or swap it out for another without having to mess with your python code.

\$\endgroup\$

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.