Adding return to while loop


#1

I am trying to incorporate the while-loop name game from here into my game for ex45. As part of this, I’ve been attempting to return scenes from within the while loop. Previously, I had defined a function outside name_game() and called that from within the loop, but had the same errors.

The traceback that is triggered on both a “failed” if or a “successful” else is:

Traceback (most recent call last):
  File "palace.py", line 465, in <module>
  File "palace.py", line 28, in play
    next_scene_name = current_scene.enter()
AttributeError: 'NoneType' object has no attribute 'enter'

I’m not sure what would be the ‘NoneType’ in this case.

The section that fails is:

class Ball(Scene):

    def enter(self):
        print(dedent('''
              If you succeed you will get the success message from the else statement and then a traceback error on the return function. If you fail you will get the fail statement from the if statement and a traceback on the return function.
              '''))

        def name_game():
            name = 'Shame'
            guess = input("What is the magician's name?: ")
            pos = 0

            while guess != name and pos < len(name):
                print("A simple task, yet you fail. Try again. A clue: letter ", end='')
                print(pos + 1, "is", name[pos] + ". ", end='')
                guess = input("Guess again: ")
                pos = pos + 1

            if pos == len(name) and name != guess:
                print("Too bad, you couldn't get it.  The name was", name + ".")
                return 'ghouls'

            else:
                print("\nYou have succeeded!")
                return 'woman'

        name_game()

The lines from the traceback are a direct copy of the Engine given in ex45:

class Engine(object):

    def __init__(self, scene_map):
        self.scene_map = scene_map

    def play(self):
        current_scene = self.scene_map.opening_scene()
        last_scene = self.scene_map.next_scene("finished")

        while current_scene != last_scene:
            next_scene_name = current_scene.enter()
            current_scene = self.scene_map.next_scene(next_scene_name)

        #be sure to print out the last Scene
        current_scene.enter()

Is there another way to return the next scene within the while loop without using a return? Does this require revising the main engine? Or making a second engine?


#2

How are you starting the Engine so to speak.
How does scene_map pass into your engine?
Is the ‘Ball’ object a part of that scene_map?
It sounds like the Ball object is not created, therefore is a NoneType and does not have the .enter ability.

Also if the indentation copied over like it is in your file, the name_game() is inside the Ball.enter() and while the name_game returns the next scene name, it is returning it into the Ball.enter() and Ball.enter is just running name_game, not returning a scene.


#3

The engine is started at the bottom of the code like this:

a_map = Map('palace')
a_game = Engine(a_map)
a_game.play()

The Engine and most of the code is taken directly from ex43 - not 45 as stated at the top of the post.

Yes, Ball is part of scene_map. Every other scene I have running using the enter(self) function has worked with scene_map.

I do not understand how to make a function where the next scene will be returned within a while loop. From what I understand, calling return in a while loop breaks the loop. This is the problem you’re describing with Ball.enter() not returning a scene. There do not seem to be examples online of returning a separate class from within a while loop - or descriptions in the docs - that I can understand. I’m curious to know if there is an alternative method that would work in this case.

This seems to have some good suggestions, but nesting the if-else statements isn’t working for me. Does that have something to do with trying to return a class?


#4

Edited:
The error says that the current_scene is a Nonetype and therefore has no enter (). It doesn’t exist. Somewhere your current_scene is being returned as None.
I really don’t think your loop is the issue.

My suggestion would be to build a two classes.

Make one call the other.
Get that to work.

Once you have that part figured out…
Try the while loop to call a class.

Hint:. A class is an object. It must be intitiated/ created before it can fully functional.

.enter is s function of a class that can not exist until the class does.

If your map has…
hallway = Hallway(some_parameters)
You call hallway, not Hallway.
Hallway is the diagram, hallway is the actual object built for the computer to use.

Hope that helps. I could help better seeing the whole code.

But you are absolutely right.
A return statement stops the loop.


#5

So, study how I was doing it, but what you’re having here is one of your enter() functions did not return. You need to print out the name of every enter function before this line happens, and then the one that prints out and causes this error is the one that doesn’t have a return. Go look inside that function and I bet you have an if-else- and inside that there’s no return. The function just drops off the end.

Another thing is, go to each .enter() you have and at the very bottom put this:

assert False, “THIS SHOULD NOT HAPPEN”

If you wrote it correctly then you should return from the .enter() before this. If you get this assert then that function is broken.


#6

@nellietobey, Code below. The Ball class successfully calls the Woman class as ‘woman’ without errors (here replaced with ‘win’, Win()). Is there another place to put return 'woman' in the while loop that will not break it? So far I have been unsuccessful calling it outside the while loop, or calling it in a nested function and then calling that nested function. The class Woman(Scene) is defined immediately after Ball(scene), and is listed on the map as 'woman': Woman().

@zedshaw, after pasting in assert False, “THIS SHOULD NOT HAPPEN” at the bottom of every enter function, everything runs fine until the user “succeeds” or fails:

Traceback (most recent call last):
  File "palace.py", line 487, in <module>
    a_game.play()
  File "palace.py", line 28, in play
    next_scene_name = current_scene.enter()
  File "palace.py", line 308, in enter
    assert False, "THIS SHOULD NOT HAPPEN"
AssertionError: THIS SHOULD NOT HAPPEN 

This is the same traceback as before, but with the addition of line 308 and the assert error. Line 308 is:
assert False, "THIS SHOULD NOT HAPPEN"

…confirming the previous traceback that the function is broken.

As mentioned above, the name_game while loop comes from the open book project, and I haven’t been able to find an equivalent in Learn Python the Hard Way. Are there any resources you, or someone else, could recommend for calling objects within a function that do not break a while loop? Is there an alternative method I have not considered? Googling “calling object from while loop python” hasn’t been helpful.

The traceback line in this simplified version is 151, not 308. Only text and extraneous scenes have been removed.

from sys import exit
from random import randint
from textwrap import dedent


class Scene(object):

    def enter(self):
        print("This scene is not yet implemented.")
        print("Subclass it an implement enter().")
        exit(1)

class Engine(object):

    def __init__(self, scene_map):
        self.scene_map = scene_map

    def play(self):
        current_scene = self.scene_map.opening_scene()
        last_scene = self.scene_map.next_scene("finished")

        while current_scene != last_scene:
            next_scene_name = current_scene.enter()
            current_scene = self.scene_map.next_scene(next_scene_name)

        #be sure to print out the last Scene
        current_scene.enter()


class Death(Scene):

    possible_ends = [
            '''End''',
            '''The End.''',
            '''Fin.'''
            ]

    def enter(self):
        print(Death.possible_ends[randint(0, len(self.possible_ends)-1)])
        exit(1)

        assert False, "THIS SHOULD NOT HAPPEN"


class Ghouls(Scene):
    def enter(self):
        print(dedent('''
            This is the Ghouls scene.
            '''))
        return 'death'

        assert False, "THIS SHOULD NOT HAPPEN"


class Win(Scene):
    def enter(self):
        print(dedent('''
            Here is where you win.
            '''))
        return 'finished'

        assert False, "THIS SHOULD NOT HAPPEN"


class Palace(Scene):

    def enter(self):
        print(dedent("""
            This is the Palace.
            """))

        action  = input("Type STAY: ")

        if action == "STAY":
            return 'court'

        else:
            print("Input error. Try again.")
            return 'palace'

        assert False, "THIS SHOULD NOT HAPPEN"


class Court(Scene):

    def enter(self):
        print(dedent("""
            You are in the Food Court.
            """))

        action = input("Type BOW: ")

        if action == "BOW":
                return 'bow'

        else:
                print("Incorrect use of language. Modify your words.")
                return 'palace'

        assert False, "THIS SHOULD NOT HAPPEN"


class Bow(Scene):

    def enter(self):
        print(dedent('''
            Type BALL.
            '''))

        choice = input("Which once do you choose?: ")

        if choice == "BALL":
            return 'ball'

        else:
            print("Modify your words.")
            return 'palace'

        assert False, "THIS SHOULD NOT HAPPEN"


class Ball(Scene):

    def enter(self):

        print(dedent('''

            THIS IS THE PROBLEM SCENE.
            '''))

        def name_game():
            name = 'Shame'
            guess = input("What is the magician's name?: ")
            pos = 0

            while guess != name and pos < len(name):
                print("A simple task, yet you fail. Try again. A clue: letter ", end='')
                print(pos + 1, "is", name[pos] + ". ", end='')
                guess = input("Guess again: ")
                pos = pos + 1

            if pos == len(name) and name != guess:
                print("Too bad, you couldn't get it.  The name was", name + ".")
                return 'ghouls'

            else:
                print("\nYou have succeeded!")
                return 'win'
        name_game()

        assert False, "THIS SHOULD NOT HAPPEN"


