HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Beginning Python guessing game

Submitted by: @import:stackexchange-codereview··
0
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 =

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 __doc__ special variable.

def get_number(level):
    """selects a random number in range depending on difficulty selected"""
    if level == "e":
        # rest of the function


About get_number

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.

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 number


Or 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 number


About guess_number

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 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 guess


Naming 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_guesses


Also 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 function
levels = {'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 number
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 number
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 guess

Context

StackExchange Code Review Q#135824, answer score: 5

Revisions (0)

No revisions yet.