Validating input
When you read input from a person running your program, you should be prepared for them to type something unexpected!
For example, let’s imagine you work for a doctor and you want to write a simple program to ask a person how much pain they are in, using a standard 10-point scale, where 10 is highest:
def get_number():
while True:
response = int(input('How much pain are you in? Enter a number from 1 to 10: '))
if response > 10:
print('That was too high, try again:')
continue
return response
print(get_number())
This code will check to be sure the number they enter is not too high. But what if they enter one of the following?
- zero
- a negative number
- a fraction
- a decimal
- a letter
Let’s write a better version:
def get_number():
while True:
response = input('How much pain are you in? Enter a number from 1 to 10: ')
# check if it is a digit
if not response.isdigit():
print('That is not a number, try again')
continue
# now we can convert to an integer
response = int(response)
# check if too high
if response > 10:
print('That was too high, try again')
continue
# check if too low
if response < 1:
print('That was too low, try again')
continue
return response
print(get_number())
Number Game
This program plays a game with any number of players to guess a number. Each player gets one chance. The player that is closest wins. Ties go to the player that guessed earlier.
The computer randomly picks a number between a lower bound and an upper bound. Each player is prompted to guess a number. A guess is invalid if:
- it isn’t a positive integer
- it isn’t between the lower and upper bounds
- another player has already guessed that number
If a player submits an invalid guess, they should be informed why their guess was invalid and they must guess again.
The function to play the game takes these arguments:
play_number_game(lower, upper, num_players)
A flow chart
Here is a flow chart illustrating how to write this game:
Code for the main game loop
Let’s write the code for the main game loop:
- pick a random number
- loop through all the players
- get a guess from each player
- add to a list of guesses
- determine winner
import sys
import random
def play_number_game(lower, upper, num_players):
guesses = []
number = random.randint(lower, upper)
for player in range(num_players):
guess = get_a_guess(player, lower, upper, guesses)
guesses.append(guess)
print(f"The number is {number}")
winner = find_a_winner(number, guesses)
print(f'Player {winner + 1} wins!')
if __name__ == '__main__':
play_number_game(int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]))
Notice how we have purposely left two functions undefined — get_a_guess()
and
find_a_winner()
. This is a good way to start writing the code.
Getting a guess
The next step is to write the get_a_guess()
function.
def get_a_guess(player):
guess = input(f'Player {player + 1} guess: ')
return guess
OK, that will sort of work. :-) But it doesn’t do any of the input validation.
And to do that, this function needs the lower and upper bounds as a parameter.
And if the user makes a mistake, we need to keep letting them input a number
until they provide a correct guess. That means we need a while True
.
def get_a_guess(player, lower, upper):
while True:
guess = input(f'Player {player + 1} guess: ')
guess = int(guess)
if guess < lower or guess > upper:
print(f"The number must be between {lower} and {upper}.")
continue
return guess
Notice how we can return out of the while loop as soon as we get a valid guess.
This is a good start, but what about checking if another player has already made the same guess? We need to add yet another parameter to the function — all the guesses made previously.
def get_a_guess(player, lower, upper, previous_guesses):
while True:
guess = input(f'Player {player + 1} guess: ')
guess = int(guess)
if guess < lower or guess > upper:
print(f"The number must be between {lower} and {upper}.")
continue
if guess in previous_guesses:
print("That number has already been guessed. Guess another.")
continue
return guess
Trying what we have
OK, that looks pretty good! Let’s be sure to call it correctly and let’s comment out the other function we haven’t written yet.
import sys
import random
def play_number_game(lower, upper, num_players):
guesses = []
number = random.randint(lower, upper)
for player in range(num_players):
guess = get_a_guess(player, lower, upper, guesses)
guesses.append(guess)
print(f"The number is {number}")
# winner = find_a_winner(number, guesses)
# print(f'Player {winner + 1} wins!')
if __name__ == '__main__':
play_number_game(int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]))
If we run this:
% python number_game.py 1 10 2
Player 1 guess: 100
The number must be between 1 and 10.
Player 1 guess: 3
Player 2 guess: 3
That number has already been guessed. Guess another.
Player 2 guess: 7
The number is 9
We can see the game appears to be working, except for picking a winner.
Determining a winner
Now we need to write find_a_winner()
. We are given the correct number and all
of the guesses. We could write a flow chart for this piece as well. Or we could
write an english description:
- keep track of best distance from guess to correct number
- keep track of the closest player
- loop through all the guesses
- compute how far this guess is from the answer
- if this guess is closer than the closet one so far
- update the best distance and the closest player return closest player
def find_a_winner(number, guesses):
best_distance = None
closest_player = None
for player in range(len(guesses)):
# find the distance between this player's guess and the true number
distance = abs(guesses[player] - number)
# check if we have found a closest yet
if best_distance == None:
best_distance = distance
closest_player = player
continue
# check if we have a better distance
if distance < best_distance:
best_distance = distance
closest_player = player
return closest_player
Putting it together
You can add the `find_a_winner() function, uncomment those two lines in the main loop, and see how this works. You should be able to play the complete game.