class Finished(Scene):

    def enter(self):
        print("The world is yours.")
        exit(1)

        assert False, "THIS SHOULD NOT HAPPEN"


class Map(object):

    scenes = {
        'palace': Palace(),
        'court': Court(),
        'bow': Bow(),
        'ball': Ball(),
        'ghouls': Ghouls(),
        'win': Win(),
        'death': Death(),
        'finished': Finished(),
    }

    def __init__(self, start_scene):
        self.start_scene = start_scene

    def next_scene(self, scene_name):
        val = Map.scenes.get(scene_name)
        return val

    def opening_scene(self):
        return self.next_scene(self.start_scene)


a_map = Map('palace')
a_game = Engine(a_map)
a_game.play()

#7

I played with your scene until I got it to work.
I marked all the changes I had to make.
Here’s a working version, I’ll mark all the problem spots, so you can see what the issues were.

class Ball():

	def enter(self):

		print("problem scene")

		def name_game():
			name = 'shame' #<-- Took capital S out
			guess = input("What is the magician's name?: ")
			pos = 0

			while pos < len(name): ###  <---  HERE
				print("A simple task, yet you fail. Try again. A clue: letter ", end='')
				print(pos + 1, "is", name[pos] + ". ", end='')
				pos += 1
				print("pos =", pos)
				print("len of name =", len(name))
				guess = guess # <--- HERE 
                               # the if else block needed more to work with.
				if pos > len(name): # <--- HERE
					print("Too bad, you couldn't get it.  The name was", name + ".")
					event = 'ghouls'
					return event
				elif guess == name and pos <= len(name):
					print("\nYou have succeeded!")
					event = 'woman'
					return event
				else:
					guess = input("guess again:") #<-- get new guess before starting loop
		event = name_game()
		print(event)
		return event  #<--- HERE   your original does NOT return a scene outside of the game



ball = Ball()
ball.enter()

#8

Change that assert to:

assert next_scene_name, 'Failed returning next scene from %r" % current_scene

Then, possible causes:

  1. Your scene is not returning as you expect. Time to start adding debug printouts from the top to the bottom printing every variable until you find out when it does that, or use the pdb https://docs.python.org/3/library/pdb.html
  2. You are returning a scene name, but it’s spelled wrong or not actually in the scenes map. Change next_scene to this:
   def next_scene(self, scene_name):
        val = Map.scenes.get(scene_name)
        assert val, "Invalid scene_name %r" % scene_name
        return val

Try those two, and basically you need to add debugging statements and print out variables to figure this out. YOU WILL NOT FIGURE THIS OUT STARING AT THE CODE. Write out lists of possible causes, add printing of variables at key points or use pdb, rule out your cuases, and look at other places than just where the error happens.


#9

After using the debugging techniques you suggested, it seems that the next_scene_name variable inside the Engine class was not being read by the name_game function in Ball(). This was also affected by current_scene not being called. I suspect it may also have something to do with how Map interacts as well. However, the assert print out you suggested does not indicate what the current scene is through %r. Full traceback reads:

 Traceback (most recent call last):
  File "palace_short.py", line 30, in <module>
    class Death(Scene):
  File "palace_short.py", line 42, in Death
    assert next_scene_name, 'Failed returning next scene from %r' % current_scene
NameError: name 'next_scene_name' is not defined

As ‘Death’ is the first scene after Engine I’m guessing that in this case it’s the assert that’s throwing the error.

Pdb gave me this traceback:

Traceback (most recent call last):
  File "palace_short.py", line 194, in <module>
  File "palace_short.py", line 25, in play
    next_scene_name = current_scene.enter()
  File "/anaconda3/lib/python3.6/bdb.py", line 54, in trace_dispatch
    return self.dispatch_exception(frame, arg)
  File "/anaconda3/lib/python3.6/bdb.py", line 110, in dispatch_exception
    if self.quitting: raise BdbQuit
bdb.BdbQuit

