Aside from the notes already given, here are two big ideas to think about:
Don't repeat yourself (DRY)
This is something you'll hear repeated a lot in discussions of code. Any time you see the same "magic values" repeated more than one place (e.g. 0 and 20), or you see two lines of code that do exactly the same thing for exactly the same reason (e.g. your input()
statements or the multiple CORRECT
checks) it's a clue that you have an opportunity to share some code.
In the case of the magic numbers, one solution is to define them as constants at the top of the file. The approach I'd prefer, personally, would be to make them parameters -- rather than hardcoding 0
and 20
, have your function(s) take a range
as a parameter.
In the case of the multiple "CORRECT" checks, this is just a matter of restructuring the loop a little.
Have each function do one thing well
This is significantly more subjective, but in looking at your function, I see two very separable things it's doing within its single main loop -- prompting the user for valid input, and giving them feedback on their guess. If you have one piece of code that just has to get the input without having to worry about the game loop, and you have another that handles the game loop without having to worry about whether the input is valid, then both of them can be simpler.
When you start doing larger projects, breaking logic apart into simple functions makes it easier to test the individual units of your code independently.
I messed around with the code for a bit using those two principles and came up with this:
import random
def describe_range(numbers: range) -> str:
"""Nice English-language description of a range."""
return f"between {numbers[0]} and {numbers[-1]}"
def input_number(numbers: range) -> int:
"""
Prompt the user for a number within the range,
retrying on invalid input.
"""
while True:
try:
number = int(input())
if not number in numbers:
raise ValueError(f"The number needs to be {describe_range(numbers)}")
return number
except ValueError as e:
print(e)
def guess_number(numbers: range) -> None:
"""
Play a guessing game with the user within a range of numbers.
Tell them whether their guess is too big or too small.
They win when they get it right!
"""
print("Enter a random number", describe_range(numbers))
answer = random.choice(numbers)
while True:
guess = input_number(numbers)
if guess > answer:
print("too big")
elif guess < answer:
print("too small")
else:
print("CORRECT")
break
if __name__ == "__main__":
guess_number(range(21))
Note that I'm using Python 3's type annotations to say what type of argument each function takes and what it returns -- this is really handy because you can use the mypy
tool to automatically check your code for errors (for example if you say a function returns an int
and there's a line in that function that returns a str
, mypy
will throw an error), and therefore you don't need to worry as much about your code getting the wrong type at runtime. The type annotations also serve as documentation for your functions so that human readers can easily see what sorts of values they should expect the code to be working with.