6
\$\begingroup\$

I started my python journey 4 days ago. I am following a Youtube tutorial currently to understand the basics of Python. So bear in mind that I am REALLY new to this. I wanted to test myself a little so I Googled for a really basic program that I could try to make. I decided on a Guessing the number game (very original). Tbh I am really proud that I could make this myself (only googled how the random.randrange thing was supposed to work). This is the code I ended up with. Is this good progress so far? I would love some advice and other tutorials links I should follow after my current one. Any help would be so helpful as I was feeling a bit down when I couldn't even solve the EASIEST question on leetcode (1929). This really gave me the confidence boost that I needed to continue my journey.

import random
import time
count = 0
def validation():
    global guess
    while guess > int(range) or guess < 1:
        print("You have inputted a value outside of the range.")
        time.sleep(1)
        guess = int(input("Please try again: "))
range = input("Please enter what range you would like to select for the round: ")
time.sleep(1)
print("You have chosen the range of: " + range)
time.sleep(2)
print("How to play: You type in a number, if your number matches the randomly selected number, YOU WIN!")
time.sleep(3)
print("You will get hints based on your guesses along the way.")
time.sleep(2)
print("Try to get to the random number in the least amount of guesses as possible.")
time.sleep(2)
computer = int(random.randrange(1,int(range)))
guess = int(input("Please enter your guess within the range of 1 to " + range + ": "))
validation()
count += 1
while guess != computer:
    if guess > computer:
        print("Your value of " + str(guess) + " is higher than the random number.")
        time.sleep(2)
        guess = int(input("Try again with a lower value: "))
        validation()
    else:
        print("Your value of " + str(guess) + " is lower than the random number.")
        time.sleep(2)
        guess = int(input("Try again with a higher value: "))
        validation()
    count += 1
print("BANG ON, YOU GUESSED THE RIGHT NUMBER!")
time.sleep(1)
print("You got to the random number in " + str(count) + " guesses.")
\$\endgroup\$
3
  • 2
    \$\begingroup\$ For a beginner after 4 days, the above code is already impressive. Thank you for describing in detail how you are learning and what your current progress is, that will help us give you helpful answers. What editor or IDE (integrated development environment) are you using to program in Python? \$\endgroup\$ Jun 16 at 19:52
  • 3
    \$\begingroup\$ @RolandIllig I use PyCharm, it was the first IDE that I was recommended and I honestly find it being pretty good. \$\endgroup\$
    – Hissan
    Jun 16 at 20:08
  • 1
    \$\begingroup\$ @Hissan, if one of the below answers was a good review, remember to mark it as the accepted answer! That way it will be highlighted as the best answer, the author will get credit, and it will stop showing up in the list of questions that still need review. \$\endgroup\$ Jun 17 at 16:18

4 Answers 4

10
\$\begingroup\$

Congrats for starting on your journey! Python is a great start to get into programming. And you got your very first project working! I can see that you used the basic elements of programming languages, namely loops (while or for), conditionals (if) and functions. You're still missing classes, but that's not important for now, you'll get there eventually. Classes can make big projects a lot easier, however for small projects they're not always needed - they are pretty good tools though, even if it's only to group data in structures.

That said, a problem you will encounter many times on your journey will be the readability and understandability of your code. The machine can understand your code perfectly well - assuming it compiles - however we as humans may not always. And with readability and understandability often comes maintainability, i.e. how easy it is to introduce a new functionality / requirement to your program.

The easiest counter example that pretty much any developer has done themselves, is to write some code and revisit it 2 weeks later, only to understand none of it.

To battle this, a lot of approaches have been taken. In Python there is the PEP Style Guide, which contains A LOT of info. I'd argue against reading it all - at least in one go - as it's just so much. Maybe a good start would be to jump to a random section, see if you understand it and try to implement it. If you don't understand it, stackoverflow or codereview are good places to ask questions.

There also are more general approaches, namely programming principles. There are also A LOT of principles, and little of them are agreed on by everyone. What pretty much everyone agrees on however, are the DRY and KISS principles.