Confirming that it’s an issue with reading next_scene_name from the name_game function.

Based on this thread, I changed it to Engine.next_scene_name, and current_scene to Engine.current_scene. This throws no errors, but created an infinite loop stuck at the first scene in the map.

Am I on the right track with calling next_scene_name using the Engine class label? Is there some kind of equivalent to …/ for variables/objects inside classes? Or should I keep looking for a different approach?

I really appreciate all of the help from you and @nellietobey. Hopefully this thread will be useful to other people trying to call objects using return within a while loop!


#10

Thanks for this! I tried your modifications, but inside the larger game it delivers all of the possible if/elif/else options in sequence. The output of a successful input looks like:

THIS IS THE PROBLEM SCENE.

What is the magician's name?: Shame
A simple task, yet you fail. Try again. A clue: letter 1 is S. 
You have succeeded!
Traceback (most recent call last):
  File "palace_short_a.py", line 181, in <module>
    a_game.play()
  File "palace_short_a.py", line 24, in play
    current_scene = self.scene_map.next_scene(next_scene_name)
  File "palace_short_a.py", line 172, in next_scene
    assert val, "Invalid scene_name %r" % scene_name
AssertionError: Invalid scene_name 'event'

While unsuccessful inputs result in endless prompts for new inputs.

It may be an issue with the Engine and Map classes talking to each other and to the Ball class. See below for more details.


#11

Hey, read your errors very carefully. Take a look:

NameError: name 'next_scene_name' is not defined

That means you’re doing an assert but at the wrong place. Where’s the next_scene_name variable. You have to think a little more clearly and not really just copy paste what I write. If you get an error, read EVERY line, very carefully and assign it to one of your lines in your code. Basically you tossed that line in, got a new error because it’s in the wrong place, then assumed that was because of your old error.

Next up:

AssertionError: Invalid scene_name 'event'

Look at that. Do you have an ‘event’ scene in your list of scenes? That looks like you’ve got some generic return doing:

return ‘event’

And then forgot to turn it into a real room event.

Here’s another thing I’m pretty sure you did: Did you write all this code and run it only a few times? Or, did you do what I recommend and run the code repeatedly at every tiny code change to make sure it keeps working? If you wrote the code in one big chunk then try deleting pieces of code until the problem goes away, then add it back to cause the problem. Here’s what I mean:

  1. You gut the enter() function of Ball. The problem still exists. It’s not in Ball.enter, so put the code back.
  2. You gut the code for another room’s .enter. The problem goes away, it’s in this function.
  3. You gut the first part of the if/else and the problem is still there. Put the code back, it’s not in the if.
  4. You gut the else, the problem goes away, it’s in that else. Look in there.

DO NOT JUST BLINDLY DO THIS. I am only making a suggestion on how to approach this, not exact instructions step by step. You’ll have to do this carefully, and I recommend making a backup of your code so you can start over.

Finally, try that and if by tomorrow you can’t find it then put your code on github.com so I can get it and I’ll do a video where I find the bug for you.


#12
def enter():
   def name_game():

Wait a minute, why do you have this function defined inside the enter() function? And then why do you call this function at the end to get the name of the thing? Get rid of this name_game thing and just play it straight. A simple enter() function that returns the next game. In fact, I want you to delete the code for this, have Ball.enter() return one event that works, and then slowly build this up without using a nested function. I totally missed that part.


#13

:sweat_smile:

Yes. The nested function. I should have paid more attention to it myself and titled this post “Adding return to nested while loop”.

The answer is don’t, just break them into two separate classes.

Working code below, for anyone interested.

from sys import exit
from random import randint
from textwrap import dedent


class Scene(object):

    def enter(self):
        print("This scene is not yet implemented.")
        print("Subclass it an implement enter().")
        exit(1)

class Engine(object):

    def __init__(self, scene_map):
        self.scene_map = scene_map

    def play(self):
        current_scene = self.scene_map.opening_scene()
        last_scene = self.scene_map.next_scene("finished")

        while current_scene != last_scene:
            next_scene_name = current_scene.enter()
            current_scene = self.scene_map.next_scene(next_scene_name)

        #be sure to print out the last Scene
        current_scene.enter()


