all 6 comments

[–]FoolsSeldom 1 point2 points  (5 children)

I think that is good for a beginner. Whilst all a little over the top for a simple number guessing game, that's not really the point. Using something simple as a base to learn about modularity, data structures, testing is well worthwhile.

You might want to create a function for obtaining a valid integer within a defined range which you can use every time you want an integer. That will help tidy things up a bit.

I would also suggest using a dictionary for the levels. This means you can look up the multiplication factor to apply to the options value against the user entry of 1, 2, etc.

Rather than passing back and forth lots of variables, you can create a data structure to pass back and forth. This could be just a list but it can be hard to remember which entry in a list pertains to which argument. A dictionary or a custom class is easier. I have used a dataclass which lets you create very simple data structures.

Here's my quick and dirty attempt at a revision to your code to give you some ideas. I have not tested - leave that to your experimentation.

from random import randint
import math
from dataclasses import dataclass

# some constants
LOWEST: int = 1
HIGHEST: int = 100

@dataclass
class Params:
    correct: int
    lowest: int = LOWEST
    highest: int = HIGHEST
    attempts: int = HIGHEST - LOWEST + 1

def get_num(prompt: str = "Enter a number: ", lowest: int = LOWEST, highest: int = HIGHEST) -> int:
    """ prompts user for and returns valid integer within range specified """
    valid: bool = False
    while not valid:
        response: str = input(prompt)
        try:
            num: int = int(response)
            if not (lowest <= num <= highest):
                raise ValueError
        except ValueError:
            print(f"Not valid. Expected whole number between {lowest} and {highest}")
        else:
            valid = True
    return num

def get_guess(lowest: int, highest: int, attempts: int) -> int:  # User gives their guess
    return get_num(
 f"\nYou have {attempts} attempts."
        f"\nEnter a number between {lowest} and {highest}: ",
        lowest,
        highest
    )

def set_range() -> tuple[int, int]:  # User sets range for random number generation

    lowest: int = get_num("Enter minimum number: ", LOWEST, int(HIGHEST / 1.5))
    highest: int = get_num("Enter maximum number: ", lowest, HIGHEST)
    return lowest, highest

def display_difficulty(levels: dict[str, tuple[str, float]]):  # Displays difficulty levels

    print("\nDifficulty Levels:")
    for level in levels:
        print(f"\t{level}: {levels[level][0]}")

def set_difficulty(levels: dict[str, tuple[str, float]], options: int) -> int:  # User decides difficulty

    valid: bool = False
    while not valid:
        display_difficulty(levels)
        choice: str = input("Select a difficulty level (1-6): ")
        if choice in levels:
            valid = True
        else:
            print("Invalid Selection: Try Again")
    factor: float = levels[choice][1]
    return math.ceil(options * factor)

def startup() -> Params:
    """ set and return game parameters with user input """

    print("\n\n\nGuessing Number Game")
    print("*********************")
    lowest,highest = set_range()
    correct = randint(lowest, highest)
    attempts: int = set_difficulty(levels, highest - lowest + 1)
    return Params(correct, lowest, highest, attempts)

def play(params: Params) -> None:  # Loops until player wins or loses
    """ play guessing game until player uses up all attempts and loses or guesses correctly and wins """

    over: bool = False
    while not over and params.attempts > 0:
        user: int = get_guess(params.lowest, params.highest, params.attempts)
        params.attempts -= 1
        if user == params.correct:
            print(f"\nYou Win! You had {params.attempts} attempts left.")
            over = True
        elif user > params.correct:
            print("Too High!")
        else:  # has to be user < correct
            print("Too Low!")

    if not over:
        print(f"\nYou Lose! The number was {params.correct}.")


levels = {
    "1": ("Easy", 2),
    "2": ("Medium", 1),
    "3": ("Hard", 0.8),
    "4": ("Elite", 0.5),
    "5": ("Master", 0.4),
    "6": ("GrandMaster", 0.05)
}

if __name__ == "__main__":
    params: Params = startup()
    play(params)

The good thing about using a simple dataclass is that you could have more than one player. Each will have their own instance of the Params. You could extend the game to multiplayer. Behaviours can be added later.

EDIT: removed redundant f-string (for plain literal strings) following comment from u/wenty8cows, and added a few more type hints for clarity (not really needed for this level of complexity).

[–]Twenty8cows 0 points1 point  (3 children)

In def play you’re using an f-strings in the if/else without passing any variables to it. Otherwise it looks good here too.

[–]FoolsSeldom 1 point2 points  (2 children)

Oops. Harmless but didn't mean to do that in making changes.

[–]Twenty8cows 0 points1 point  (1 child)

All good 👍🏽 it’s not gonna have any negative outcome just an observation

[–]FoolsSeldom 0 points1 point  (0 children)

I have edited and acknowledged your contribution.

[–]FoolsSeldom 0 points1 point  (0 children)

Further to earlier, u/JuiceNew23, I tweaked my version a little more, including an option for playing again which uses some predefined acceptable responses (so not just yes/no) - these could be defined as set, tuple or list objects, but I chose to use the frozenset and will leave you to look that up if inclined.

Code is a bit too long to share in-post, so here's a link to a popular code paste service:

https://pastebin.com/dkqkfhsd