import random

# This is the amount of digits that are accounted for in the random number generation and user input collection processes. This value can be modified to increase/decrease diversity in value selection.
digitCount = 3

def getRandomNumber():
    '''
    This function generates a random number (the size is based on digitCount) by cycling through an array of numbers (0 - 9) within a for loop.

    PARAMETER
        None.

    RETURN VALUE
        randomNumber: The random number string that is created upon cycling through the array of numbers and joining them into one string.
    '''
    # This details the array of numbers that are considered in the random number generation process. random.shuffle(numbers) shuffles the order of items within the array.
    numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    random.shuffle(numbers)
    # This creates the random number string by checking how many digits are required (digitCount) and iterating through the for loop that many times to form one string of appended values (randomNumber).
    randomNumber = ""
    for i in range(digitCount):
        randomNumber += str(numbers[i])
    return randomNumber

def getClues(guess, randomNumber):
    '''
    This function, upon the user inputting a guess, returns each clue upon differentiating conditions, sorts the clues alphabetically (i.e., "Bagels", "Fermi", "Pico"), and joins them into one response.

    PARAMETERS
        guess: The user's inputted string.
        randomNumber: The random number string that is created upon cycling through the array of numbers and joining them into one string.

    RETURN VALUE
        clues: The hints returned to the user upon meeting certain conditions through the guess input.
    '''
    # This returns the string "Correct!" upon a successful guess of the randomNumber by the user.
    if guess == randomNumber:
        return "Correct!"
    # This returns any of the listed clues (if it meets any of their conditions) based on guessing the randomNumber incorrectly.
    else:
        clues = []
        for i in range(len(guess)):
            # If the guess includes a digit which is in the correct answer and in the correct place, append "Fermi" to the return response.
            if guess[i] == randomNumber[i]:
                clues.append("Fermi")
            # If the guess includes a digit which is in the correct answer, but not in the correct place, append "Pico" to the return response.
            elif guess[i] in randomNumber:
                clues.append("Pico")
        # If the guess includes no digits which are in the correct answer, return "Bagels".
        if len(clues) == 0:
            return "Bagels"
        # This sorts the clues alphabetically and joins them into one string.
        clues.sort()
        return ' '.join(clues)

def restart():
    '''
    This function allows the user to decide (after a completed session) if he or she wants to play again. If so, then this will reload the game with the previously defined difficulty.

    PARAMETER
        None.

    RETURN VALUE
        input: Based on the input, and if it starts with "y", then the game will reload the game with the previously defined difficulty.
    '''
    # If the user inputs "y" and clicks "Enter", then the game will reload with the previously defined difficulty. If the user inputs any other character and clicks "Enter", then the game will not reload.
    print("Would you like to play again? If this is true, then type 'y' (yes). If this is false, then type any character to quit.")
    return input().lower().startswith('y')

