Exercise 42: Getting Classy

While it's fun to put functions inside of dictionaries, you'd think there'd be something in Python that does this for you. There is and it's the class keyword. Using class is how you create an even more awesome "dict with functions" than the one you made in the last exercise. Classes have all sorts of powerful features and uses that I could never go into in this book. Instead, you will just use them like they are fancy dictionaries with functions.

A programming language that uses classes is called an "Object Oriented Programming". This is an old style of programming where you make "things" and you "tell" those things to do work. You have been doing a lot of this. A whole lot. You just didn't know it. Remember when you were doing this:

stuff = ['Test', 'This', 'Out']
print ' '.join(stuff)

You were actually using classes. The variable stuff is actually a list class. The ' '.join(stuff) is calling the join function of the string ' ' (just an empty space) is also a class, a string class. It's all classes!

Well, and objects, but let's just skip that word for now. You will learn what those are after you make some classes. How do you make classes? Very similar to how you made the ROOMS dict, but easier:

class TheThing(object):

    def __init__(self):
        self.number = 0

    def some_function(self):
        print "I got called."

    def add_me_up(self, more):
        self.number += more
        return self.number

# two different things
a = TheThing()
b = TheThing()

a.some_function()
b.some_function()

print a.add_me_up(20)
print a.add_me_up(20)
print b.add_me_up(30)
print b.add_me_up(30)

print a.number
print b.number

Warning

Alright, this is where you start learning about "warts". Python is an old language with lots of really ugly obnoxious pieces that were bad decisions. To cover up these bad decisions they make new bad decisions and then yell at people to adopt the new bad decisions. The phrase class TheThing(object) is an example of a bad decision. I won't get into it right here, but you shouldn't worry about why your class has to have (object) after its name, just type it this way all the time or other Python programmers will yell at you. We'll get into why later.

You see that self in the parameters? You know what that is? That's right, it's the "extra" parameter that Python creates so you can type a.some_function() and then it will translate that to really be some_function(a). Why use self? Your function has no idea what you are calling any one "instance" of TheThing or another, you just use a generic name self. That way you can write your function and it will always work.

You could actually use another name rather than self but then every Python programmer on the planet would hate you, so do not. Only jerks change things like that and I taught you better. Be nice to people who have to read what you write because ten years later all code is horrible.

Next, see the __init__ function? That is how you setup a Python class with internal variables. You can set them on self with the . (period) just like I will show you here. See also how we then use this in add_me_up() later which lets you add to the self.number you created. Later you can see how we use this to add to our number and print it.

Classes are very powerful, so you should go read about them. Read everything you can and play with them. You actually know how to use them, you just have to try it. In fact, I want to go play some guitar right now so I'm not going to give you an exercise to type. You are going to go write an exercise using classes.

Here's how we would do exercise 41 using classes instead of the thing we created:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from sys import exit
from random import randint

