We have seen this example previously. There I included a lot of loops to handle user input and the main logic… Now it is time to refactor the application to use functions. This should make the code readable and we could re-use parts later.
One of those re-usable parts is the number reading. We have two points where we want the user to enter a number and have written slightly the same code. This is a very good place to start refactoring and extract this into one function.
So let’s extract this into a function. The only difference is the message we print to ask the user for a number. This can be handled as a parameter of the function because this is the only variable part. So let’s see the definition of the function:
def ask_user_for_number(message_text):
while True:
try:
return int(input(message_text))
except ValueError:
print("This was not a number!")
continue
As you can see this is a very straightforward approach where we ask the user for input and return it if it is a number.
Naturally we cannot handle the verification of the number to be 100 or 1000 in this method so we have to modify out code block there too.
At the end of this simple modification we have the application with following code (and it has the same functionality):
__author__ = 'GHajba'
import random
def ask_user_for_number(message_text):
while True:
try:
return int(input(message_text))
except ValueError:
print("This was not a number!")
continue
return number
while True:
max_number = 0
while max_number != 100 and max_number != 1000:
max_number = ask_user_for_number('Should the secret number between 1 and 100 or 1 and 1000? ')
if max_number == 100:
guess_count = 7
else:
guess_count = 10
print('You have chosen {}, you will have {} guesses to find the secret number.'.format(max_number, guess_count))
secret_number = random.randint(1, max_number)
print('I have chosen the secret number...')
guesses = 0
while guess_count - guesses:
guesses += 1
guessed = ask_user_for_number("What's your guess? ")
if guessed == secret_number:
print('Congrats, you have Won!')
break
elif guessed > secret_number:
print('The secret number is lower...')
else:
print('The secret number is higher...')
else:
print("Sorry, you lose.")
print("The secret number was ", secret_number)
answer = ''
while answer.lower() not in ['yes', 'no', 'y', 'n']:
answer = input("Do you want to play another round? (yes / no) ")
if 'no' == answer or 'n' == answer:
break
This looks like a very nice code without duplication and it is very lean. However if you know something about testing you might say that this code has a very big “main” block which is hard to test.
I have to agree. Unit testing (although I will write about it in a later chapter) would be bothersome here. The solution would be to split this big main loop into smaller functions which can be tested on their own.
For example evaluating if the user has won or lost. For this we could write a function which takes the secret number, the guesses, the guess count and the guessed number as input and would evaluate the message to tell the user. But four parameters for a function are quite much so let’s divide it for now. I’ll create a function which tells the user if the secret number is higher or lower as the guessed number — based on these inputs.
def user_won(guessed, secret):
if guessed == secret_number:
print('Congrats, you have Won!')
return True
if guessed > secret_number:
print('The secret number is lower...')
else:
print('The secret number is higher...')
In the function user_won we use a return statement to indicate if the user has won. If not then we return the implicit None which will evaluate to false.
One more thing we can test is asking the user if he/she wants to play another round or not.
def want_continue():
answer = ''
while answer.lower() not in ['yes', 'no', 'y', 'n']:
answer = input("Do you want to play another round? (yes / no) ")
return answer in ['yes','y']
After all these changes let me show you again the full code:
__author__ = 'GHajba'
import random
def ask_user_for_number(message_text):
while True:
try:
return int(input(message_text))
except ValueError:
print("This was not a number!")
continue
return number
def user_won(guessed, secret):
if guessed == secret_number:
print('Congrats, you have Won!')
return True
if guessed > secret_number:
print('The secret number is lower...')
else:
print('The secret number is higher...')
def want_continue():
answer = ''
while answer.lower() not in ['yes', 'no', 'y', 'n']:
answer = input("Do you want to play another round? (yes / no) ")
return answer in ['yes','y']
while True:
max_number = 0
while max_number != 100 and max_number != 1000:
max_number = ask_user_for_number('Should the secret number between 1 and 100 or 1 and 1000? ')
if max_number == 100:
guess_count = 7
else:
guess_count = 10
print('You have chosen {}, you will have {} guesses to find the secret number.'.format(max_number, guess_count))
secret_number = random.randint(1, max_number)
print('I have chosen the secret number...')
guesses = 0
while guess_count - guesses:
guesses += 1
guessed = ask_user_for_number("What's your guess? ")
if user_won(guessed, secret_number):
break
else:
print("Sorry, you lose.")
print("The secret number was ", secret_number)
if not want_continue():
break