def main():
    '''
    This function showcases the instructions of Bagels, along with the difficulty settings that we have created for the user. It also contains the main functionality of the game in correspondence with the functions created above this one.

    PARAMETER
        None.

    RETURN VALUE
        None.
    '''
    # This set of print functions introduces the user to the instructions of Bagels, along with the difficulty settings that we have created for him or her.
    print("Welcome to Bagels!\n")
    print("Bagels is a deduction game in which the player attempts to guess a random three-digit number (with no repeating digits) generated by the program. After each guess, the program will return one of three clues:\n")
    print("Bagels: None of the three digits guessed is in the correct answer.")
    print("Pico: One of the three digits guessed is in the correct answer, but the digit guessed is not in the correct place.")
    print("Fermi: One of the three digits guessed is in the correct answer and in the correct place.\n")
    print("The program will return the clues in alphabetical order. This is important to note as it means that one cannot assume, for example, that the first digit guessed is in the three-digit number and in the correct place AND the second digit guessed is in the three-digit number, but not in the correct place, from a response that reads: '<NAME>'.\n")
    print("The 'easy' difficulty allows you to have fifteen (15) guesses.")
    print("The 'normal' difficulty allows you to have ten (10) guesses.")
    print("The 'hard' difficulty allows you to have five (5) guesses.\n")
    # This invites the user to input his or her preferred difficulty settings. If he or she fails to follow the outlined conventions, then the user will be assigned a random amount of guesses between one (1) and twenty (20).
    difficultyInput = input("Select your difficulty by typing in one of the options above or, for convenience, the first letter of them. If you do not select a permissable difficulty, you will be assigned a random amount of guesses between one (1) and twenty (20).\n")
    difficulty = difficultyInput.lower()
    # If the user inputs "e" or "easy", then he or she will be assigned the "easy" difficulty.
    if difficulty == "e" or difficulty == "easy":
        newDifficulty = "EASY"
        guesses = 15
    # If the user inputs "n" or "normal", then he or she will be assigned the "normal" difficulty.
    elif difficulty == "n" or difficulty == "normal":
        newDifficulty = "NORMAL"
        guesses = 10
    # If the user inputs "h" or "hard", then he or she will be assigned the "hard" difficulty.
    elif difficulty == "h" or difficulty == "hard":
        newDifficulty = "HARD"
        guesses = 5
    # If the user fails to be assigned the "easy", "normal", or "hard" difficulties, then he or she will receive the "random" difficulty, which gives the user a random amount of guesses based on a range from one (1) to twenty (20).
    else:
        newDifficulty = "RANDOM"
        guesses = random.randint(1, 20)
    # If the difficulty is one of the above difficulties, then return the print functions below. Because they use the variable definitions, they update with the decisions that the user makes.
    if newDifficulty == "EASY" or newDifficulty == "NORMAL" or newDifficulty == "HARD" or newDifficulty == "RANDOM":
        print("You selected the", newDifficulty, "difficulty.")
        print("You have", guesses, "guesses.\n")
        print("The number that you are required to guess contains", digitCount, "digits.")
    # While everything within this statement is true...
    while True:
        # randomNumber is generated by calling the getRandomNumber function.
        randomNumber = getRandomNumber()
        print("The", digitCount, "digit number set has been generated. You have", guesses, "opportunities to guess it correctly.")
        # guessAttempts is the variable that is used for tracking the amount of guesses the user has made.
        guessAttempts = 1
        # While guessAttempts is less or equal than the amount of guesses...
        while guessAttempts <= guesses:
            guess = ""
            # While the length of the guess does not equal the amount of digits required for a true guess, refresh the guess (thus, not counting it).
            while len(guess) != digitCount:
                # "%s" and "% (guessAttempts)"" are associated in that %s is the tracker for how many guessAttempts are iterated.
                print("Guess #%s: " % (guessAttempts))
                guess = input()
            # clues is generated by calling the getClues function. Additionally, below, you will see that the clues are printed, which makes sense, so that the user can have an idea regarding their progress on solving the random number.
            clues = getClues(guess, randomNumber)
            print(clues)
            guessAttempts += 1
            # This ends the game upon a successful guess of the randomNumber by the user.
            if guess == randomNumber:
                break
            # This ends the game upon the user failing to guess the randomNumber within the allotted amount of guesses. It also informs the user of the correct answer.
            if guessAttempts > guesses:
                print("You have failed to guess the random three-digit number correctly with your allotted amount of guesses.", randomNumber, "was the correct answer.") 
        # If the user decides not to restart the game, then the program is ended. 
        if not restart():
            break

main()
import random

# This is the amount of digits that are accounted for in the random number generation and user input collection processes. This value can be modified to increase/decrease diversity in value selection.
digitCount = 3

def getRandomNumber():
    '''
    This function generates a random number (the size is based on digitCount) by cycling through an array of numbers (0 - 9) within a for loop.

    PARAMETER
        None.

    RETURN VALUE
        randomNumber: The random number string that is created upon cycling through the array of numbers and joining them into one string.
    '''
    # This details the array of numbers that are considered in the random number generation process. random.shuffle(numbers) shuffles the order of items within the array.
    numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    random.shuffle(numbers)
    # This creates the random number string by checking how many digits are required (digitCount) and iterating through the for loop that many times to form one string of appended values (randomNumber).
    randomNumber = ""
    for i in range(digitCount):
        randomNumber += str(numbers[i])
    return randomNumber

def getClues(guess, randomNumber):
    '''
    This function, upon the user inputting a guess, returns each clue upon differentiating conditions, sorts the clues alphabetically (i.e., "Bagels", "Fermi", "Pico"), and joins them into one response.

    PARAMETERS
        guess: The user's inputted string.
        randomNumber: The random number string that is created upon cycling through the array of numbers and joining them into one string.

    RETURN VALUE
        clues: The hints returned to the user upon meeting certain conditions through the guess input.
    '''
    # This returns the string "Correct!" upon a successful guess of the randomNumber by the user.
    if guess == randomNumber:
        return "Correct!"
    # This returns any of the listed clues (if it meets any of their conditions) based on guessing the randomNumber incorrectly.
    else:
        clues = []
        for i in range(len(guess)):
            # If the guess includes a digit which is in the correct answer and in the correct place, append "Fermi" to the return response.
            if guess[i] == randomNumber[i]:
                clues.append("Fermi")
            # If the guess includes a digit which is in the correct answer, but not in the correct place, append "Pico" to the return response.
            elif guess[i] in randomNumber:
                clues.append("Pico")
        # If the guess includes no digits which are in the correct answer, return "Bagels".
        if len(clues) == 0:
            return "Bagels"
        # This sorts the clues alphabetically and joins them into one string.
        clues.sort()
        return ' '.join(clues)