Speaking of them, your project is pretty small and about as simple as it gets, so I'd say you're fine with the KISS principle. However, you are repeating code:

    if guess > computer:
        print("Your value of " + str(guess) + " is higher than the random number.")
        time.sleep(2)
        guess = int(input("Try again with a lower value: "))
        validation()
    else:
        print("Your value of " + str(guess) + " is lower than the random number.")
        time.sleep(2)
        guess = int(input("Try again with a higher value: "))
        validation()

The following part is repeated:

        validation()

and can easily be pulled out of the conditional:

    if guess > computer:
        print("Your value of " + str(guess) + " is higher than the random number.")
        time.sleep(2)
        guess = int(input("Try again with a lower value: "))
    else:
        print("Your value of " + str(guess) + " is lower than the random number.")
        time.sleep(2)
        guess = int(input("Try again with a higher value: "))
    validation()

The parts in the conditional still look pretty repetitive, and are basically the same, except for a few keywords:

print(...)
sleep(2)
guess = input(...)

so it can probably be extracted into a function - which I'll leave open for now as this post is already pretty long and for a project this small it doesn't matter a lot.

If you want to read further information, here's a post I recently wrote that goes a lot more in depth about basic readability and understandability. You can probably ignore the part about insufficient encapsulation, but I highly recommend reading the part about naming and long functions.

Cheers and have fun!

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Thank you so much for your advice, I will surely comply! \$\endgroup\$
    – Hissan
    Jun 16 at 20:43
7
\$\begingroup\$

White Space

Your code needs some.

Specifically, blank lines between logical sections of code.

  • between imports and function definitions
  • Between function definitions
  • After the last function definition before mainline code
  • Between setup code (asking for range & generating random number), & looping code (user guess loop), & wrap up code (congratulatory messages after exiting guess loop)

Not so hidden in that list is an implicit “Don’t put code above the function declarations.” Specifically, count = 0 should be moved closer to where it is used.

Reserved Identifiers

range(start, end, step) is a builtin function you can no longer use because you’ve made a variable called range. While it is allowed, don’t do it!

DRY: Don’t Repeat Yourself

