Trivia Game¶
Last Week!
This is our last week for Coding Club!
You should keep playing at home. See Installing Python at Home
This project will not be completed today!
Learning to code requires you to try and keep trying until you understand
There are lots of other ways to learn to code. See Useful Links for Learning
Our Trivia Game works like so:
We load some trivia questions
We ask the user each question in turn
Each level has an increasing point reward
If the user gets a question wrong, their points are halved
If the user gets 3 questions wrong, they lose everything
If the user decides to quit (enter no answer), they exit with their current score
What it Looks Like¶
This is what running the game should look like:
Level 1 for 1000pts Score: 0pts Errors: 0/3
What is RAM?
1) Memory
2) Female Sheep
3) Baby Sheep
4) Forgotten Sheep
Your answer (Enter to Cancel)? 2
WRONG! 1 Errors
Level 1 for 1000pts Score: 0pts Errors: 1/3
What is RAM?
1) Memory
2) Female Sheep
3) Baby Sheep
4) Forgotten Sheep
Your answer (Enter to Cancel)? 3
WRONG! 2 Errors
Level 1 for 1000pts Score: 0pts Errors: 2/3
What is RAM?
1) Memory
2) Female Sheep
3) Baby Sheep
4) Forgotten Sheep
Your answer (Enter to Cancel)? 4
WRONG! Sorry, you've lost everything
Please try again!
Press <enter> to leave >
Reading the Game¶
You can download the game and the questions here:
Background¶
we need to be able to Read a File to load our questions
we will use Loops to load the questions from the file
we will store those questions and answers (in Lists)
we want to pull out particular elements in the lists (via List Indexing)
the first field in each line is a question
the second field is the answer
the rest of the line is wrong answers
we want to loop over the questions (with for Loops)
we need to be able to define Functions to make the code easier to understand
Overall Game Loop¶
Our overall game looks like this:
def run_game():
"""Run the Trivia Game until the user exits, wins or loses"""
errors = 0
score = 0
list_of_questions = load_questions()
for level_number,level in enumerate(list_of_questions):
errors,score = run_level( level_number, level, errors, score )
# errors == -1 -> user quit
# errors == 3 -> user failed
if errors >= 3 or errors < 0:
break
if score:
print("Your score was {:,}".format(score, ))
else:
print("Please try again!")
raw_input("Press <enter> to leave > ")
We are going to track:
score (points)
errors (to see if the user has failed)
we are using the function form of
print()
here, just to change things upif you look at the top of the module you will see a weird import statement where we imported the print function
the current level (and level number)
we’ll do this by iterating with a for loop (see Loops)
we use the
enumerate()
function to track our current level number
But to iterate over the questions, we have to have them loaded first.
Loading the Questions¶
This is what the questions.csv file looks like:
What is RAM?|Memory|Female Sheep|Baby Sheep|Forgotten Sheep
CSV files are a common way to represent a simple spreadsheet-like grid of columns and rows. There’s a module that handles all of the details of reading this format for us:
def load_questions(filename=DEFAULT_QUESTIONS):
"""Load our questions from the filename
Format of the file:
question|answer|bad_answer|bad_answer...
returns [
[ 'question', 'answer', 'bad_answer',... ],
[ 'question', 'answer', 'bad_answer',... ],
...
]
"""
import csv
questions = []
for record in csv.reader(open(filename), delimiter='|'):
if len(record) > 2: # skip empty lines
questions.append(record)
return questions
The only bit of extra logic is to reject short lines (the if statement that checks len( record )).
Running a Level¶
def run_level( level_number, level, errors, score ):
"""Run a single level until user gets it correct, fails, or quits
returns errors,score
returns errors == -1 if the user quits
"""
question,correct,answers = level[0],level[1],level[1:]
random.shuffle(answers)
correct_answer = False
while not correct_answer:
display_status(level_number, score, errors)
display_questions( question,answers )
response = get_response( )
if response is None:
# User has chosen to leave with current score
print("Sorry to see you go!")
return -1,score
try:
chosen = answers[response]
except IndexError:
print("Need to choose one of our options, please")
else:
if chosen == correct:
print("Correct!\n")
score += reward(level_number)
correct_answer = True
else:
errors += 1
score = score//2
if errors >= 3:
print("WRONG! Sorry, you've lost everything\n")
score = 0
break
else:
print("WRONG! %s Errors\n"%(errors,))
return errors,score
Things to investigate:
List Indexing (level[0], level[1], level[1:], answers[response])
random.shuffle()
so that the answer isn’t always 1score // 2 (divide without producing a fractional number)
Randomizing the Order¶
We both need to keep track of what answer is correct and randomize the list.
We do that by tracking the correct answer with:
correct = level[1]
When the user chooses a number (index) we check what answers[chosen] is and compare that to our saved chosen variable
We catch any errors where the user has chosen a number that’s not in the answer set (such as -112) and ask them to behave
Displaying Status/Questions¶
We are going to use the str
class’ .format method to format our
game status and questions:
def display_status( level_number, score, errors ):
"""Print out status report for a given level and current winnnings
Uses string formatting to produce a nicely-formatted display
"""
print("Level {0: 2d} for {1: 6d}pts Score: {2: 6d}pts Errors: {3}/3".format(
level_number+1, # note: normal people think in 1-index
reward(level_number), # calculate it
score, # current value we are tracking
errors,
))
That’s one big call, the {0: 2d} format inside the string reads as:
take the first (0-th index) value
format it with 2 spaces
as a decimal (base-10 number)
While the {3} format reads as:
take the fourth (3-rd index) value
format it “naturally”
We do the same thing for the questions, but we indent those lines
(put spaces in front of them) so it’s easier for the user to read them.
We’re going to use the enumerate()
function again to keep
track of which number we are currently showing.
def display_questions( question,answers ):
"""Display the question and the answers
* displays the answers with 1-indexed labels
return None
"""
print(question)
for i,answer in enumerate(answers):
print(' {0}) {1}'.format(i+1, answers[i]))
Note
Most people tend to think in 1-index numbers, so we add 1 to the
value we’re getting from enumerate()
.
Getting The User’s Choice¶
We’ll use
raw_input()
to get the user’s selectionWe want to allow the user to quit, so if they enter nothing (just hit enter) we return a None
We don’t want the user hitting a wrong key to make them exit, so we catch any errors trying to make the number an integer and allow them to retry
def get_response():
"""Ask the user to make a choice
returns 0-indexed result or None if the user enters nothing
"""
while True:
try:
content = raw_input("Your answer (Enter to Cancel)? ")
if not content:
return None
return int(content) - 1
except ValueError:
print("Didn't recognize a number in that: {0!r}".format(content))
pass
Calculating the Reward¶
We use a basic point amount of 1000 and then double it for each level
def reward( level_number ):
"""Reward should double for each level starting at 1000"""
return 2**level_number * 1000
Run the Game when the Script is Run¶
if __name__ == "__main__":
run_game()