Here is my first program I'm working on. I have built a framework here showing the sort of output and user interaction I would like during runtime, but it feels grossly oversized and unintuitive. Here is the code:

# This is an advanced guess the number game.

import random # The very basis of this game.
import math # Used for math.ceil
import time # Used to split up long portions of text.


def introDisplay(): # Displays the intro and defines the playerName Var. at the local scope, then returns it to the global scope.

    print('The Guessing Game! Now with more frustration!')
    playerName = input('\nHello, what is your name? ')
    print('\nWelcome ' + playerName + '.')
    print('\nThis is a guessing game with three different modes. It also has two different')
    print('settings for each mode. The range in which you will be guessing numbers, and')
    print('the amount of attempts you will have. You will be playing through the modes in')
    print('order, but you can always return to a game mode you\'ve unlocked.')
    #time.sleep(5)

    if playerName == 'Debug':
        gameModesUnlocked = input('\nEnter number of modes unlocked. <1/2/3> ')
        gameModesUnlocked = int(gameModesUnlocked)
    else:
        gameModesUnlocked = 1

    return playerName, gameModesUnlocked


def getSettings():

    while True:     # Main loop 1: Game mode.
        gameMode = chooseGameMode(playerName, gameModesUnlocked)
        confirmedGameMode = confirmGameMode(gameMode, gameModesUnlocked)
        if confirmedGameMode == True:
            break

    while True:     # Main loop 2: Range settings.
        rangeLower, rangeHigher, rangeLowerString, rangeHigherString, chooseToDefineRange = chooseRange()
        if chooseToDefineRange == True:
            rangeLower, rangeHigher, rangeLowerString, rangeHigherString = defineRange()
        confirmedRange = confirmRange(rangeLowerString, rangeHigherString)
        if confirmedRange == True:
            break

    while True:     # Main loop 3: Attempt settings.
        attemptsNumber, attemptsNumberString, chooseToDefineAttempts = chooseAttempts(rangeLower, rangeHigher)
        if chooseToDefineAttempts == True:
            attemptsNumber, attemptsNumberString = defineAttempts(rangeLower, rangeHigher)
        confirmedAttempts = confirmAttempts(attemptsNumberString)
        if confirmedAttempts == True:
            break


def chooseGameMode(playerName, gameModesUnlocked): # Here the player chooses the game mode and the result is returned to the playGame function.

    if gameModesUnlocked == 1:      # Ends this function if there is no possible choice to be made.
        print('\nOk, ' + playerName + ' Let\'s play the first mode: Simple guessing game!')
        gameMode = 1
        return gameMode

    print('\nOk, ' + playerName + ' choose which mode you would like to play.')
    if gameModesUnlocked == 2:        
        tempInput = input('The simple guessing game, or the one with addition and subtraction? <1/2> ')
        while True:
            if len(tempInput) == 1 and tempInput in '12':
                gameMode = int(tempInput)
                break
            else:
                tempInput = input('Please choose 1 or 2. ')
    elif gameModesUnlocked == 3:
        tempInput = input('The simple guessing game, the one with addition and subtraction, or the one with\nmore advanced maths? <1/2/3> ')
        while True:
            if len(tempInput) == 1 and tempInput in '123':
                gameMode = int(tempInput)
                break
            else:
                tempInput = input('Please choose 1, 2 or 3. ')
    else:
         print('Error!') # Work on error handling.

    return gameMode


def confirmGameMode(gameMode, gameModesUnlocked):# Used to confirm that the player is happy with their game mode setting. Result controls interaction with main loop 1.

    confirmedGameMode = True # Defined here to avoid repetition. Only returned in Escape #1/2.

    if gameModesUnlocked == 1:      # Ends this function if there is no possible choice to be made. Escape #1
        return confirmedGameMode

    if gameMode == 1:               # Reiterates to the chosen mode to the player.
        print('\nYou have chosen mode the first: Simple guessing game.', end=' ')
    elif gameMode == 2:
        print('\nYou have chosen mode the second: Guessing game with addition and subtraction.')
    elif gameMode == 3:
        print('\nYou have chosen mode the third: Guessing game with more advanced maths.')

    tempAnswer = input('Continue? <y/n> ').lower()
    while True:                     # Input loop.
        if tempAnswer == 'y':       # Returns and does break while loop in main. Escape #2
            print('\nOk!')
            return confirmedGameMode
        elif tempAnswer == 'n':     # Returns and does not break while loop in main. Escape #3
            print('\nThen, let\'s try again.')
            confirmedGameMode = False # Will not allow main loop to be broken.
            return confirmedGameMode
        elif len(tempAnswer) != 1 or tempAnswer not in 'yn':    # Bad input in 'tempAnswer', make new call and re-run input loop.
            tempAnswer = input('Please make a valid choice. <y/n> ').lower()


