Ex 43 AttributeError: 'NoneType' object has no attribute 'enter'


#1

I’m working on ex43 and admittedly am just writing down the posted code as I don’t understand classes yet.

When I run the program, in most cases I end up with the following error:

Traceback (most recent call last):
      File "ex43.py", line 200, in <module>
        a_game.play()      
File "ex43.py", line 25, in play
        next_scene_name = current_scene.enter()
    AttributeError: 'NoneType' object has no attribute 'enter'`

Each class ends with a return to the scenes dict, which from what I understand based on this Reddit thread should be all that is required to avoid returning None. But it throws an error anyways.

What is it about a_game.play() and next_scene_name = current_scene.enter() that is not talking to the scenes dict?

Code here:

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):

    quips = [
        "You died.",
        "Ur ded.",
        "Game error. Your fault.",
        "Don't even bother trying again.",
        "I still don't understand the logic of this program. Bye."
    ]

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


class CentralCorridor(Scene):

    def enter(self):
        print(dedent("""
        This is the start of the game.
        Choose a number: 1, 2 or 3.
        """))

        action = input("What's your number?: ")

        if action == "1":
            print(dedent("""
            This choice was unsuccessful.
            Why did you think you're #1?
            Narcissist.
            """))
            return 'death'

        elif action == "2":
            print(dedent("""
                Unfortunately, you chose wrong.
                Now you're going to suffer the consequenses.
                Inconsequential.
                """))
            return 'death'

        elif action == "3":
            print(dedent("""
                You're lucky.
                Three is a solid number.
                You may contiue.
                """))
            return 'laser_weapon_armory'

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


class LaserWeaponArmory(Scene):

    def enter(self):
        print(dedent("""
            You now have to choose three digits.
            Choose correctly, you move on.
            Incorectly, you fail.
            You have 10 tries.
            """))

        code = f"{randint(1,9)}, {randint(1,9)}, {randint(1,9)}"
        guess = input("Choose now [keypad]: ")

        if guess == code:
            print(dedent("""
                You win!
                We move ahead.
                """))
            return 'the_bridge'

        else:
            print(dedent("""
                You fail.
                Unsurprising.
                """))
            return 'death'




class TheBridge(Scene):

    def enter(self):
        print(dedent("""Get ready. You're going to have to choose a number again.
            Choose one from 1 to 9.
            """))

        action = input("What number?: ")

        if action == "1":
            print(dedent("""
                No sorry, you're dead.
                """))

            return 'death'

        elif action == "5":
            print(dedent("""
                Lucky! You advance!
                """))

            return 'escape_pod'

        else:
            print("DOES NOT COMPUTE!")

            return 'the_bridge'


class EscapePod(Scene):

    def enter(self):
        print(dedent("""
        Now you need to pick a number from 1 to 5.
        """))

        good_pod = randint(1,5)
        guess = input("Go ahead [pod #]: ")

        if int(guess) != good_pod:
            print(dedent("""
            No. That's wrong.
            """))
            return 'death'

        else:
            print("You won!")
            return 'finished'

class Finished(Scene):

    def enter(self):
        print(dedent("""
        Good job.
        """))
        return 'finished'


class Map(object):

    scenes = {
        'central_corridor': CentralCorridor(),
        'laser_weapon_armory': LaserWeaponArmory(),
        'the_bridge': TheBridge(),
        'escape_pod': EscapePod(),
        '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('central_corridor')

a_game = Engine(a_map)

a_game.play()

#2

Hello @heavywind.

If you put your code in here there will be many eyes in here that can help you to find what causes this error. The reason can be small and simple. But very hard to spot.

Type in before your code:
”[code]”

Paste your code between these lines.

Finish with: ”[/code]”


#3

Thanks, @ulfen69. Edited original post to include code.


#4

Hello @heavywind, If you notice on page 206 in the book on your class ‘Death’ you also have the exit(1), which helps you to exit your game once you lose in the game.

class Death(Scene):

quips = [
    "You died.",
    "Ur ded.",
    "Game error. Your fault.",
    "Don't even bother trying again.",
    "I still don't understand the logic of this program. Bye."
]

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

on this specific part:

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

Also in your LaserWeaponArmory class:

code = f"{randint(1,9)}, {randint(1,9)}, {randint(1,9)}"

you would need to get rid of the commas that separate the random integers like this:

 code = f"{randint(1,9)}{randint(1,9)}{randint(1,9)}"

Hope this help!


#5

When you get this error it’s usually because you forgot a return in a function. So if you do this:

def badfunc():
    print("I'm going to return a value.")
    # notice no return here

x = badfunc()
x.dothing()

The reason this happens is the value you get from calling a function that does not return something is None. When you try to call a function on None you get this error. You fix it by returning:

def badfunc():
   print("I'm going to return.")
   return thing

x = badfunc()
x.dothat()

So, you have a room that has an enter() function that does not return. Just have to find out where that is.


#6

I am curious, why exit(1)? Why not exit(0) or exit(Death)?

For
code = f"{randint(1,9)}{randint(1,9)}{randint(1,9)}"
are the commas removed because then the user would have to enter x, x, x instead of xxx? Or is there another reason?

Thanks for your help, @zedshaw and @Alfredo13!


#7

Hello @heavywind,

This reference below gives you a very good basic answer to your question about exit(1) and exit(0):


exit(0) means a clean exit without any errors/problems

exit(1) means there was some issue/error/problem and that is why the program is exiting.


That is as far as I understand for these two function calls. I wouldn’t worry too much about the details as of right now

for the rest of the code on string formatting, try to run it with the commas and see what happens and then without the commas and also see what happens. Also, see the error messages and try to understand what is trying to tell you, but basically you got the right idea.

I had all these questions as well and right now know the very basic functionality of these as well.

Hope this help!