def restart():
    '''
    This function allows the user to decide (after a completed session) if he or she wants to play again. If so, then this will reload the game with the previously defined difficulty.

    PARAMETER
        None.

    RETURN VALUE
        input: Based on the input, and if it starts with "y", then the game will reload the game with the previously defined difficulty.
    '''
    # If the user inputs "y" and clicks "Enter", then the game will reload with the previously defined difficulty. If the user inputs any other character and clicks "Enter", then the game will not reload.
    print("Would you like to play again? If this is true, then type 'y' (yes). If this is false, then type any character to quit.")
    return input().lower().startswith('y')

def main():
    '''
    This function showcases the instructions of Bagels, along with the difficulty settings that we have created for the user. It also contains the main functionality of the game in correspondence with the functions created above this one.

    PARAMETER
        None.

    RETURN VALUE
        None.
    '''
    # This set of print functions introduces the user to the instructions of Bagels, along with the difficulty settings that we have created for him or her.
    print("Welcome to Bagels!\n")
    print("Bagels is a deduction game in which the player attempts to guess a random three-digit number (with no repeating digits) generated by the program. After each guess, the program will return one of three clues:\n")
    print("Bagels: None of the three digits guessed is in the correct answer.")
    print("Pico: One of the three digits guessed is in the correct answer, but the digit guessed is not in the correct place.")
    print("Fermi: One of the three digits guessed is in the correct answer and in the correct place.\n")
    print("The program will return the clues in alphabetical order. This is important to note as it means that one cannot assume, for example, that the first digit guessed is in the three-digit number and in the correct place AND the second digit guessed is in the three-digit number, but not in the correct place, from a response that reads: '<NAME>'.\n")
    print("The 'easy' difficulty allows you to have fifteen (15) guesses.")
    print("The 'normal' difficulty allows you to have ten (10) guesses.")
    print("The 'hard' difficulty allows you to have five (5) guesses.\n")
    # This invites the user to input his or her preferred difficulty settings. If he or she fails to follow the outlined conventions, then the user will be assigned a random amount of guesses between one (1) and twenty (20).
    difficultyInput = input("Select your difficulty by typing in one of the options above or, for convenience, the first letter of them. If you do not select a permissable difficulty, you will be assigned a random amount of guesses between one (1) and twenty (20).\n")
    difficulty = difficultyInput.lower()
    # If the user inputs "e" or "easy", then he or she will be assigned the "easy" difficulty.
    if difficulty == "e" or difficulty == "easy":
        newDifficulty = "EASY"
        guesses = 15
    # If the user inputs "n" or "normal", then he or she will be assigned the "normal" difficulty.
    elif difficulty == "n" or difficulty == "normal":
        newDifficulty = "NORMAL"
        guesses = 10
    # If the user inputs "h" or "hard", then he or she will be assigned the "hard" difficulty.
    elif difficulty == "h" or difficulty == "hard":
        newDifficulty = "HARD"
        guesses = 5
    # If the user fails to be assigned the "easy", "normal", or "hard" difficulties, then he or she will receive the "random" difficulty, which gives the user a random amount of guesses based on a range from one (1) to twenty (20).
    else:
        newDifficulty = "RANDOM"
        guesses = random.randint(1, 20)
    # If the difficulty is one of the above difficulties, then return the print functions below. Because they use the variable definitions, they update with the decisions that the user makes.
    if newDifficulty == "EASY" or newDifficulty == "NORMAL" or newDifficulty == "HARD" or newDifficulty == "RANDOM":
        print("You selected the", newDifficulty, "difficulty.")
        print("You have", guesses, "guesses.\n")
        print("The number that you are required to guess contains", digitCount, "digits.")
    # While everything within this statement is true...
    while True:
        # randomNumber is generated by calling the getRandomNumber function.
        randomNumber = getRandomNumber()
        print("The", digitCount, "digit number set has been generated. You have", guesses, "opportunities to guess it correctly.")
        # guessAttempts is the variable that is used for tracking the amount of guesses the user has made.
        guessAttempts = 1
        # While guessAttempts is less or equal than the amount of guesses...
        while guessAttempts <= guesses:
            guess = ""
            # While the length of the guess does not equal the amount of digits required for a true guess, refresh the guess (thus, not counting it).
            while len(guess) != digitCount:
                # "%s" and "% (guessAttempts)"" are associated in that %s is the tracker for how many guessAttempts are iterated.
                print("Guess #%s: " % (guessAttempts))
                guess = input()
            # clues is generated by calling the getClues function. Additionally, below, you will see that the clues are printed, which makes sense, so that the user can have an idea regarding their progress on solving the random number.
            clues = getClues(guess, randomNumber)
            print(clues)
            guessAttempts += 1
            # This ends the game upon a successful guess of the randomNumber by the user.
            if guess == randomNumber:
                break
            # This ends the game upon the user failing to guess the randomNumber within the allotted amount of guesses. It also informs the user of the correct answer.
            if guessAttempts > guesses:
                print("You have failed to guess the random three-digit number correctly with your allotted amount of guesses.", randomNumber, "was the correct answer.") 
        # If the user decides not to restart the game, then the program is ended. 
        if not restart():
            break