def chooseRange(): # Here the player chooses between 3 pre-set ranges to play in, or chooses to define their own, either choice ends the function.

    rangeLower = 1 # Sets some defaults to avoid empty variables being returned, using the defaults for the 's' range setting.
    rangeLowerString = '1'
    rangeHigher = 20
    rangeHigherString = '20'
    chooseToDefineRange = False

    chosenRangeType = input('\nWould you like a small, medium or large range? Or perhaps you\'d like to\nchoose the range yourself? <s/m/l/*> ').lower()
    while True:                         # Input loop.
        if chosenRangeType == 's':      # This section accepts input for the pre-sets and breaks out of the input loop, allowing the function to return.
            break
        elif chosenRangeType == 'm':
            rangeHigher = 40
            rangeHigherString = '40'
            break
        elif chosenRangeType == 'l':
            rangeHigher = 60
            rangeHigherString = '60'
            break
        elif chosenRangeType == '*':    # This section handles the case that users would like to define their own range. New input loop.            
            tempAnswer = input('Really choose your own range? <y/n> ').lower()
            while True:                 # Input loop.
                if tempAnswer == 'y':
                    chooseToDefineRange = True
                    return rangeLower, rangeHigher, rangeLowerString, rangeHigherString, chooseToDefineRange
                elif tempAnswer == 'n':
                    chosenRangeType = input('Then, let\'s try again. <s/m/l/*> ').lower()
                    break
                elif len(tempAnswer) != 1 or tempAnswer not in 'yn':    # Bad input in 'tempAnswer', make new call and re-run input loop.
                    tempAnswer = input('Please make a valid choice. <y/n> ').lower()
        elif len(chosenRangeType) != 1 or chosenRangeType not in 'sml*':    # Bad input in 'chosenRangeType', make new call and re-run input loop.
            tempAnswer = input('Please make a valid choice. <s/m/l/*> ').lower()

    return rangeLower, rangeHigher, rangeLowerString, rangeHigherString, chooseToDefineRange


def defineRange(): # Called when the player decides to define their own range to play in.

    tempInput = input('Please choose the lower number in your range. (1-9990) ')
    while True:                                 # Input loop.
        try:                                    # Tries to assign tempInput to an integer, impossible if input is not a number.
            rangeLower = int(tempInput)
            if rangeLower in range(1,9991):
                rangeLowerString = tempInput
                break                           # Breaks this input loop if input is as expected, allowing the next one to run.
            else:
                tempInput = input('Please enter a number between 1 and 9990. ') # Bad input in 'tempInput', make new call and re-run input loop.
        except ValueError:                      # This line is called in the case that a non-number is input.
            tempInput = input('Please enter a number between 1 and 9990. ') # Bad input in 'tempInput', make new call and re-run input loop.

    tempInteger = rangeLower + 10
    tempString = str(tempInteger)

    tempInput = input('Please choose the higher number in your range. (' + tempString + '-10000) ')
    while True:                                 # Input loop.
        try:                                    # Tries to assign tempInput to an integer, impossible if input is not a number.
            rangeHigher = int(tempInput)
            if rangeHigher in range(tempInteger,10001):
                rangeHigherString = tempInput
                break                           # Breaks this input loop if input is as expected, allowing the function to return.
            else:
                tempInput = input('Please enter a number between ' + tempString +' and 10000. ') # Bad input in 'tempInput', make new call and re-run input loop.
        except ValueError:                      # This line is called in the case that a non-number is input.
            tempInput = input('Please enter a number between ' + tempString +' and 10000. ') # Bad input in 'tempInput', make new call and re-run input loop.

    return rangeLower, rangeHigher, rangeLowerString, rangeHigherString