class Death(Scene):

    possible_ends = [
            '''End''',
            '''The End.''',
            '''Fin.'''
            ]

    def enter(self):
        print(Death.possible_ends[randint(0, len(self.possible_ends)-1)])
        exit(1)


class Ghouls(Scene):
    def enter(self):
        print(dedent('''
            This is the Ghouls scene.
            '''))
        return 'death'


class Win(Scene):
    def enter(self):
        print(dedent('''
            Here is where you win.
            '''))
        return 'finished'


class Palace(Scene):

    def enter(self):
        print(dedent("""
            This is the Palace.
            """))

        action  = input("Type STAY: ")

        if action == "STAY":
            return 'court'

        else:
            print("Input error. Try again.")
            return 'palace'


class Court(Scene):

    def enter(self):
        print(dedent("""
            You are in the Food Court.
            """))

        action = input("Type BOW: ")

        if action == "BOW":
                return 'bow'

        else:
                print("Incorrect use of language. Modify your words.")
                return 'palace'


class Bow(Scene):

    def enter(self):
        print(dedent('''
            Type BALL.
            '''))

        choice = input("Which once do you choose?: ")

        if choice == "BALL":
            return 'ball'

        else:
            print("Modify your words.")
            return 'palace'


class Ball(Scene):

    def enter(self):

        print(dedent('''

            THIS SCENE IS NO LONGER A PROBLEM.
            '''))

        return 'name_game' # <-- while-loop moved to new 'name_game' scene


# name_game() function moved to a dedicated class.
class NameGame(Scene):
    def enter(self):

        name = 'Shame'
        guess = input("What is the magician's name?: ")
        pos = 0

        while guess != name and pos < len(name):
            print("A simple task, yet you fail. Try again. A clue: letter ", end='')
            print(pos + 1, "is", name[pos] + ". ", end='')
            guess = input("Guess again: ")
            pos = pos + 1

        if pos == len(name) and name != guess:
            print("Too bad, you couldn't get it.  The name was", name + ".")
            return 'ghouls'

        else:
            print("\nYou have succeeded!")
            return 'win'


class Finished(Scene):

    def enter(self):
        print("The world is yours.")
        exit(1)


class Map(object):

    scenes = {
        'palace': Palace(),
        'court': Court(),
        'bow': Bow(),
        'ball': Ball(),
        'name_game': NameGame(),
        'ghouls': Ghouls(),
        'win': Win(),
        'death': Death(),
        'finished': Finished(),
    }

    def __init__(self, start_scene):
        self.start_scene = start_scene

    def next_scene(self, scene_name):
        val = Map.scenes.get(scene_name)
        assert val, "Invalid scene_name %r" % scene_name
        return val

    def opening_scene(self):
        return self.next_scene(self.start_scene)
        assert next_scene_name, 'Failed returning next scene from %r' % current_scene


a_map = Map('palace')
a_game = Engine(a_map)
a_game.play()

#14

Boom! I should have seen that when you posted it but just glanced my eyes over it. That turns out to be a common thing beginners try and I can’t figure out where they’re getting it from. Where did you think to try that?


#15

Probably laziness. The if/elif/else options in ex43’s CentralCorridor all had return 'someroom' at the end (see code below). So instead of making scene_b return name_game as a separate scene, the name_game’s while loop function was nested inside the enter function’s if/else statements.

Reference code:

class CentralCorridor(Scene):

    def enter(self):
        print("Intro text")

        action = input("> ")

        if action == "A":
            print("A text.")
            return 'scene_a'

        elif action == "B":
            print("B text.")
            return 'scene_b'

        else:
            print("DOES NOT COMPUTE!")
            return 'central_corridor' 

Incorrectly modified code:

 class CentralCorridor(Scene):

        def enter(self):
            print("Intro text")
            action = input("> ")

            if action == "A":
                print("A text.")
                return 'scene_a'

                else action == "B":
                    print("B text.")

                def nested_function():

                    while True:
                         print("True")
                         return 'scene_b'                    

                    if action == "A":
                        print("A text.")
                        return 'scene_a'

                    else action == "B":
                        print("B text.")
                        return 'scene_b'

            else:
                print("DOES NOT COMPUTE!")
                return 'central_corridor'

#16

So strange as I see about 1/10 students try that and honestly it should be an error but for various reasons it’s valid Python. Anyway, lesson learned.