class Game(object):

    def __init__(self, start):
        self.quips = [
            "You died.  You kinda suck at this.",
             "Your mom would be proud. If she were smarter.",
             "Such a luser.",
             "I have a small puppy that's better at this."
        ]
        self.start = start

    def play(self):
        next = self.start

        while True:
            print "\n--------"
            room = getattr(self, next)
            next = room()


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


    def princess_lives_here(self):
        print "You see a beautiful Princess with a shiny crown."
        print "She offers you some cake."

        eat_it = raw_input("> ")

        if eat_it == "eat it":
            print "You explode like a pinata full of frogs."
            print "The Princess cackles and eats the frogs. Yum!"
            return 'death'

        elif eat_it == "do not eat it":
            print "She throws the cake at you and it cuts off your head."
            print "The last thing you see is her munching on your torso. Yum!"
            return 'death'

        elif eat_it == "make her eat it":
            print "The Princess screams as you cram the cake in her mouth."
            print "Then she smiles and cries and thanks you for saving her."
            print "She points to a tiny door and says, 'The Koi needs cake too.'"
            print "She gives you the very last bit of cake and shoves you in."
            return 'gold_koi_pond'

        else:
            print "The princess looks at you confused and just points at the cake."
            return 'princess_lives_here'

    def gold_koi_pond(self):
        print "There is a garden with a koi pond in the center."
        print "You walk close and see a massive fin poke out."
        print "You peek in and a creepy looking huge Koi stares at you."
        print "It opens its mouth waiting for food."
        
        feed_it = raw_input("> ")

        if feed_it == "feed it":
            print "The Koi jumps up, and rather than eating the cake, eats your arm."
            print "You fall in and the Koi shrugs than eats you."
            print "You are then pooped out sometime later."
            return 'death'

        elif feed_it == "do not feed it":
            print "The Koi grimaces, then thrashes around for a second."
            print "It rushes to the other end of the pond, braces against the wall..."
            print "then it *lunges* out of the water, up in the air and over your"
            print "entire body, cake and all."
            print "You are then pooped out a week later."
            return 'death'

        elif feed_it == "throw it in":
            print "The Koi wiggles, then leaps into the air to eat the cake."
            print "You can see it's happy, it then grunts, thrashes..."
            print "and finally rolls over and poops a magic diamond into the air"
            print "at your feet."

            return 'bear_with_sword'

        else:
            print "The Koi gets annoyed and wiggles a bit."
            return 'gold_koi_pond'


    def bear_with_sword(self):
        print "Puzzled, you are about to pick up the fish poop diamond when"
        print "a bear bearing a load bearing sword walks in."
        print '"Hey! That\' my diamond! Where\'d you get that!?"'
        print "It holds its paw out and looks at you."
        
        give_it = raw_input("> ")

        if give_it == "give it":
            print "The bear swipes at your hand to grab the diamond and"
            print "rips your hand off in the process.  It then looks at"
            print 'your bloody stump and says, "Oh crap, sorry about that."'
            print "It tries to put your hand back on, but you collapse."
            print "The last thing you see is the bear shrug and eat you."
            return 'death'

        elif give_it == "say no":
            print "The bear looks shocked.  Nobody ever told a bear"
            print "with a broadsword 'no'.  It asks, "
            print '"Is it because it\'s not a katana?  I could go get one!"'
            print "It then runs off and now you notice a big iron gate."
            print '"Where the hell did that come from?" You say.'

            return 'big_iron_gate'

    def big_iron_gate(self):
        print "You walk up to the big iron gate and see there's a handle."

        open_it = raw_input("> ")

        if open_it == 'open it':
            print "You open it and you are free!"
            print "There are mountains.  And berries! And..."
            print "Oh, but then the bear comes with his katana and stabs you."
            print '"Who\'s laughing now!?  Love this katana."'

            return 'death'

        else:
            print "That doesn't seem sensible.  I mean, the door's right there."
            return 'big_iron_gate'


a_game = Game("princess_lives_here")
a_game.play()

What You Should See

The output from this version of the game should be exactly the same as the previous version, and in fact you will notice that some of the code is nearly the same. Compare this new version of the game and with the last one so you understand the changes that were made. Key things to really get are:

  1. How you made a class Game(object) and put functions inside it.
  2. How __init__ is a special intialization method that sets up important variables.
  3. How you added functions to the class by indenting them so they were deeper under the class keyword. This is important so study carefully how indentation creates the class structure.
  4. How you indented again to put the contents of the functions under their names.
  5. How colons are being used.
  6. The concept of self and how it's used in __init__, play, and death.
  7. Go find out what getattr does inside play so that you understand what's going on with the operation of play. In fact, try doing this by hand inside Python to really get it.
  8. How a Game was created at the end and then told to play() and how that got everything started.

Extra Credit

  1. Go find out what the __dict__ is and figure out how to get at it.
  2. Try adding some rooms to make sure you know how to work with a class.
  3. Create a two-class version of this, where one is the Map and the other is the Engine. Hint: play goes in the Engine.