def confirmRange(rangeLowerString, rangeHigherString): # Used to confirm that the player is happy with their range setting.

    tempAnswer = input('\nYour range will be between ' + rangeLowerString + ' and ' + rangeHigherString + '. Are you happy with this? <y/n> .').lower()
    while True:                                 # Input loop.
        if tempAnswer == 'y':
            print('Ok!')
            confirmedRange = True               #
            break                               # Breaks, allowing the function to return, and the program to leave main loop 2.
        elif tempAnswer == 'n':
            print('Then, let\'s try again.')
            confirmedRange = False              #
            break                               # Breaks, allowing the function to return, but does not let the program leave main loop 2.
        elif len(tempAnswer) != 1 or tempAnswer not in 'yn':    # Bad input in 'tempAnswer', make new call and re-run input loop.
            tempAnswer = input('Please make a valid choice. <y/n> ').lower()

    return confirmedRange


def chooseAttempts(rangeLower, rangeHigher):

    rangeTrue = rangeHigher - rangeLower + 1 # Sets some defaults to avoid empty variables being returned, using the defaults for the 'e' attempt setting.
    attemptsNumber = int(math.ceil(rangeTrue / 2))
    chooseToDefineAttempts = False

    chosenDifficulty = input('Now, let\'s choose your difficulty! Would you like it to be easy, normal or hard?\nOr choose your own amount of attempts? <e/n/h/*> ').lower()
    while True:                         # Input loop.
        if chosenDifficulty == 'e':     # This section accepts input for the pre-sets and breaks out of the input loop, allowing the function to return.
            break
        elif chosenDifficulty == 'n':
            attemptNumber = int(math.ceil(rangeTrue / 3))
            break
        elif chosenDifficulty == 'h':
            attemptNumber = int(math.ceil(rangeTrue / 4))
            break
        elif chosenDifficulty == '*':   # This section handles the case that users would like to define their own number of attempts. New input loop.            
            tempAnswer = input('Really choose your own number of attempts? <y/n> ').lower()
            while True:                 # Input loop
                if tempAnswer == 'y':
                    chooseToDefineAttempts = True
                    return attemptsNumber, attemptsNumberString, chooseToDefineAttempts
                elif tempAnswer == 'n':
                    chosenDifficulty = input('Then, let\'s try again. <e/n/h/*> ').lower()
                    break
                elif len(tempAnswer) != 1 or tempAnswer not in 'yn':    # Bad input in 'tempAnswer', make new call and re-run input loop.
                    tempAnswer = input('Please make a valid choice. <y/n> ').lower()
        elif len(chosenDifficulty) != 1 or chosenDifficulty not in 'enh*':    # Bad input in 'chosenDifficulty', make new call and re-run input loop.
            tempAnswer = input('Please make a valid choice. <e/n/h/*> ').lower()

    attemptsNumberString = str(attemptsNumber)

    return attemptsNumber, attemptsNumberString, chooseToDefineAttempts


def defineAttempts(rangeLower, rangeHigher): # Called when the player decides to define their own number of attempts to play with.

    rangeTrue = rangeHigher - rangeLower + 1            # Some maths and assignment used in this function.
    attemptsFloor = int(math.ceil(rangeTrue * .10))
    attemptsCeiling = int(math.ceil(rangeTrue * .90))
    attemptsFloorString = str(attemptsFloor)
    attemptsCeilingString = str(attemptsCeiling)

    tempInput = input('Please enter the number of attempts you would like to use (' + attemptsFloorString + '-' + attemptsCeilingString + '). ')
    while True:                         # Input loop.
        try:                            # Tries to assign tempInput to an integer, impossible if input is not a number.
            attemptsNumber = int(tempInput)
            if attemptsNumber in range(attemptsFloor,attemptsCeiling):
                attemptsNumberString = tempInput
                break                   # Breaks this input loop if input is as expected, allowing the function to return.
            else:
                tempInput = input('Please enter a number between ' + attemptsFloorString + ' and ' + attemptsCeilingString + '. ') # Bad input in 'tempInput', make new call and re-run input loop.
        except ValueError:              # This line is called in the case that a non-number is input.
            tempInput = input('Please enter a number between ' + attemptsFloorString + ' and ' + attemptsCeilingString + '. ') # Bad input in 'tempInput', make new call and re-run input loop.

    return attemptsNumber, attemptsNumberString