You use time.sleep(#) many, many times. Each time, you type the prefix time.. Instead, you can just import the sleep function:

from time import sleep

…
sleep(2)
…
sleep(3)
…

DRY: Don’t Repeat Yourself

You use int(range) a few times, converting the string into an int each time. Instead, store the integer directly in the variable:

maximum = int(input("Please enter what range you would like to select for the round: "))

Unnecessary string manipulation

Here you have an explicit conversation of guess to a string:

        print("Your value of " + str(guess) + " is higher than the random number.")

The print(…) statement can print out multiple items, and will automatically put a space between each. Do you could write the slightly shorter (and clearer:

        print("Your value of", guess, "is higher than the random number.")

Alternately, you can use f-strings:

        print(f"Your value of {guess} is higher than the random number.")

Random Range

In Python, ranges are inclusive of the lower limit, and exclusive of the upper limit. This means random.randrange(1, 10) will generate random numbers from 1, up to, but not including 10.

In contrast, random.randint(1, 10) will generate the number 10 on occasion. It is probably the function you want.

Additionally, the value returned by both random.randrange and random.randint will always be an integer. There is no need to pass the result to int(...).

Reworked Code

Here is a reworking of your code with some of the above points. I've add in some additional error checking, so if the player types in a bad value, like x instead of 10, it doesn't crash the program.

There is no longer any global variables.

I've added a "main guard", which runs guessing_game(). This is a good habit to get into, as it makes it easier to test your code and/or import functions into other modules.

For brevity, I've left out several of the print() statements and all of the sleep() calls. You'll want to add those back in.

import random

def ask_for_int(prompt):
    
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            print("Not a valid integer")

def guessing_game_round(limit, secret):
    
    guess = None

    count = 0
    while guess != secret:

        if guess is None:
            prompt = f"Please enter your guess within the range of 1 to {limit}: "
        else:
            hint = 'lower' if guess > secret else 'higher'
            prompt = f"Try again with a {hint} value: "

        guess = ask_for_int(prompt)

        while not (1 <= guess <= limit):
            print("You have inputted a value outside of the range.")
            guess = ask_for_int("Please try again: ")

        count += 1

        if guess != secret:
            result = 'higher' if guess > secret else 'lower'
            print(f"Your value of {guess} is {result} than the random number.")

    print("BANG ON, YOU GUESSED THE RIGHT NUMBER!")
    print(f"You got to the random number in {count} guesses.")

def guessing_game():
    
    limit = ask_for_int("Please enter what range you would like to select for the round: ")
    while limit < 2:
        limit = ask_for_int("Too low.  Please enter a larger value: ")

    secret = random.randint(1, limit)
    guessing_game_round(limit, secret)


if __name__ == '__main__':
    guessing_game()
\$\endgroup\$
2
  • \$\begingroup\$ @AjNeufled All you advices are really helpful. The tutorial I followed told that the print statement cannot print a string along with another data type or vice versa, that is why I was using string conversion. I will keep all this in mind from now on. Thanks alot! \$\endgroup\$
    – Hissan
    Jun 17 at 7:02
  • \$\begingroup\$ Noticed you are using randrange() instead of randint(). I've added a section about that to the review. \$\endgroup\$
    – AJNeufeld
    Jun 17 at 23:02
2
\$\begingroup\$

Underlining the structural advices of AJNeufeld:

Structure helps to break down logic and express this organization visually, so that your code gets easier to read.

I will try to illustrate the benefits with examples (comparing before and after).

1. Whitespace

See PEP-008 (the original Python-Styleguide) for blank lines (vertical) to structure your code and (horizontal) whitespace in expressions and statements to make them easy readable.

Example

Before:

import random
import time
count = 0
def validation():

After

import random
import time

count = 0


def validation():
    // body


// main script
if __name__ ==  "__main__":
    // starts here

Gives you visual overview of 4 blocks (compare to chapters in a book):

  1. imports
  2. fields or global variable declarations
  3. methods or functions
  4. main-block for scripts, modules, etc.

2. Don't Repeat Yourself (DRY)

The DRY principle is so important in software development, because it's application results in reuse of components, modularization and is the key-enabler of automation.

Example

Before:

time.sleep(1)
print("You have chosen the range of: " + range)
time.sleep(2)
print("How to play: You type in a number, if your number matches the randomly selected number, YOU WIN!")
time.sleep(3)
print("You will get hints based on your guesses along the way.")
time.sleep(2)
print("Try to get to the random number in the least amount of guesses as possible.")
time.sleep(2)

WET: 8 lines that unveil a hidden pattern - repetition:

  • a pair of statements repeats (2 x 4 printing with pause)
  • pauses seem related to text-length, most 2 seconds long only few outliers with 1 or 3
  • most printed texts are static (not containing variables) and none of them waits for an answer - a monologue!

After:

from time import sleep

range = 0
scripted_monologue = [
    (2, f"You have chosen the range of: {range}"),
    (3, "How to play: You type in a number, if your number matches the randomly selected number, YOU WIN!"),
    (2, "You will get hints based on your guesses along the way."),
    (2, "Try to get to the random number in the least amount of guesses as possible.")
]


def play(monologue):
    for (seconds, text) in monologue: 
        print(text)
        sleep(seconds)


play(scripted_monologue)

Modularized for reuse (followed DRY):

  • now you can play any monologue and even define them like a script in an external CSV-file
  • the loop repeats for you whatever is defined in the passed in list
  • instead 8 lines, you can re-use 1 call: play(scripted_monologue)
  • even the pauses can be calculated by text-length

Re-use alternatives:

  • calculate the pause from text-length (read-speed assumed: 2 seconds for 50 chars)
def read_duration(text):
    return len(text) * (2 / 50)  # 2 seconds for 50 chars


def play(monologue):
    for (_, text) in monologue:  # ignore predefined seconds 
        print(text)
        sleep(read_duration(text))  # calculate the pause
  • Or instead printing lets say the text using a text-to-speech command (like say from macOS) so that blind users can hear.

All you have to do is adjust the function, no need to touch your main.

\$\endgroup\$
1
  • \$\begingroup\$ @Teepeemm, Thanks for spotting this adverse-semantical typo: shifting the character backwards in alphabet from m to l gives the antonym which I meant. \$\endgroup\$
    – hc_dev
    Jun 18 at 9:19
1
\$\begingroup\$

As you said, you are using PyCharm for writing Python code. Using this IDE, it only takes a single keyboard shortcut to make your code conform to large parts of PEP 8, which is the Python style guide.

The exact shortcut depends on your operating system. Other IDEs have similar features.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.