How to Write a Text Adventure in Python Part 1: Items and Enemies

This is an abbreviated version of the book Make Your Own Python Text Adventure.

Typically, a text adventure game involves the player exploring and interacting with a world to tell a story. For this tutorial, I wanted the game to take place in a cave with the goal of escaping alive. You can of course use this idea too, but feel free to use your own idea! Almost all parts of this tutorial are interchangeable with your own custom elements. We’re going to start by defining some items and enemies.

Items

Start by creating a new directory called adventuretutorial and create a blank file called __init__.py. This tells the Python compiler that adventuretutorial is a Python package which contains modules. Go ahead and create your first module in this same directory called items.py.

The first class we are going to create is the Item class. When creating a class, consider the attributes that class should have. In general, it will be helpful for items to have a name and description. We can use these to give the player information about the item. Let’s also add a value attribute that will help players to compare items within an economy.

class Item():
    """The base class for all items"""
    def __init__(self, name, description, value):
        self.name = name
        self.description = description
        self.value = value

    def __str__(self):
        return "{}\n=====\n{}\nValue: {}\n".format(self.name, self.description, self.value)

This class has just two methods, but we’re going to see them pop up a lot. The __init__ method is the constructor and it is called whenever a new object is created. The __str__ method is usually a convenience for programmers as it allows us to print an object and see some useful information. Without a __str__ method, calling print() on an object will display something unhelpful like <__main__.Item object at 0x101f7d978>.

While we could create an Item directly, it wouldn’t be very interesting for the player because there is nothing special about it. Instead, we’re going to extend the Item class to define more specific items.

One of my favorite parts of games is finding treasure so let’s go ahead and create a Gold class.

class Gold(Item):
    def __init__(self, amt):
        self.amt = amt
        super().__init__(name="Gold",
                         description="A round coin with {} stamped on the front.".format(str(self.amt)),
                         value=self.amt)

The Gold class is now a subclass of the superclass Item. Another word for a subclass is child class and superclasses may be called parent or base classes.

The constructor here looks a little confusing but let’s break it down. First you’ll notice an additional parameter amt that defines the amount of this gold. Next, we call the superclass constructor using the super().__init__() syntax. The superclass constructor must always be called by a subclass constructor. If the constructors are exactly the same, Python will do it for us. However, if they are different, we have to explicitly call the superclass constructor. Note that this class doesn’t have a __str__ method. If a subclass doesn’t define its own __str__ method, the superclass method will be used in its place. That’s OK for the Gold class because the value is the same as the amount so there’s no reason to print out both attributes.

I mentioned earlier that this game is going to have weapons. We could extend Item again to make some weapons, but weapons all have something in general: damage. Whenever a lot of specific classes are going to share the same concept, it’s usually a good idea to store that shared concept in a superclass. To do this, we will extend Item into a Weapon class with a damage attribute and then extend Weapon to define some specific weapons in the game.

class Weapon(Item):
    def __init__(self, name, description, value, damage):
        self.damage = damage
        super().__init__(name, description, value)

    def __str__(self):
        return "{}\n=====\n{}\nValue: {}\nDamage: {}".format(self.name, self.description, self.value, self.damage)


class Rock(Weapon):
    def __init__(self):
        super().__init__(name="Rock",
                         description="A fist-sized rock, suitable for bludgeoning.",
                         value=0,
                         damage=5)


class Dagger(Weapon):
    def __init__(self):
        super().__init__(name="Dagger",
                         description="A small dagger with some rust. Somewhat more dangerous than a rock.",
                         value=10,
                         damage=10)

This should feel very familiar as we are following the same process of creating subclasses to define more specific elements in the game.

Enemies

Now that you’re an expert at extending objects, creating the enemies will be a breeze. Go ahead and create a new module called enemies.py.

Our base class Enemy should include a name, hit points, and damage the enemy does to the player. We’re also going to include a method that allows us to quickly check if the enemy is alive.

class Enemy:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage

    def is_alive(self):
        return self.hp > 0

