Lab 4: Math wiz Due: 11:59 PM on 2020-03-11

FAQ

** Please note new due date – you have an automatic 24-hour extension **

Background

For this lab, you will implement a timed game where the user answers as many simple math questions as possible in a given time period.

Before you start working, make sure that you understand at a high-level how the game is supposed to be played. We will build up the final version of this program one piece at a time. As you build each piece, make sure you understand where it fits into the larger program and make sure it’s working correctly before moving on.

Right-click (Ctrl-click on a Mac) on the link below, select “Save as…”, and save the file for this assignment. Then open the saved file with Thonny.

Download the starter file.

Add your code in the places specified by the comments.

Guide

Generating random equations

The first thing you will need is the ability to generate a random equation. Our random equations will contain the operators +, -, and *. The following pseudocode describes one way to generate a random equation. Pseudocode is a way to describe an algorithm in detail without getting bogged down in language syntax (recall “syntax vs. semantics”). It is a compromise between English and code.

generate a random number between 1 and 10 to start the equation
for each operator that you want to add on:
    pick a random operator from +, -, * and add it to the equation
    pick a random number between 1 and 10 and add it to the equation

Using this approach, we can generate a random equation with as many operators as we would like.

Write a function called random_equation that has a single parameter, the number of operators to generate in the random equation, and returns a string representing a random math equation involving the numbers 1-10 and operators +, -, * (note that we don’t allow for 0 because it make multiplication too easy, and we don’t allow division because it produces floating point results that can be impossible to guess). Your function should work for zero operators (see example below). Here are a few example runs:

>>> random_equation(4)
'5 - 6 - 7 * 6 * 4'
>>> random_equation(1)
'3 * 7'
>>> random_equation(2)
'8 - 5 * 2'
>>> random_equation(7)
'8 + 7 + 4 - 7 - 2 * 3 * 2 * 4'
>>> random_equation(0)
'4'

Hints: You’ll likely need to use some sort of loop structure to get the repetition (i.e., for or while). Think about which one is more appropriate here. Also, notice that our equation is a string, so we’ll be building up a string similar to how we did in the last assignment by appending additional pieces.

Getting an answer

Now that you have an equation, you will need to have some way of repeatedly prompting the user for an answer until that answer is correct. Before writing any more code, read through this whole section since we give some hints/advice at the end.

There is a function named eval built into Python that evaluates any expression represented as a string and returns the value represented by that expression. We’re going to use this function to figure out what the answer is to our random equation. For example:

>>> eq = random_equation(4)
>>> eq
'7 * 5 - 2 - 4 * 8'
>>> x = eval(eq)
>>> x
1

Notice that the value that eval returns is an int, which we would expect. In fact, eval can be used to evaluate more complicated string expressions that include function calls, etc. Experiment with this if you’re curious!

Write a function called query_equation that takes as a parameter a string representing an equation (e.g., the return value from your random_equation function). This function should prompt the user with the equation and then wait for an answer from the user. If the user gets it wrong, then it will output a message to the user indicating this and then prompt the user again with the equation (eval will be useful in figuring out if the user’s answer is correct). The function should continue to prompt the user until they get the answer right. When the user does finally get it right, the function should print “Correct!”. The message the user gets if they answer incorrectly should depend on how close they are:

There are many ways you can tackle this function, but we suggest an incremental approach:

  1. Write the function without a loop so that it only queries the user once and then either prints out “Correct!” or “Keep trying.” depending on whether or not the user got it right. Remember that input returns a string, so if you want an integer you will need to convert the string to an integer.
  2. Add some sort of loop to repeatedly query the user when incorrect.
  3. Add in “Close. Try again.” that is printed when the user gets it wrong but is within 2 of the correct answer.

Here is a quick example with query_equation:

>>> eq = random_equation(2)
>>> eq
'5 + 9 + 7'
>>> query_equation(eq)
5 + 9 + 7 = 25
Keep trying.
5 + 9 + 7 = 22
Close. Try again.
5 + 9 + 7 = 20
Close. Try again.
5 + 9 + 7 = 21
Correct!

Playing the game

We now have all of the pieces we need to put together our final program. Write a function named play_game that has two parameters, the game duration in seconds and the number of operators. The function should use the time function in the time module to time the user. As long as the elapsed time hasn’t exceeded the specified game duration, you should present the user with a new random equation and then keep track of how many they get correct (you should be thinking about some kind of loop structure). When time runs out, you should print out how many the user got correct and how long the game was actually played for exactly as shown below (including punctuation).

10-9*7 = -53
Correct!
You got 1 correct in 10.424232006072998 seconds.

