patternpythonMinor
Beginning Python guessing game
Viewed 0 times
beginninggamepythonguessing
Problem
I am new to Python (and coding in general) and after about a week of reading "Thinking Like a Computer Scientist: Learning with Python" I decided to try and build a version the classic "guessing game". I added some extra features such as counting the number of guesses the user takes, and playing against a simulated "computer" player to make the program slightly more interesting. Also, the number of guesses the computer takes is based on the mean number of guesses needed to guess a number in a given range (which is logarithmic of base 2 for range n) and varies according to standard deviation.
Any feedback on the structure of my code or the way I generate the number of guesses the computer takes would be much appreciated!
```
# Number guessing game in Python
# Taylor Wright
# July 27 2016
import random
def get_number(level): #selects a random number in range depending on difficulty selected
if level == "e":
number = random.randint(1,20)
if level == "m":
number = random.randint(1,100)
if level == "h":
number = random.randint(1,1000)
elif level != "e" and level != "m" and level != "h":
print ("Invalid input!")
get_number()
return number
def select_level(): #prompts the user to select a difficulty to play on
while True:
level = str(input("Would you like to play on easy, medium, or hard? \n"
"Type 'e' for easy, 'm' for medium, or 'h' for hard!\n"))
if level != "e" and level != "m" and level != "h":
print("Invalid input!\n")
if level == "e" or level == "m" or level == "h":
break
return level
def guess_number(level): #function that prompts the user to guess within range depending on chosen difficulty
if level == "e":
guess = int(input("Guess a number between 1 and 20:\n"))
if level == "m":
guess = int(input("Guess a number between 1 and 100:\n"))
if level == "h":
guess =
Any feedback on the structure of my code or the way I generate the number of guesses the computer takes would be much appreciated!
```
# Number guessing game in Python
# Taylor Wright
# July 27 2016
import random
def get_number(level): #selects a random number in range depending on difficulty selected
if level == "e":
number = random.randint(1,20)
if level == "m":
number = random.randint(1,100)
if level == "h":
number = random.randint(1,1000)
elif level != "e" and level != "m" and level != "h":
print ("Invalid input!")
get_number()
return number
def select_level(): #prompts the user to select a difficulty to play on
while True:
level = str(input("Would you like to play on easy, medium, or hard? \n"
"Type 'e' for easy, 'm' for medium, or 'h' for hard!\n"))
if level != "e" and level != "m" and level != "h":
print("Invalid input!\n")
if level == "e" or level == "m" or level == "h":
break
return level
def guess_number(level): #function that prompts the user to guess within range depending on chosen difficulty
if level == "e":
guess = int(input("Guess a number between 1 and 20:\n"))
if level == "m":
guess = int(input("Guess a number between 1 and 100:\n"))
if level == "h":
guess =
Solution
Without changing the global structure of your code, here are a few things that could make it better.
NB: I will try to cover as much as I can, skipping objects and if-reduction, as it has already been covered in another answer
Docstrings
You use regular comments at the beginning of your functions. Their purpose is to indicate what the function does, therefore they should be docstrings. The difference is that it is stored in the
About
You could use a dictionary of levels to make it shorter and easier to understand. Also more flexible when you want to change levels later, or add new levels.
The content of the function is then something like:
Or even better, try and catch a
About
Golden rule : never rely on a user input (you can read about it here for example). As we've seen before, the user does not always enter what you asked for, and this can cause your game to crash. Fortunately, using
Naming arguments
Sometimes functions can be obscure, it can be helpful for others (and future you) to name arguments, as Python allows nicely.
This makes it way easier to understand.
Magic numbers
Speaking of the line of code just above,
For instance,
and the line becomes much more understandable
We could improve the
Then the whole method becomes way more readable
Also note that in
Lauching
It is recommended to encapsulate your
Global remarks
You used a lot of recursive calls. This is OK, since the number of calls remains small (once per user mistake). But I personally have a preference for
This answer is already quite long, and I've given you pieces of information to improve your code, not revolutionized the whole structure, that's why I didn't copy the whole code at the end.
NB: I will try to cover as much as I can, skipping objects and if-reduction, as it has already been covered in another answer
Docstrings
You use regular comments at the beginning of your functions. Their purpose is to indicate what the function does, therefore they should be docstrings. The difference is that it is stored in the
__doc__ special variable.def get_number(level):
"""selects a random number in range depending on difficulty selected"""
if level == "e":
# rest of the functionAbout
get_numberYou could use a dictionary of levels to make it shorter and easier to understand. Also more flexible when you want to change levels later, or add new levels.
levels = {'e': 20, 'm': 100, 'h': 1000}The content of the function is then something like:
if level != "e" and level != "m" and level != "h":
print ("Invalid input!")
get_number()
else:
number = random.randint(1, levels[level])
return numberOr even better, try and catch a
KeyError that is raised when the key does not exist. Final get_number function:levels = {'e': 20, 'm': 100, 'h': 1000}
def get_number(level):
"""selects a random number in range depending on difficulty selected"""
try:
number = random.randint(1, levels[level])
except KeyError:
print ("Invalid input!")
get_number()
return numberAbout
guess_numberGolden rule : never rely on a user input (you can read about it here for example). As we've seen before, the user does not always enter what you asked for, and this can cause your game to crash. Fortunately, using
try/except can solve this problem. I've kept the use of the levels dictionary here.def guess_number(level):
"""function that prompts the user to guess within range
depending on chosen difficulty"""
try:
guess = int(input("Guess a number between 1 and "+str(levels[level])+":\n"))
except KeyError:
print('This should never have happened. "level" is not part of the levels')
except (ValueError, NameError, SyntaxError):
print('Invalid input\n')
guess = guess_number(level)
return guessNaming arguments
Sometimes functions can be obscure, it can be helpful for others (and future you) to name arguments, as Python allows nicely.
if level == "e":
com_guesses = round(random.normalvariate(mu=3.7, sigma=1.1))This makes it way easier to understand.
Magic numbers
Speaking of the line of code just above,
3.7 and 1.1 seem to come out of nowhere. Two solutions to get this straight:- Create constants (not my favorite)
For instance,
EASY_MU = 3.7
EASY_SIGMA = 1.1
MEDIUM_MU = 5.8
# ...and the line becomes much more understandable
if level == "e":
com_guesses = round(random.normalvariate(mu=EASY_MU, sigma=EASY_SIGMA))- Use dictionaries (again)
We could improve the
levels dictionary to make it contain what we want for later.levels = {'e': {'size': 20, 'mu': 3.7, 'sigma': 1.1},
'm': {'size': 100, 'mu': 5.8, 'sigma': 1.319},
'h': {'size': 1000, 'mu': 8.99, 'sigma': 1.37474}
}Then the whole method becomes way more readable
def com_num_guesses(level):
"""function to get the number of guesses taken by the computer"""
try:
com_guesses = round(random.normalvariate(mu=levels[level]['mu'], sigma=levels[level]['sigma']))
except KeyError:
print('This should never have happened. "level" is not part of the levels')
print("The computer guessed the number in {0} guesses! Can you beat that?".format(com_guesses))
return com_guessesAlso note that in
get_number and guess_number, we will now have calls to levels[level]['size'] since the dictionary has changed.Lauching
It is recommended to encapsulate your
mainloop function within an if __name__ == '__main__' condition. That way, it will not get run if this module is imported by someone else. More information on that here.if __name__ == '__main__':
mainloop()Global remarks
You used a lot of recursive calls. This is OK, since the number of calls remains small (once per user mistake). But I personally have a preference for
while True loops that break as soon as the user input is correct. It uses less memory.This answer is already quite long, and I've given you pieces of information to improve your code, not revolutionized the whole structure, that's why I didn't copy the whole code at the end.
Code Snippets
def get_number(level):
"""selects a random number in range depending on difficulty selected"""
if level == "e":
# rest of the functionlevels = {'e': 20, 'm': 100, 'h': 1000}if level != "e" and level != "m" and level != "h":
print ("Invalid input!")
get_number()
else:
number = random.randint(1, levels[level])
return numberlevels = {'e': 20, 'm': 100, 'h': 1000}
def get_number(level):
"""selects a random number in range depending on difficulty selected"""
try:
number = random.randint(1, levels[level])
except KeyError:
print ("Invalid input!")
get_number()
return numberdef guess_number(level):
"""function that prompts the user to guess within range
depending on chosen difficulty"""
try:
guess = int(input("Guess a number between 1 and "+str(levels[level])+":\n"))
except KeyError:
print('This should never have happened. "level" is not part of the levels')
except (ValueError, NameError, SyntaxError):
print('Invalid input\n')
guess = guess_number(level)
return guessContext
StackExchange Code Review Q#135824, answer score: 5
Revisions (0)
No revisions yet.