Can't get while loop to stop

I am making a number guessing game, and I have a while loop over the whole thing. The problem is, I can’t get it to stop when I want it to.

import random
import sys
import time
ynChoice = 'x'
global runGTN
runGTN = True

while runGTN == True: #the loop
    typing_speed = 300 #wpm
    def printSlow(*t):
        for l in t:
            sys.stdout.write(l)
            sys.stdout.flush()
            time.sleep(random.random()*10.0/typing_speed)
        print()

    def confirmChoice(ynChoice):
        if ynChoice.lower() == 'yes' or ynChoice.lower() == 'y':
            printSlow('Got it!')
            ynChoice = 'y'
        elif ynChoice.lower() == 'no' or ynChoice.lower() == 'n':
            printSlow('Oh. Ok!')
            ynChoice = 'n'
        else:
            printSlow('I don\'t understand. Please input \"yes\" or \"no\".')

    def askGuessDifficulty():
        printSlow('Alright! I\'ll pick a number from 1 to 100, and you try to guess it!')
        printSlow('''You can do easy mode (E), with infinite guess; normal mode (N), with 10 guesses;
    hard mode (H), with 5 guesses; or custom mode (C), where you pick how many guesses you get!''')
        guessDifficulty = input()
        global guesses
        if guessDifficulty.lower() == 'e':
            guesses = 999
            confirmChoice(input('You want easy mode, right? (Please input \"Yes\" or \"No\")'))
        elif guessDifficulty.lower() == 'n':
            guesses = 10
            confirmChoice(input('You want normal mode, right? (Please input \"Yes\" or \"No\")'))
        elif guessDifficulty.lower() == 'h':
            guesses = 5
            confirmChoice(input('You want hard mode, right? (Please input \"Yes\" or \"No\")'))
        elif guessDifficulty.lower() == 'c':
            confirmChoice(input('You want custom mode, right? (Please input \"Yes\" or \"No\")'))
            guesses = input()
            if guesses.isnumeric() == True:
                printSlow('Ok! You want to have ', guesses, ' guesses, right?')
                confirmChoice(input('(Please input \"yes\" or \"no\")'))
                if ynChoice == 'n':
                    return
            else:
                printSlow('I don\'t understand. Please input a number for the amount of guess that you want.')
        else:
            printSlow('''I don\'t understand. Please input \"E\" for easy mode, \"N\" for
            normal mode, \"H\" for hard mode, and \"C\" for custom mode''')

    def playerGuess():
        playerGuessWin = False
        printSlow('Ok! I\'m thinking of a number from 1 to 100')
        printSlow('You\'ll have ', guesses, ' guesses to get it.')
        printSlow('Go ahead and guess when you\'re ready.')
        min = 1
        max = 100
        playerGuessNum = random.randint(min,max)
        while playerGuessWin == False:
            currentGuess = input()
            if currentGuess.isnumeric() == True and int(currentGuess) <= 100 and int(currentGuess) >= 1:
                printSlow('Your guess is...')
                if currentGuess == playerGuessNum:
                    printSlow('CORRECT!')
                else:
                    printSlow('Not right :(')
            else:
                printSlow('I don\'t understand. Please enter a number from 1 to 100.')

    def guessTheNumberStart():
        printSlow('You want to play Guess The Number, right?')
        confirmChoice(input('(Please input \"yes\" or \"no\")'))
        if ynChoice == 'y':
            printSlow('Alright! Let\'s do it!')
        elif ynChoice == 'n':
            printSlow('Oh. Ok!')
            global runGTN
            runGTN = False #this is where it's supposed to stop the while loop
            return #I tried using "break" but it said that it wasn't inside a loop. It just continues on for some reason.
        printSlow('Do you want to guess my number, or should I guess yours?')
        printSlow('(If you want to guess, input \"I\". If I should guess, input \"U\".)')
        whoGuess = input()
        if whoGuess.lower() == 'i':
            printSlow('Ok, you guess!')
            askGuessDifficulty()
            playerGuess()
        elif whoGuess.lower() == 'u':
            printSlow('Ok, I\'ll guess!')
        else:
            printSlow('I don\'t understand. Please input \"I\" if you should guess and input \"U\" if I should guess.')
    guessTheNumberStart()

Hi,

you need to learn about namespace. The keyword global is not used on the top of a module.
You may delete the following line:

global runGTN

It is generally used inside of a function to signify that a particular variable’s scope extends outside of it. Why are you defining the functions inside of a while loop multiple times? You only need to define the functions once and then you may call them as many times depending on program needs/conditions.

I would suggest reviewing your code and determining what it is exactly that you want it to do and in what sequence.

Recommend the following regarding global:

Rules of global Keyword

The basic rules for global keyword in Python are:

  • When we create a variable inside a function, it is local by default.
  • When we define a variable outside of a function, it is global by default. You don’t have to use the global keyword.
  • We use the global keyword to read and write a global variable inside a function.
  • Use of the global keyword outside a function has no effect.

By the way, to answer your post header, to make the while loop to stop, generally a test condition needs to be met. In which case you may use the keyword break.

for example:

# Test for a predetermined value
counter = 0

while counter < 5:  # it will exit after the value 4
    print('counter = ', counter)
    counter += 1  # increment test variable

OR

# Run until a condition has been met:
counter = 0

while True:  # it will print values up to 9
    print('counter = ', counter)
    counter += 1  # increment test variable

    if counter == 10:
        break

In your code, determine which type is required.

You can, of course, have an infinite loop in your main program file to have your code continuously running.

Look at function confirmChoice:

def confirmChoice(ynChoice):
    if ynChoice.lower() == 'yes' or ynChoice.lower() == 'y':
        printSlow('Got it!')
        ynChoice = 'y'
    elif ynChoice.lower() == 'no' or ynChoice.lower() == 'n':
        printSlow('Oh. Ok!')
        ynChoice = 'n'
    else:
        printSlow('I don\'t understand. Please input \"yes\" or \"no\".')

It accepts a value that’s passed in as parameter ynChoice.

Parameters are local to the function, so binding a new value to it won’t affect any similarly-named variable that’s outside the function.

Therefore, this:

confirmChoice(input('You want easy mode, right? (Please input \"Yes\" or \"No\")'))

will ask for input and pass it to confirmChoice, which might change what its local name is bound to, then print a mesage, and finally return.

It will have no effect on the value of the global ynChoice that exists outside the function.

1 Like

Is there a way to make the parameter ynChoice global, or do I have to do it another way?

It is already global. You have defined it at the top of the module and per the second bullet point above:

  • When we define a variable outside of a function, it is global by default. You don’t have to use the global keyword.

Here is a simple example. Notice how I have assigned a variable outside of the function yet it is visible to the body of the function.

tester = 10

def some_func(x, y):

    print('Testing a global variable.')

    return tester * x * y

print(some_func(5,6))

Is defining functions inside a WHILE loop, or any loop, best practice? Because it makes it hard to read. I’ve never seen that before.

If defining functions inside a loop is done, does that make the function local to that loop?

The body inside of a loop should be the code that you are running. It generally isn’t for defining functions. That is kind of like assigning the same value to a variable over and over again.

Here is an example:

while True:

    def some_func(x, y):

        print('The sum is: ', x + y)

    counter += 1
    
    if counter == 4:
        break

some_func(10, 20)  # Outside of the loop and accessible

The function is accessible outside of the loop even if it is defined inside a loop. The point is that it should not be defined multiple times if it is not being modified in any way let alone multiple functions.
Not only is that redundant, but it adds latency to your script.

Thank you for all the feedback, as I’m very new to programming. I changed my code a little, but I’m getting a syntax error saying that my break is outside of a loop.

def guessTheNumberStart():
    printSlow('You want to play Guess The Number, right?')
    confirmChoice(input('(Please input \"yes\" or \"no\")'))
    if ynChoice == 'y':
        printSlow('Alright! Let\'s do it!')
    elif ynChoice == 'n':
        printSlow('Oh. Ok!')
        break #break, which is inside function, which is inside loop, so why doesn't it work?
    printSlow('Do you want to guess my number, or should I guess yours?')
    printSlow('(If you want to guess, input \"I\". If I should guess, input \"U\".)')
    whoGuess = input()
    if whoGuess.lower() == 'i':
        printSlow('Ok, you guess!')
        askGuessDifficulty()
        playerGuess()
    elif whoGuess.lower() == 'u':
        printSlow('Ok, I\'ll guess!')
    else:
        printSlow('I don\'t understand. Please input \"I\" if you should guess and input \"U\" if I should guess.')