This function should NOT be a lot of code, but should utilize the two previous functions that you’ve written. The key responsibility of this function is timing and keeping track of how many the player has gotten correct. Recall from the prelab that we can measure how much time has elapsed by first recording a starting time and then measuring the difference between the current time returned by time() and the initial start time.

As with the previous function, we suggest an incremental approach. There are many ways you could tackle this, but one would be:

  1. Start by getting the timing loop working. For example, just get any input from the user (using input) and then print it out. Do this over and over again until the time has elapsed.
  2. Change your loop now to generate a random equation and then use that equation to query the user.
  3. Finally, add in the bookkeeping part where you keep track of how many the user got right and then at the end, print out the game summary.

A note about timing: Because we are doing the timing outside of the query_equation function, time will NOT expire until the user gets the answer right. That is, the actual time elapsed for the game will be longer than the specified duration (and could be quite a bit longer). This is OK and how we expect your implementation to work. That is, keep presenting new questions to the player as long as time has not “expired”. While there are mechanisms to cutoff the game the moment time expires, doing so is outside the scope of this course.

A note about testing: Your game report

You got 1 correct in 10.424232006072998 seconds.

must match this format exactly, including the same words. Keep in mind that while “correct” and “right” are synonyms in this context, Python doesn’t know that.

The finishing touches

When your play_game function is working, you can finish things up: Complete your program by adding functionality to the main function, which gets called from the if __name__ == "__main__" conditional that executes automatically when your program is run. You should ask the user if they want to play a game. If they say “yes”, then you should prompt the user to see how long they want to play for (in seconds) and then the game should start. If the user does not say “yes”, just give the user a nice goodbye message.

A short instance of the game would look like:

Do you want to play a game [yes/no]? yes
How long do you want to play for [seconds]? 10
10-9*7 = -53
Correct!
You got 1 correct in 10.424232006072998 seconds.

Specification

At a minimum your submission should have:

  1. A function named random_equation with one parameter, the number of operators, that returns a string with a random math equation with that number of operators (as described in the guide).
  2. A function name query_equation with one parameter, a string representing an equation (e.g., the return value from your random_equation function), which queries the user for the correct answer until they get it right. query_equation should not return anything.
  3. A function named play_game with two parameters, the game duration in seconds and the number of operators, which presents the user new equations to answer (via query_equation) until time elapses. play_game should print the number of correct answers and the actual time elapsed (not the specified duration) exactly as shown in the guide.
  4. A function named main that takes no parameters, which prompts the user and launches the game as described in the guide: asks the user if they want to play a game; if no, gives a goodbye message; if yes, asks how long, and then calls play_game sending the amount of time as a parameter.

Creativity Suggestions

When implementing your creativity additions, do not change the parameters to the functions. If you add additional information to the game summary do so on a different line outside of the play_game function, i.e., make sure to print the number of correct answers and the time elapsed from within play_game as shown in the guide, as the automated tests are looking for a line with that exact format. Do not add any additional questions (i.e. with input) to query_equation or play_game other than specified. Any other input should occur outside of those functions. For example, if you want to add an option for the user to play again, implement that loop in the main function not in play_game.

When you’re done

When you’re done you should have at least four functions (maybe more). Make sure that your program is properly commented:

In addition, make sure that you’ve used good coding style (including meaningful variable names, constants where relevant, vertical white space, etc.). There are numerous good candidates for constants in this lab including the range of numbers to include in the random equations, the allowed operators, etc.

Submit your program via Gradescope. Your program program file must be named lab4_math_wiz.py. You can submit multiple times, with only the most recent submission (before the due date) graded. Note that the tests performed by Gradescope are limited. Passing all of the visible tests does not guarantee that your submission correctly satisfies all of the requirements of the assignment.

Grading

Feature Points
random_equation  
Correctly formed equations 4
All 3 operators randomly 1
Number varies based on input 1
query_equation  
Print correct/incorrect correctly 2
Loops until correct 3
Close answers get different feedback 1
play_game  
Generates random equation and queries user 1
Loops until time expired 3
Prints out game summary 1
main  
Correctly handles user input for game starting 2
Gets game duration 1
   
Comments, style 3
Creativity points 2
Total 25

FAQ Excerpts

Why is Gradescope giving me errors?

Our testing script in Gradescope tests each individual function separately, so Gradescope may pick up some problems with code that seems to function fine when run as a program in Thonny.

Why is the actual time elapsed greater than the duration?

The example shows an actual time elapsed (10.4s) that is greater than the specified duration (10s). This is expected. We can only check how much time has elapsed when the player successfully answers a question and so time may have “expired” while they were in the process of answering an equation, i.e. they started before time “expired”, but did not finish until afterwards. (See also A note about timing in the lab write-up.)