Next, create a few subclasses of Enemy to define some specific enemies you want the player to encounter. Here are mine, but feel free to use your own ideas instead.

class GiantSpider(Enemy):
    def __init__(self):
        super().__init__(name="Giant Spider", hp=10, damage=2)


class Ogre(Enemy):
    def __init__(self):
        super().__init__(name="Ogre", hp=30, damage=15)

Notice that we don’t have to include is_alive() in the subclasses. This is because subclasses automatically get access to the methods in the superclass.

If you are completely new to programming, some of this may be confusing. My book Make Your Own Python Text Adventure is great for beginners because it does not assume any knowledge of programming concepts.

Click here for part 2

Tagged on: , , , ,

39 thoughts on “How to Write a Text Adventure in Python Part 1: Items and Enemies

  1. Ryan

    Hey Philip, what version of python did you make this on? I’m very new to python in general, but I keep getting syntax errors regarding colons and indents, but I’m writing them exactly as you have them. I’m wondering if it might be a translation issue with a different version of python (I’m using v.3.2.3), or Maybe it is too soon to run the module?

    1. Phillip

      Hi, Ryan! That version should be fine. If you run into too much trouble, try checking out the project from GitHub to compare to the code you’ve written. Also, at this point in the tutorial, there isn’t really anything to run.

  2. Tord Mathiassen

    Hi, i have a few questions. in the beginning you write that we should create a directory called adventuretutorial and two blank files called _init_.py and items.py. Can make the folder and files on my desktop? I’m a little bit confused on how I am going to use this.

  3. Joseph

    Uhm, in your code example for the enemy main class, you just said Enemy: and then went onto the next thing. Are you sure there should not be empty parenthesis before the colon?

  4. Cassidy

    When I try to run it in Terminal, its says

    Traceback (most recent call last):
    File “/Users/lumbo/Desktop/adventuretutorial/game.py”, line 3, in
    from player import Player
    File “/Users/lumbo/Desktop/adventuretutorial/player.py”, line 2, in
    import items, world
    File “/Users/lumbo/Desktop/adventuretutorial/items.py”, line 1, in
    class Gold(Item):
    NameError: name ‘Item’ is not defined

    1. Phillip Johnson

      Hmm, I’m not sure what the problem is. That probably means there is a typo or omission somewhere. You might want to compare your code against my code in the GitHub repository to make sure it is the same. Happy programming!

  5. Cassidy

    Okay, I fixed the:
    NameError: name ‘Item’ is not defined
    But now I have this:
    TypeError: super() takes at least 1 argument (0 given)
    Do I have to put something in between the parenthesis?

  6. Ryan

    Hey, I’m getting a TypeError when I run the code. Says __init__ takes 1 positional argument but 3 were given. Using Python 3.5, and would gladly use any assistance.

    class Weapons(Item):
        def __init__(self, name, description, attack):
            self.attack = attack
            super().__init__(name, description)
    
    class EnchantedLongsword(Weapons):
    	def __init__(self):
    		super().__init__(name="Enchanted Longsword", description="A prestine longsword you found with small runes inscribed along the Cross Guard. You feel a small magical force emanating from the weapon as you hold it.", attack = 12)
    
    1. Phillip Johnson Post author

      This error means that an __init__() function is not set up for three arguments, but you’re trying to pass that many arguments in. Without seeing the complete code, my guess would be there’s a problem in the Item class. Ignoring self, count your arguments in each initializer starting with your most-specific class and working your way up to the most general class.

  7. MJHauserman

    I keep getting
    AttributeError: “Gold” object has no attribute ‘description’

    class Gold(Item):
       def __init__(self, amt):
            self.amt = amt
            super().__init__(name="Gold",
                                        description="A round coin with {} stamped on the front.".format(str(self.amt)),
                                         value=self.amt
    
    1. Phillip Johnson Post author

      That looks OK to me, where are you getting that error? The Python stack trace should give you some more information. Compare your code where the error is against mine and make sure everything matches.

  8. Ben Finney

    A correction:

    The __init__ method is the constructor and it is called whenever a new object is created.

    The constructor for a Python class is named ‘__new__’; it is a class method that returns the new instance. Most beginners never need to know about the constructor and never need to use ‘__new__’.

    The ‘__init__’ method of an instance is not the constructor. It is the initialiser for the instance; it has access to the new instance (as ‘self’) and returns None. Beginners should learn this as the “initialiser” (not the constructor, which is different).

    Thank you for making this course!

  9. Andrew

    How would I go about making a Bandage item that appends the players health so the player has a way of gaining his/her health back

    1. Phillip Johnson Post author

      There’s quite a few steps involved, but in general, you need a Heal action that checks the player’s inventory for the bandaid. If a bandaid is present, then call a Heal function that increases the player HP, and remove the bandaid from the inventory. This is actually something I go into detail about in the book, if you’re interested.

  10. Kyle Mullaney

    I continually get the following error. The specifics change slightly, it seems, depending on which enemy is being assigned to an enemy tile. All throw name ‘enemies’ is not defined.

    Traceback (most recent call last):
      File "/Users/KylesAir/Documents/Coding/Python Learning/Text Adventure Book/ROOT/game.py", line 2, in 
        import world
      File "/Users/KylesAir/Documents/Coding/Python Learning/Text Adventure Book/ROOT/world.py", line 70, in 
        [EnemyTile(2,0),StartTile(2,1),EnemyTile(2,2)],
      File "/Users/KylesAir/Documents/Coding/Python Learning/Text Adventure Book/ROOT/world.py", line 27, in __init__
        self.enemy = enemies.Runner()
    NameError: name 'enemies' is not defined
    

    I figured out a pervious problem involving the import but non-evocation of the file Player.
    i.e. I had imported it but not defined it!

    Here I cannot find a similar problem. Any Suggestions?

    1. Phillip Johnson Post author

      Is it possible that you are missing the import enemies at the top of your file? That error means Python doesn’t know what “enemies” means. By using the import, you are telling Python that it is a package to access.

  11. Kelvin

    After create your items class, are the actual items objects in the class or are you defining them in a dictionary or something outside the class?

  12. Kelvin

    Another Question sorry, reading through the book, and im wondering is it necessary to create different classes of items or would be fine just creating instances of item with the specific name, descrip, and value. I’m making a dating based adventure game, and i cant see reason for a bunch of classes of items. Thanks

    1. Phillip Johnson Post author

      There’s no right or wrong answer to this question. It really depends on how you’re going to use the items in your code. In general, it’s nice to not repeat yourself. So if you know you are going to create a bunch of Cats in your code, you don’t want to retype the description of the Cat in lots of places, so you make a Cat class. It can also help with code readability to know something about the object based on its name.

      The more dissimilar your objects are, the more you should lean towards using distinct classes. The more similar your are objects are the more you should lean toward using a generic class with slightly different attributes for each instance.

  13. Chris Whittome

    I only have Python 2.7, will this only run on Python 3.5?
    I’m assuming the only difference I need to make is typing object: eg: class Item(object) ?

  14. DL

    Hi, thanks for the tutorial!

    I would like to include another property of weight (and also add some other damage properties)

    class Weapon(Item):
        def __init__(self, name, description, value, damage, damage2, weight):
            self.damage = damage
            self.damage2 = damage2
            self.weight = weight
            super().__init__(name, description, value)
    

    So here I am inheriting the name, desc and value from the parent Item, and then I am specifying the weapon-specific attributes (damage, damage2 and weight) that I would like all weapons to share.

    However I am not sure how to modify the below:

        def __str__(self):
            return "{}\n=====\n{}\nValue: {}\nDamage: {}".format(self.name, self.description, self.value, self.damage)
    

    I guess I would add (self.damage2, self.weight) to the bracketed text
    But I am not sure what everything in the “{}….{}” means or what the .format means. Could you help, please?

    Any help would be much appreciated 🙂

    Thank you again!

Leave a Reply

Your email address will not be published. Required fields are marked *