while True: #loop
    guessTheNumberStart()

error:

File "/home/adamwellerfahy/minigames.py", line 150
    break
    ^^^^^
SyntaxError: 'break' outside loop
>>> ```

the break has to be used in a loop. Here, you are using inside of a conditional statement body.

Loops consists of while and for

Its called scope. The break is local (visible) only to the body of the function and not to the while loop in which the function resides.

If you want to jump out of the function for this condition, you can of course instead use:

return None

For example, practice with this test script:

test_value = 2

def some_func(h):

    if h == 2:
        print('h is 2')

    elif h == 4:
        return None

    elif h == 1:
        print('The value is 1.')

    print('The end of the function.')

print('Outside and after function.')

some_func(test_value)

The code inside a function is compiled once, ahead of time, before anything runs. The def statement creates the function object from mostly-pre-made pieces and assigns it to a name. The assignment follows the same scoping rules as any other assignment - loops don’t have their own scope.

There’s generally not a good reason do to this. Sometimes it’s for the purpose of “binding” arguments to a function that will be used as a callback, and making separate versions with differently bound arguments. For example, to handle button clicks in a GUI, binding separate information to each button’s callback, so it can do something appropriate depending on which button was clicked. However, this is trickier than it appears:

“inside” here is only talking about how the code is structured - not about what happens when you run it. The function is not indented, and doesn’t have the while line above it, therefore it isn’t inside the loop.

A function can be called from multiple places - that’s the point. Therefore, it doesn’t and shouldn’t normally care about where it was called from, and certainly can’t directly change the control flow of that other code. Instead, a function knows about the arguments that were passed to it (the things you put between () when you call it), which it refers to internally via its parameters (the things you put between () on the def line). That’s the primary way to get information in to the function. To get information out, the primary way is to return a value. That value represents the result of the function’s calculation. Then the outside code can examine that result to decide what to do.

The way to keep this organized is to make sure that functions have a single, clearly defined role - one thing that they calculate, and one meaning for the result of that calculation.

Try writing a function that only asks the user whether to play, and returns a value that indicates the answer. (Hint: what type of value makes sense to answer a yes-or-no question?) Then write another function to find out who should play first. (Hint: if the input here is bad, then we should prompt the user again, right? So, maybe the loop should really be inside this function… ?) Do not move on to askGuessDifficulty or playerGuess here - those are not part of how you find out who plays first. In the outside code, call those functions to find out the answers, and proceed accordingly.

Although the function is being called inside your while loop, the code
inside the function doesn’t know that.

The body of a function is self contained, and there’s no loop inside
the function. Hence the error - the break has no loop to break out of
there.

If you’re wanting to terminate the while loop early, that test needs to
be in the while loop code. Usually we’d examine the return value from
the function. For example you could have the function return value be
some kind of “final call” Boolean value - True when the caller should
stop calling the function again, and False otherwise.

So the while loop might look like this:

 while True:
     is_final = guessTheNumberStart()
     if is_final:
         break

Here, the break is inside the while loop’s body.

As an aside, you can simplify this if statement:

     if guessTheNumberStart():
         break

and because the loop itself does nothing but call the function, you can
write the loop like this:

 while not guessTheNumberStart():
     pass

i.e. it says “run while guessTheNumberStart(0 returns a false value” aka
“until the user guesses correctly”.

WRT the function, you’d put a return False at the bottom (the default
result) and a return True where you have the current break
statement:

 def guessTheNumberStart():
     printSlow('You want to play Guess The Number, right?')
     confirmChoice(input('(Please input \"yes\" or \"no\")'))
     if ynChoice == 'y':
         printSlow('Alright! Let\'s do it!')
     elif ynChoice == 'n':
         printSlow('Oh. Ok!')
         return True
     printSlow('Do you want to guess my number, or should I guess yours?')
     printSlow('(If you want to guess, input \"I\". If I should guess, input \"U\".)')
     whoGuess = input()
     if whoGuess.lower() == 'i':
         printSlow('Ok, you guess!')
         askGuessDifficulty()
         playerGuess()
     elif whoGuess.lower() == 'u':
         printSlow('Ok, I\'ll guess!')
     else:
         printSlow('I don\'t understand. Please input \"I\" if you should guess and input \"U\" if I should guess.')
     return False