You've written functions. Many functions. Now let's put your skill as a function-writer to work.
How so? We'll write a program that consists of many functions. Moreover, this program will be self-contained; it will ask for input from the user, process that input, ask again if necessary, etc. Thus all we need to do is run it and then it takes over.
Before we jump in, I must acquaint you with some bits of Python that we haven't yet seen.
First we need to know how to solicit user input and store that input away in a variable.
Try this two-liner in your code editor.
user_input = input("Tell me about yourself. ")
print("You said:", user_input)
Here's my run:
Tell me about yourself. I'm not shy, just a little introverted.
You said: I'm not shy, just a little introverted.
>>>
We see here Python's input function. The argument is the string that will be printed to the screen when input is solicited. When executed, the user will see that string (for me in the console, for you perhaps in a pop-up window) and will be given the opportunity to type.
The output of input is the string typed by the user. In the code snippet above, this input string was stored in the variable user_input.
Note this carefully. The value returned by in the input function is always a string. Did the user type in "True"? That's a string, not a Boolean. Did she type in "2"? That's a string, not an int. This means that if we wish to input an integer, we'll have to convert from string type to int type. Let's discuss how to do that.
So, let's say the user typed in "2" when asked to input a value. That's a string, as the console shows us:
>>> answer = input("Pick a number: ")
Pick a number: 2
>>> type(answer)
<class 'str'>
That's a problem of course. We can't do with strings what we can do with numbers. (I continue on with the console session begun above.)
>>> 3 ** answer
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'str'
How do we turn the string "2" into the int 2? The type conversion function int.
>>> an_int = int(answer)
>>> an_int
2
>>> type(an_int)
<class 'int'>
So the lesson is clear. If you need the user to input an int, you'll have to take the output of the input function and run it through the int function.
Alright, let's say we want the user to input an integer but the user types "armadillo". What happens if we try to convert that to int type? Well, let's try.
>>> answer = input("Pick a number: ")
Pick a number: armadillo
>>> int(answer)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
int("armadillo")
ValueError: invalid literal for int() with base 10: 'armadillo'
Python threw up its hands. It can't convert the string "armadillo" to an int. This of course would be fatal if it were a line of code in a program. The program would crash, and that my dear students is the programmers fault. You might be temped to blame the silly user, but the hard truth is that you must always account for silly user input.
We'd like to catch inputs that can't become ints, inform the user of their error and ask for another input. How? try and except.
user_input = input("Please input an integer: ")
try:
input_int = int(user_input)
except:
print("I beg your pardon, but that is not an integer.")
You see here two new keywords: try and except. We use them to handle the error that would be generated by an attempt to convert "armadillo" to an int. We handle and don't crash. How so? Here's the logic:
Try to execute the block of code under try. If no line of code in that block throws an error, execute it all and skip the except block.
If any line in the try block does throw an error, skip the rest of that block, go to the except block and execute it.
So we'll try to convert user_input to int type. If that doesn't throw an error, input_int will now hold an int. If it does throw an error, input_int will be undefined and the user will see the message "I beg your pardon, but that cannot become an integer."
This is good - we won't crash if the user isn't cooperative. But we'd like to make the user keep at it until they get it right. Hmm ... we want to keep at it until a certain condition obtains. We want to ... what's that word again? I've got it! Loop! We want to loop until we get an actual int! That means while of course. Look at this pretty little snippet.
while True:
user_input = input("Please input an integer: ")
try:
input_int = int(user_input)
except:
print("I beg your pardon, but that is not an integer.")
print("Please do try again.")
else:
print("Bravo! You've done it!")
break # end the while loop
We've seen all of this except for the else at the end. The logic of this else is that if all the code in the try block is executed error free, then we skip the except block and continue on to the else block. Why have an else block? Why not just place all the else block code in the try block? My answer is a question: What if the code in the else block contained an unexpected error, like perhaps a simple syntax error? What if we had misspelled print as prnt there? Well, the try block catches any exception and sends us to the except block. So that syntax error that we should really want to raise an exception would not; and that makes it more difficult than necessary to debug. The lesson here is this: in the try block, place only those lines of code which you think might crash.
In the program I want you to write (description below), you'll need to clear the pause execution, clear the console, get the current time and generate random integers. I'll explain how to do each of these in turn.
To pause execution, we import the time module and then call its sleep method. Like this:
import time
print("Hello")
time.sleep(2) # Pause for two seconds
print("world!")
To clear the console, we have two options. The first is easier, the second a bit harder. The first is simply to simply execute this line of code:
print('\033c')
A second is to import the os module and then do os.system('clear'). Like this:
import os
os.system('clear')
Note that this is for a Linux OS. If you're on Windows, it should be os.system('cls') instead.
To time a process, we grab the time at the start and at the time at the end, and then we subtract the two. How to get the current time? time.time() after we import time. (Time began at midnight on 1/1/1970. I know, I know. The world looks older. It's had a rough few decades.)
import time
t_init = time.time()
time.sleep(3) # pause for 3 seconds
t_final = time.time()
print(t_final - t_init)
To generate random integers, we need the random module's randint function. random.randint(a, b) generates a random integer between a and b inclusive.
>>> import random
>>> random.randint(1, 6)
5
>>> random.randint(1, 6)
4
>>> random.randint(1, 6)
2
>>> random.randint(1, 6)
1
You're now ready to begin the project. Write a number guess game. Details:
The game should begin when I hit the run button. I should not have to call a function in the console.
Once the game begins, ask the user to input the range for the integer they're to guess. This will be two integers - the lower bound and the upper bound.
If the user doesn't input a number, tell her and make her choose again.
If the user's lower bound isn't less than her upper bound, tell her and make her choose again.
Once valid lower and upper limits are input, clear the screen.
Inform the user of the lower and upper bounds.
Next ask the user to guess the number.
If she's correct, congratulate her and end the game.
If she's not correct, tell her whether she was too high or too low and then have her guess again.
Before each guess, inform the user of how many guesses she's made. When done, assess her performance. Sarcasm is welcome.
Keep track of the amount of time the user took to play. Print that when the game is done. Round the time taken to the nearest hundreth. (Don't know how? Do a Google search for "Python round function".)
When the game is done, ask the user if she wants to play again. If she inputs "Yes", play again. If she inputs anything else, end the program and say "Goodbye!".
Let me emphasize that when I test your code, I WILL try silly inputs. Like "dog" when I'm asked for a number; and an upper bound that isn't greater than the lower bound. Your code should handle these gracefully. That means no crashes!
Remember who were are! We're function writers! All of your code (except for a single line I'll describe below) should be inside some function or other.
Remember too that we keep our functions short, and to do that we make them all single-task. Why do we do this! Because we don't like pain! Long, multi-task functions are hard to get right.
Once a function is written, test it. With all possible types of input. Does your function ask for an integer and then return it? Well, try it with the input "armadillo" (or something else as silly). If you function crashes or otherwise misbehaves, fix it! Your functions should be crash-proof.
Some of your functions will of course call others. This is utterly typical. In programs of any complexity, user-created functions call other user-created functions (which often call other user-created functions, which often call other user-created functions, etc.).
What functions should your write? You'll have to decide for yourself, but here are some suggestions:
A function that asks the user to input an integer and returns the integer input. It should take as argument the message to display when it asks for input. Moreover it should be robust; it should not crash if the input cannot be made into an int but should instead ask that the user try again.
A function that asks for a pair of integers and accepts the user input only if both inputs are indeed integers and the first is less than the second. This second function will call the input function described above.
A function called one_game that (surprise, surprise) plays a game. Just one.
One last function is mandatory. Its name is game_loop. It will call one_game and then, when the game is over, asks the user whether she wants to play again.
The last line of code in your project will be game_loop(). This means that, when the run button is hit, the game will begin.
Below are suggestions about how you might structure one_game and game_loop.
def one_game():
# set up a game: get lower and upper bounds from the user, generate the secret number,
# initialize a time taken variable and a number of guesses variable
while True:
# get guess from player
# if player guesses correctly, break out of while loop
# if she doesn't guess correctly, increment the number of guesses made
# congratulate the user
# display the total time taken and the number of guesses made
def game_loop():
while True:
one_game()
# get player answer to "Play again?"
# if answer isn't yes, break out of while loop
game_loop() # this will be your last line of code
First, I expect that after I hit the run button, the game should begin. I should not have to call a function in the console.
Second, you code must not crash; and I will try to crash it.
I think it'll be helpful to see a game actually played. This is a copy-paste from my console.
Input lower bound: armadillo
That can't be converted to int type. Try again.
Input lower bound: 10
Input upper bound: 1
Lower bound must be less than upper bound. Try again.
Input lower bound: 1
Input upper bound: 10
Lower bound: 1
Upper bound: 10
Number of guesses so far: 0
What's your guess: 5
That's too high.
Number of guesses so far: 1
What's your guess: 1
That's too low.
Number of guesses so far: 2
What's your guess: 3
Got it! Congrats!
Elapsed time: 13.85 seconds
Number of guesses: 3
Play again? Y or y for yes, anything else for no: Y
Input lower bound: 1
Input upper bound: 10
Lower bound: 1
Upper bound: 10
Number of guesses so far: 0
What's your guess: 5
That's too low.
Number of guesses so far: 1
What's your guess: 10
That's too high.
Number of guesses so far: 2
What's your guess: 7
That's too high.
Number of guesses so far: 3
What's your guess: 6
Got it! Congrats!
Elapsed time: 11.50 seconds
Number of guesses: 4
Play again? Y or y for yes, anything else for no: n