def confirmAttempts(attemptsNumberString):

    tempAnswer = input('\nYou will have ' + attemptsNumberString + ' attempts. Are you happy with this? <y/n> .').lower()
    while True:                                 # Input loop.
        if tempAnswer == 'y':
            print('Ok!')
            confirmedAttempts = True            #
            break                               # Breaks, allowing the function to return, and the program to leave main loop 3.
        elif tempAnswer == 'n':
            print('Then, let\'s try again.')
            confirmedAttempts = False           #
            break                               # Breaks, allowing the function to return, but does not let the program leave main loop 3.
        elif len(tempAnswer) != 1 or tempAnswer not in 'yn':    # Bad input in 'tempAnswer', make new call and re-run input loop.
            tempAnswer = input('Please make a valid choice. <y/n> ').lower()

    return confirmedAttempts





#  PROGRAM  START  #############################################################################

playerName, gameModesUnlocked = introDisplay()

gameMode, rangeLower, rangeHigher, rangeLowerString, rangeHigherString, attemptsNumber, attemptsNumberString = getSettings()

I know I'm making very poor use of functions here, and getting to the point where I understand how to use arguments correctly will make my program a lot smaller and neater. For example I have a few function that are almost identical except for their expected input and output, can I merge them with better use of arguments? And how would I go about that?

At the moment I am only working on the first part of the program, where you choose your settings. There are three different types of functions, 'choose'. 'define' and 'confirm'. If anyone could show me an example of how they might go about writing this part of my program in a more intuitive way I would be very grateful.

Aethir,
the only advice I would have to give on the subject is that you seem to put a lot of print statements in all of your functions, where I on the other hand would use functions to process some information and return an answer, and use a main function to handle print statements and call functions when needed. Doesn't really cut down on lines of code, but can make it seem less chaotic and easier to navigate.

Hope this makes sense and helps,

  • WolfShield

Here is my number guess game, if you want to compare with your approach. http://www.daniweb.com/software-development/python/code/287950/classic-number-guess-game-python-ternary-operator-demonstration

Basic thing not to bloat code with infinite loops checking validity is to just accept anything as long as exception does not occur and let user easily to reenter the numbers if he notices that he entered wrong. Above code is doing that kind of check with try except.

Also using GUI like tk helps you with built in input methods from list or given list of choices, so sometimes making GUI program is easier than comprehensive text display version.

Yea, I think trying to make a 'really good' text based game in this case is not helpful. I was simply trying to make the best use of the tools I had already learnt. I've rewritten this code several times so far and I believe I make it better each time in certain ways, like handling bad input, but if it as you say, that a gui based program wont need such stringent checks on validity, then I guess it's is a little pointless.

I can see that filling functions with print() is not a good idea, but I'm failing to see where exactly I should put the output, I see that python does not have a 'main' function like C++, it all comes down to a matter of convention, learning and making use of that convention is what I would like to work on. Unfortunatley few tutorials give example of truly conventional code, and if they do, it leaves the beginner noe the wiser on how to scale it up into larger, multi function programs.

If I have to have multi-line print statements I do something like this:

print("This is my first line of text\n" + \
      "And then a second line.\n" + \
      "And a third.")

I read python's indention really well, probably like a lot of people, and this makes those lines get out of the way so I can read through the function.
Also, the first version of some semi-complicated task is always a bit of a mess with me. I go back and
refactor and function things out when I am comfortable with the way things are working. It also gives me a chance to simplify and do things that I hadn't noticed the first time around. (Like turning a 7 line function into a 3 line). The more I learn about the language, the shorter and clearer my code becomes (still have a lot to learn though).

This article has been dead for over six months. Start a new discussion instead.