main()
# This is the amount of digits that are accounted for in the random number generation and user input collection processes. This value can be modified to increase/decrease diversity in value selection. This function generates a random number (the size is based on digitCount) by cycling through an array of numbers (0 - 9) within a for loop.
 
     PARAMETER
         None.
 
     RETURN VALUE
         randomNumber: The random number string that is created upon cycling through the array of numbers and joining them into one string. # This details the array of numbers that are considered in the random number generation process. random.shuffle(numbers) shuffles the order of items within the array. # This creates the random number string by checking how many digits are required (digitCount) and iterating through the for loop that many times to form one string of appended values (randomNumber). This function, upon the user inputting a guess, returns each clue upon differentiating conditions, sorts the clues alphabetically (i.e., "Bagels", "Fermi", "Pico"), and joins them into one response.
 
     PARAMETERS
         guess: The user's inputted string.
         randomNumber: The random number string that is created upon cycling through the array of numbers and joining them into one string.
 
     RETURN VALUE
         clues: The hints returned to the user upon meeting certain conditions through the guess input. # This returns the string "Correct!" upon a successful guess of the randomNumber by the user. # This returns any of the listed clues (if it meets any of their conditions) based on guessing the randomNumber incorrectly. # If the guess includes a digit which is in the correct answer and in the correct place, append "Fermi" to the return response. # If the guess includes a digit which is in the correct answer, but not in the correct place, append "Pico" to the return response. # If the guess includes no digits which are in the correct answer, return "Bagels". # This sorts the clues alphabetically and joins them into one string. This function allows the user to decide (after a completed session) if he or she wants to play again. If so, then this will reload the game with the previously defined difficulty.
 
     PARAMETER
         None.
 
     RETURN VALUE
         input: Based on the input, and if it starts with "y", then the game will reload the game with the previously defined difficulty. # If the user inputs "y" and clicks "Enter", then the game will reload with the previously defined difficulty. If the user inputs any other character and clicks "Enter", then the game will not reload. This function showcases the instructions of Bagels, along with the difficulty settings that we have created for the user. It also contains the main functionality of the game in correspondence with the functions created above this one.
 
     PARAMETER
         None.
 
     RETURN VALUE
         None. # This set of print functions introduces the user to the instructions of Bagels, along with the difficulty settings that we have created for him or her. # This invites the user to input his or her preferred difficulty settings. If he or she fails to follow the outlined conventions, then the user will be assigned a random amount of guesses between one (1) and twenty (20). # If the user inputs "e" or "easy", then he or she will be assigned the "easy" difficulty. # If the user inputs "n" or "normal", then he or she will be assigned the "normal" difficulty. # If the user inputs "h" or "hard", then he or she will be assigned the "hard" difficulty. # If the user fails to be assigned the "easy", "normal", or "hard" difficulties, then he or she will receive the "random" difficulty, which gives the user a random amount of guesses based on a range from one (1) to twenty (20). # If the difficulty is one of the above difficulties, then return the print functions below. Because they use the variable definitions, they update with the decisions that the user makes. # While everything within this statement is true... # randomNumber is generated by calling the getRandomNumber function. # guessAttempts is the variable that is used for tracking the amount of guesses the user has made. # While guessAttempts is less or equal than the amount of guesses... # While the length of the guess does not equal the amount of digits required for a true guess, refresh the guess (thus, not counting it). # "%s" and "% (guessAttempts)"" are associated in that %s is the tracker for how many guessAttempts are iterated. #%s: " % (guessAttempts)) # clues is generated by calling the getClues function. Additionally, below, you will see that the clues are printed, which makes sense, so that the user can have an idea regarding their progress on solving the random number. # This ends the game upon a successful guess of the randomNumber by the user. # This ends the game upon the user failing to guess the randomNumber within the allotted amount of guesses. It also informs the user of the correct answer. # If the user decides not to restart the game, then the program is ended.