How to Write a Text Adventure in Python Part 4: The Game Loop

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

The end is near, we’re almost ready to play the game! We’ll finish this series by implementing the game loop and receiving input from the human player.

The Game Loop

While some applications follow a discrete set of steps and terminate, a game typically just “keeps going”. The only way the program stops is if the player wins, loses, or quits. To handle this behavior, games usually run inside a loop. On each iteration, the game state is updated and input is received from the human player. In graphical games, the loop runs many times per second. Since we don’t need to continually refresh the player’s screen for a text game, our code will actually pause until the player provides input. Our game loop is going to reside in a new module game.py.

import world
from player import Player

def play():
    world.load_tiles() player = Player()
    while player.is_alive() and not player.victory:
        #Loop begins here

Before play begins, we load our world from the text file and create a new Player object. Next, we begin the loop. Note the two conditions we check: if the player is alive and if victory has not been achieved. For this game, the only way to lose is by dying. However, there isn’t any code yet that lets the player win. In my story, I want the player to escape the cave alive. If they do that, they win. To implement this behavior, we’re going to add a very simple room and place it into our world. Switch back to tiles.py and add this class:

class LeaveCaveRoom(MapTile):
    def intro_text(self):
        return """
        You see a bright light in the distance...
        ... it grows as you get closer! It's sunlight!
        
        Victory is yours!
        """

    def modify_player(self, player):
        player.victory = True

Don’t forget to include one of these rooms somewhere in your map.txt file. Now that the player can win, let’s finish the game loop.

def play():
    world.load_tiles()
    player = Player()
    #These lines load the starting room and display the text
    room = world.tile_exists(player.location_x, player.location_y)
    print(room.intro_text())
    while player.is_alive() and not player.victory:
        room = world.tile_exists(player.location_x, player.location_y)
        room.modify_player(player)
        # Check again since the room could have changed the player's state
        if player.is_alive() and not player.victory:
            print("Choose an action:\n")
            available_actions = room.available_actions()
            for action in available_actions:
                print(action)
            action_input = input('Action: ')
            for action in available_actions:
                if action_input == action.hotkey:
                    player.do_action(action, **action.kwargs)
                    break

The first thing the loop does is find out what room the player is in and then executes the behavior for that room. If the player is alive and they have not won after the behavior executes, we prompt the human player for input. This is done using the built-in input() function. If the human player provided a matching hotkey, then we execute the associated action using the do_action method.

The last thing we need to include is an instruction for Python to know that play() should run when running the file. Include these lines at the bottom of the game.py module:

if __name__ == "__main__":
    play()

To run the program, navigate to the folder containing the adventuretutorial package in your console and run python adventuretutorial/game.py. If you get warnings about packages, try setting your PYTHONPATH environment variable manually. Have fun!

Where to go from here

Congratulations! You now have a working text adventure game. With the information learned here, you should be able to quickly add your own custom items, enemies, and tiles. If you’re up for more of a challenge, here are some of the features included in Make Your Own Python Text Adventure:

  • An easier and more flexible way to build your world (no text files or reflection!)
  • A game economy where the player can buy and sell items
  • The ability for players to heal during and between fights
  • Difficulty settings to make the game harder or easier
Tagged on: , , , ,

134 thoughts on “How to Write a Text Adventure in Python Part 4: The Game Loop

  1. Damon Jenkins

    Hi Phillip. I have gone through the steps, and when I tried to launch game.py, cmd said “Import Error: No module named ‘adventuretutorial’. I have Googled for a while to solve this but I couldn’t find anything. Would you be willing and/or able to help me out?

    1. Phillip

      Please see the section above:

      To run the program, navigate to the folder containing the adventuretutorial package in your console and run python adventuretutorial/game.py. If you get warnings about packages, try setting your PYTHONPATH environment variable manually. Have fun!

      Happy programming!

      1. Joon0624

        if i try to run the programs its says it had an error. How do i fix this?

        Traceback (most recent call last):
        File "/game.py", line 2, in 
        from player import Player
        File "/player.py", line 44 
        if i damage > max_dmg:
        

        syntaxError: invalid syntax

      2. Alex

        Hi Phillip. I have three problems:
        do_action is an unresolved attribute, and world is too. I have tried to use ‘import world’, and my computer says there is no such package. Can you help me?

  2. ian

    i have everything set up the same way it is in the final product, however when i try to run the program, it brings up an error and says that there is no module named adventuretutorial. It says the same thing when i try to run even the program that is provided at GitHub.
    How do i fix this?
    I am using windows 8 and python 2.7.9

    1. ian

      So i guess my problem is the same as the other persons, but my question is how do i set my PYTHONPATH environment variable manually? i know how to get to the add environment variables screen, but after that i am lost

  3. Bart

    AttributeError: ‘module’ object has no attribute ‘OgreRoom

    it throws an error at the first place in map.txt where i decided was gonna be a wall.
    example:
    GiantSpiderRoom FindDaggerRoom FindGoldRoom FindGoldRoom OgreRoom
    OgreRoom EmptyCavePath GiantRatRoom EmptyCavePath FindDaggerRoom

    that is the first 2 rows. i made a 12×5 grid in a spreadsheet and copy pasted. I even checked the map.txt on github and it throws an error the same way for that map.txt file.
    it reads the first line of rooms and throws an error when it tries to drop down to the 2nd row.
    I’m using Python 3x

  4. Mika

    Hello. My game now works, and I am trying to make a new action “run”. Main purpose of it is to run in random direction. So my code looks like this:
    def run(self):
    direction = random.randint(0,3)
    if direction == 0:
    MoveEast()
    elif direction == 1:
    MoveWest()
    elif direction == 2:
    MoveSouth()
    elif direction == 3:
    MoveNorth()
    and I didn’t forget to make a class. When I run the game, and I use my “run” command, nothing happens. It doesn’t give me error, it just continues, and asks me for another action.

  5. Jeff

    For some reason when I run the main loop, it doesn’t print the intro text to the starting room. If I move east then west it prints the intro text. Any suggestions for what’s missing?

    1. Jerry Cui

      the line to print the intro text must be before the while loop. in your code, the intro text is inside the loop, so it will always be printed out.

  6. Chris

    Hi Phillip,
    Thank you for this series. I worked through this with the students in my Python class. Everything works great! However, when the game starts it shows the actions available but does not show the intro_text for the StartingRoom. Can you tell me if there is a simple way to fix this? Thank you!

    1. Phillip

      Hey, nice find! I’ve put in a fix for this on GitHub and in the post. Basically, we just manually print the intro text since the player isn’t going to “move into” that room at the beginning.

  7. Sinfulstyle

    Hey there, I’ve set everything up the way you described. but I’m getting an Error. No such file or directory: ‘resources/map.txt’. I’m running Ubuntu is that matters. I created the file exactly as you had in libre calc and copied everything minus the snake pit and changed the name of the CaveExit. Selected all, copied and pasted into a file I had made called map.txt, which is inside the resources folder, which is inside the game folder. It’s probably something stupid, I know, but I am quite new and would appreciate any help._world = {}

    def tile_exists(x, y):
    return world.get((x, y))

    def load_tiles():
    """Parses a file that describes the world space into the _world object"""
    with open('resources/map.txt', 'r') as f:
    rows = f.readlines()
    x_max = len(rows[0].split('\t')) # Assumes all rows contain the same number of tabs
    for y in range(len(rows)):
    cols = rows[y].split('\t')
    for x in range(x_max):
    tile_name = cols[x].replace('\n', '')
    world[(x, y)] = None if tile_name == '' else getattr(__import__('tiles'), tile_name)

    Full error:
    File “adventure.py/game.py”, line 23, in
    play()
    File “adventure.py/game.py”, line 5, in play
    world.load_tiles()
    File “/home/sinfulstyle/adventure.py/world.py”, line 8, in load_tiles
    with open(‘resources/map.txt’, ‘r’) as f:
    FileNotFoundError: [Errno 2] No such file or directory: ‘resources/map.txt’

    1. Phillip

      Glad you could fix it. Another option is to execute your code from the project root folder. As per the post, “To run the program, navigate to the folder containing the adventuretutorial package in your console and run python adventuretutorial/game.py.”

      If you execute your code from a different location, you’ll need to adjust the path to the resources folder, as you did.

      Happy coding!

      1. Sinfulstyle

        Wow! You are fast and active. Thank you very much, sorry I didn’t really understand what you meant by that. I did read it all and manually typed everything so I’m working through a butt load of errors. Haha figured it’s good practice though. I know I saw the answer to this question earlier but I hadn’t gotten that far and I can’t seem to find a way to navigate previous comments. Anyways if you could help me with this error:
        File “/home/sinfulstyle/adventure.py/world.py”, line 14, in load_tiles
        tile_name = cols[x].replace(‘\n’, ”)
        IndexError: list index out of range

        I’d be extremely grateful. I honestly have no idea where to start.

        1. Sinfulstyle

          again fixed that, i thought I had seen someone say something about not having the tabs the same, so I spaced out the rest of the text so it was all the same length on the x axis. Now I’m getting this:
          _world[(x, y)] = None if tile_name == ” else getattr(__import__(’tiles’), tile_name)(x, y)
          AttributeError: ‘module’ object has no attribute ‘

  8. Sinfulstyle

    For some reason I’m having a huge problem with the map.txt file. I either get list index is out of range or the module objects has no attribute. Can you elaborate on the text file creation? I’ve tried everything from making it in libre and copying it into a text file, making sure to keep all of the tabs are inserted. To copying your raw map.txt from github. Changed all of my rooms so they matched yours. Pulling hairs hear man. Haha.

      1. Jason

        I too am having the _world[(x, y)] = None if tile_name == ” else getattr(__import__(’tiles’), tile_name)(x, y)
        AttributeError: ‘module’ object has no attribute ‘error. I read the answers on part 2, and still am not understanding. I tried copying and pasting the map.txt file, that didn’t work.
        I have played around with the file, in notepad, as well as open office spreadsheet, and everytime I try it, it just gives me that error, the only thing that has changed is the part where it says no attribute ” … the ” switches to whatever I have first in the text file.
        I am clueless….

        1. Phillip Johnson Post author

          There’s a difference between '('module' object has no attribute '') and '('module' object has no attribute 'OgreRoom'). The former has been discussed extensively in the comments of Part 2. If you’re getting the second error, then it is probably because Python cannot find all of your code at runtime. Make sure you are in the correct directory when you run the code and that you are running it from a terminal/command line. If all else fails, you may need to adjust your PYTHONPATH, but that’s probably the solution to another more systemic problem.

  9. Elli

    First of all, thank you for this great guide!

    I am having problem with my program at the moment. Game starts fine, but…

    You find yourself in a cave with a flickering torch on the wall.
    You can make out four paths, each equally as dark and foreboding.

    Choose an action:

    a: Move west
    i: View Inventory
    Action:

    Only actions the player is offered in any room are “Move west” and “View Inventory” (I have set my movements on wasd: w is north, a is west, s is south, d is east, e is attack). I can only move west along the path: Starting Room -> Empty Room -> Giant Spider Room. Spider attacks the player on Spider Room, but still the only actions are “Move west” and “View Inventory” (no attack or flee). If I move west from Spider Room, I get errors, as there is not intended to be a room after Giant Spider Room in that direction. If I use “d” (east), Spider just attacks the player again.

    I have no clue why this is happening, or why “west” is the only option. I’m not sure what parts of my code to copy paste to show, even.

    This is what I have on my actions.py:

    [Mod edit: Removed for brevity]

    and this is what I have on tiles.py on class MapTile:

    class MapTile:
        def __init__(self, x, y):
            self.x = y
            self.y = y
    
        def intro_text(self):
            raise NotImplementedError()
    
        def modify_player(self, player):
            raise NotImplementedError()
    
        def adjacent_moves(self):
            """Returns all move actions for adjacent tiles."""
            moves = []
            if world.tile_exists(self.x + 1, self.y):
                moves.append(actions.MoveEast())
            if world.tile_exists(self.x - 1, self.y):
                moves.append(actions.MoveWest())
            if world.tile_exists(self.x, self.y - 1):
                moves.append(actions.MoveNorth())
            if world.tile_exists(self.x, self.y + 1):
                moves.append(actions.MoveSouth())
            return moves
    
        def available_actions(self):
            """Returns all of the available actions in this room."""
            moves = self.adjacent_moves()
            moves.append(actions.ViewInventory())
    
            return moves
    
    1. Phillip

      Oh, wow, that’s a nasty bug! There’s a typo here:

      class MapTile:
          def __init__(self, x, y):
              self.x = y
              self.y = y
      

      You are setting both the x and y coordinates equal to the y-value. Change it to self.x = x and hopefully that fixes it!

      1. Elli

        Thank you, I completely missed that very silly typo there! Changing that one fixed the movement; all correct options are now presented in every room. Spiders are still unattackable (they cause damage, but player can just continue to the next room, no different options from regular rooms like attack or flee), but I’ll go through my code with a microscope and try to find the cause for that 🙂 Must be similar “too obvious” typo that I have missed somewhere else.

        1. Elli

          Just wanted to give an update, sorry for spam – I found my last couple of typos. I had “def available_action(self):”, not “def available_actions(self):” under class EnemyRoom, which caused the code to always return self.adjacent_moves() and never give attack or flee options, d’oh. Code is working perfectly now! 🙂 Thank you for the tutorial and quick help!

  10. Tommy Collinson

    I have completed and tried to to run the game and this “AttributeError: ‘Player’ object has no attribute ‘is_alive'”
    def play():
    world.load_tiles()
    player = Player()
    while player.is_alive() and not player.victory:

    That is the problem area, I have no idea what this is as i am extremely new to python

    1. Phillip

      It’s difficult to say what the problem is specifically, but I would review your Player class against what I have in Part 3 of the tutorial (or GitHub). It sounds like you may have just missed adding the is_alive function.

  11. Michael

    How would you go about adding an economy to the game? Any tips or codes I could try?
    I wouldn’t even know how to make it stored total amount gold rather than individual collected coins.
    Please help.

  12. Tommy Collinson

    Thank you for your help on my last question, but now I have another.
    I decide to add a way to regain health by eating food. I created a subclass in the item class and the code runs but when I run it instead of removing the food it adds more.
    def eat(self):
    best_food = None
    max_hp = 0
    for i in self.inventory:
    if isinstance(i, items.Food):
    if i.hp > max_hp:
    max_hp = i.hp
    best_food = i

    print("You eat an {} and gained {} Hp.".format(best_food.name, best_food.hp))
    self.hp += best_food.hp
    print("Your HP is {}.".format(self.hp))
    best_food = [1] in self.inventory
    self.inventory[1] = ''

    e.g. when I pick up an apple I eat it then it get removed from the list but then 2 more apples appear.
    Please help.

  13. Valerie

    Hi,
    I apologize if this question has already been brought up, I looked back through all of the comments and I don’t think I saw it asked anywhere else. When I try and run game.py from the terminal, I get the error from items.py:

    line 19, in __init__
        super().__init__(name="Gold",
    TypeError: super() takes at least 1 argument (0 given)
    

    My code for this part of the script in items.py is:

    class Gold(Item):
         def __init__(self, amt):
              self.amt = amt
              #superclass constructor, always called by a subclass constructor
              super().__init__(name="Gold",
    		                    description="A round coin with {} stamped on the front.".format(str(self.amt)),
    		                    value=self.amt)
    

    Do I need to add arguments to super()? Thanks!

    1. Phillip

      This most likely means you are using version 2.x version of Python because the super() constructor was added in Python 3. There’s some more discussion of this error in the comments on the Intro post, if that helps. Happy coding!

    2. Chad Wilson

      It is relatively trivial to fix this up to work on Pything 2.x. Here is what I did.

      1. Search for all top level class definitions and change from from “class Name:” to “class Name(object):”. This is referred to using the new style of classes.

      2. Search for all super().__init__ constructor calls and change them to “super(CLASSNAME, self)__init__. For example in tiles.py you should super(EnemyRoom, self).__init__(x, y) for the constructor in the EnemyRoom class.

      3. Search for all prints with formatting using {} formatters, and replace the empty {} with the proper indexes. Example. print “{0} HP is {1}.”.format(enemy.name, enemy.hp)

      That should be everything

  14. Jason Walker

    Whenever i try and run the game via PyCharm(IDE i am using) i get the following error:

    C:\Python34\python.exe: can’t open file ‘C:/Users/JASON_PC/PycharmProjects/textadv/items’: [Errno 2] No such file or directory

  15. Chad Wilson

    I would like to work on this some more and commit my additions to your github, but I want to do this in python 2.x. How do you suggest I do that? By ‘do that’ I mean in github.

    1. Phillip Johnson Post author

      I would start by forking the project on GitHub and then make changes in your own repo. However, won’t be adding Python 2.x support into the main project since I encourage new learners to start with Python 3.x.

  16. Cassidy

    I don’t understand what’s going on with this! This is my FindSword code:

    class FindIronSwordRoom(LootRoom):
    def __init__(self, x, y):
    super().__init__(x, y, items.IronSword())

    def intro_text(self):
    return “””
    You find an small iron short sword on the ground.
    “””

    And then it gives me this error everytime I try to run it:

    TypeError: __init__() missing 4 required positional arguments: ‘name’, ‘description’, ‘value’, and ‘damage’
    logout

    It’s very frustrating! Please help!

  17. Kei

    Hello,

    I would also like to thank Philip for creating this tutorial and everyone else for asking excellent questions. It has helped me along the way. I have a version of this game that runs, but it crashes whenever I encounter an enemy, such as a troll (similar to Giant Spider). I am using Python 3.4 on OS X El Capitan (a Mac). This is the error message I get on IDLE:

    Traceback (most recent call last):
      File "/Users/KJ/Desktop/adv_game/game.py", line 35, in 
        play()
      File "/Users/KJ/Desktop/adv_game/game.py", line 30, in play
        player.do_action(action, **action.kwargs)
      File "/Users/KJ/Desktop/adv_game/player.py", line 18, in do_action
        action_method(**kwargs)
      File "/Users/KJ/Desktop/adv_game/player.py", line 39, in move_west
        self.move(dx=-1, dy=0)
      File "/Users/KJ/Desktop/adv_game/player.py", line 27, in move
        print(world.tile_exists(self.location_x, self.location_y).intro_text())
      File "/Users/KJ/Desktop/adv_game/tiles.py", line 136, in intro_text
        if self.enemy.is_alive():
    TypeError: is_alive() missing 1 required positional argument: 'self'
    

    If I go to line 136 of tiles.py and modify it to is_alive(self), it simply gives me a different error once I restart the game and next encounter a troll. That error basically comes down to:

    ...
     File "/Users/KJ/Desktop/adv_game/tiles.py", line 136, in intro_text
        if self.enemy.is_alive(self):
      File "/Users/KJ/Desktop/adv_game/enemies.py", line 15, in is_alive
        return self.hp > 0
    AttributeError: 'TrollRoom' object has no attribute ‘hp’
    

    My question is, what am I doing wrong in either the tiles.py module or enemies.py module? I think I am following the tutorial closely so I thought it should work.

    Thanks,
    Kei

    1. Phillip Johnson Post author

      The TypeError you referenced usually happens when you try to call a method on an object that has not been instantiated. In this example, it seems you are trying to call is_alive on a Troll. In the __init__() for TrollRoom is it possible that you have something like enemies.Troll instead of enemies.Troll()? If that doesn’t fix it, send me a link to your code and I can take a look.

      When you tried to fix the error, you fixed it by passing self into is_alive(). However, that’s not the correct solution because then you are trying to pass a TrollRoom object into the is_alive() method, which doesn’t make sense because a room cannot be alive or dead. That’s why you go the second error that the TrollRoom does not have HP.

  18. Kei

    You were exactly right about error! I had written enemies.Troll instead of enemies.Troll(), though I didn’t make the same mistake for the MinotaurRoom (which was based on the Ogre). Thank you!

  19. Kei

    I had one more question. Is there any way to add an AI component to the text adventure without adding hundreds of lines of code to make it effective? By “AI” I mean having the computer play against the human player, perhaps by using alpha beta search or minimax search. Right now it seems to be more like a one-player game.

    1. Phillip Johnson Post author

      Perhaps the easiest thing would be to improve the Enemy class so that it attacks intelligently. But to do that, the enemy would need to have its own set of possible moves like attack, heal, flee, use magic, etc. But “easiest” is relative, this would require a good deal of logic to be added to the Enemy class.

  20. Kei

    Thanks Philip! There’s another enhancement idea that I am trying to think of how to implement. How could I write a method to let the computer guide the player by telling him which move he should make next? So the computer should ask the player if he wants a suggestion, and if the player agrees then the computer suggest a move and makes it. It should keep doing this until it’s able to beat the game by exiting the cave. I’m thinking that it would involve implementing Minimax or alpha beta, but I’m not yet sure how to implement them in Python into this game.

    1. Phillip Johnson Post author

      It may be possible, but you’d have to do some significant restructuring of the code. Minimax requires some concept of “points” so you would need the game to assign point values to outcomes and then be able to play out different scenarios to determine which move resulted in the highest point value. If you’d like to learn more about Minimax, you might want to check out this post.

  21. Tom Nazir

    Hi Phillip,
    First of thanks a bunch for this tutorial – it’s superb!
    I am having a problem that whenever the player enters a room containing a weapon, the game throws back this error:

    Traceback (most recent call last):
    File “at/game.py”, line 27, in
    play()
    File “at/game.py”, line 12, in play
    room.modify_player(player)
    File “C:\Users\Tom\Desktop\Python\escape from (insert name here)\Tom\at\tiles.
    py”, line 20, in modify_player
    raise NotImplementedError()
    NotImplementedError

    What appears to be the problem?

    Cheers,
    Tom

    1. Phillip Johnson Post author

      The NotImplementedError is actually an exception that we put in place to make sure we don’t create base objects directly. It’s basically a check to make sure we don’t break our own rules. My guess is that your map is trying to create a MapTile object instead of something like LootRoom. Or perhaps you copy-pasted and forgot to change the modify_player() method. You might want to review Part 2 where I talk about this exception and how new tiles are created. Happy coding!

      1. Tom Nazir

        Hi Phillip,

        Everything is working now – basically my “LootRoom” class was giving (for want of a better word) its loot to something I had called “player” instead of the actual “the_player”. Silly me.

        Thanks for the snappy response!
        Tom

  22. Pierre

    Hey, I am seeming have problems with my code in the Player Module

    actionmethod = getattr(self, action.method.__name__)
                   ^
    IndentationError: expected an indented block
    

    How would i exactly be able to fix it??

    1. Phillip Johnson Post author

      Usually that means the line of code is not properly indented. Whitespace matters in Python, so you have to make sure that lines are indented the correct number of spaces. It also could mean that you have mixed spaces and tabs. Your text editor should have a “show all characters” feature that will make whitespace visible. Hope that helps!

      1. Pierre

        Thanks Philip

        One more question. When the code is completed, there seems to be a certain problem with the code. It comes up with this.

        Traceback (most recent call last):
        File “/Users/pierre/Desktop/AdventureTime/Game.py”, line 23, in
        play()
        File “/Users/pierre/Desktop/AdventureTime/Game.py”, line 5, in play
        world.load_tiles()
        File “/Users/pierre/Desktop/AdventureTime/world.py”, line 14, in load_tiles
        tile_name = cols[x].replace(‘\n’, ”) # Windows users may need to replace ‘\r\n’
        IndexError: list index out of range
        [Finished in 0.131s]

        Can I please get some help. Thanks for the help!

        Pierre

  23. Ross

    When I run the code I get the error:

    Traceback (most recent call last):
    File “game.py”, line 23, in
    play()
    File “game.py”, line 7, in play
    world.load_tiles()
    File “/home/ross/Desktop/TextAdv/world.py”, line 18, in load_tiles
    _world[(x, y)] = None if tile_name == ” else getattr(__import__(’tiles’), tile_name)(x, y)
    TypeError: emptyCorridorEW() takes 1 positional argument but 2 were given

    Any idea what I could be doing wrong?

    Thanks,

    Ross

    1. Ross

      Never mind, I had defined my tile as a function rather than a class (d’oh). I’m now getting this problem though –

      Traceback (most recent call last):
      File “game.py”, line 23, in
      play()
      File “game.py”, line 10, in play
      print(room.intro_text())
      AttributeError: ‘NoneType’ object has no attribute ‘intro_text’

      Any idea what could cause that?

  24. Teun

    First of all thank you very much for making this tutorial, I learned a lot!

    I do have one problem, after a lot of work I managed to make the game work(sadly without some small features I wanted to implement in it) only, it’s not printing the return text after you “beat” a room! It seems to be getting overwritten by the def attack in player.py, because that’s all I’m seeing after an enemy is dead. Because in tiles.py it should return the text in the else statement if the self.enemy.isalive = false, correct?

    Thank you for your answer!

  25. Alfie Atkinson

    how do i fix this?? i’ve checked and there scouldn’t even be an indented block there, should there?

    Traceback (most recent call last):
    File “C:\Users\Alfie\Desktop\Python\Programs\RPG\game.py”, line 26, in
    play()
    File “C:\Users\Alfie\Desktop\Python\Programs\RPG\game.py”, line 5, in play
    world.load_tiles()
    File “C:\Users\Alfie\Desktop\Python\Programs\RPG\world.py”, line 16, in load_tiles
    _world[(x, y)] = None if tile_name == ” else getattr(__import__(’tiles’), tile_name)(x, y)
    File “C:\Users\Alfie\Desktop\Python\Programs\RPG\tiles.py”, line 9
    raise NotImplementedError()
    ^
    IndentationError: expected an indented block

    1. Alfie Atkinson

      This is my code for that section

      import items, enemies, actions, world
       
      class MapTile:
          def __init__(self, x, y):
              self.x = x
              self.y = y
      
      	def intro_text(self):
          	raise NotImplementedError()
       
      	def modify_player(self, player):
          	raise NotImplementedError()
      
  26. EthanKelly

    This is a great tutorial! I have had trouble with a section dealing with location_x and location_y
    I run game.py, and it gives me this:

    Traceback (most recent call last):
    File “game.py”, line 30, in
    play()
    File “game.py”, line 11, in play
    room = world.tile_exists(player.location_x, player.location_y)
    AttributeError: ‘Player’ object has no attribute ‘location_x’

    My code for world.py

    room = world.tile_exists(player.location_x, player.location_y)

    and my code for Player()

    class Player():
    def __init__(self):
    self,inventory = [items.Shoe(15), items.SuperStick()]
    self.hp = 50
    self.location_x, self.location_y = world.starting_position
    self.victory = False

    I hope you can help me!

  27. blueninja516

    Hello, I was trying to figure this out on my own and I know its a few days after, but I tried the code, and whenever I try and run it, I get

    Traceback (most recent call last):
      File "C:\Users\Andrew\Desktop\Text_Adventure\game.py", line 29, in 
        play()
      File "C:\Users\Andrew\Desktop\Text_Adventure\game.py", line 4, in play
        world.load_tiles()
      File "C:\Users\Andrew\Desktop\Text_Adventure\world.py", line 5, in load_tiles
        with open('map.txt', 'r') as f:
    FileNotFoundError: [Errno 2] No such file or directory: 'map.txt'
    
    I have this code for world.py
    _world = {}
    starting_position = (0,0)
    def load_tiles():
    	"""Parses a file that describes the world space into the _world object"""
    	with open('map.txt', 'r') as f:
    		rows = f.readlines()
    	x_max = len(rows[0].split('\t')) #Assumes all rows contain the same number of tabs
    	for y in range(len(rows)):
    		cols = rows[y].split('\t')
    		for x in range(x_max):
    			tile_name = cols[x]/replace('\r\n', '') # Windows users may need to replace '\r\n'
    			if tile_name == 'StartingRoom':
    				global starting_position
    				starting_position = (x, y)
    			_world[(x, y)] = None if tile_name == '' else getattr(__import__('tiles'), tile_name)(x, y)
    def tile_exists(x, y):
    	return _world.get((x,y))
    

    I don’t know what I did wrong and any help would be useful at this point.

    1. Phillip Johnson Post author

      It’s a little difficult to tell because you didn’t follow the same directory structure as the tutorial. What’s your directory structure (the tree command will give you a nice print out)? How are you running the game?

      1. blueninja516

        Sorry, I got that part working. I just had a few things named differently apparently. And I’m running it in the command line, it just stopped giving me errors so I’m just waiting for it to start up.

  28. CB

    Hi

    Your books great stuff.

    Just a tip for anyone else. I was getting the error:

    AttributeError: ‘tuple’ object has no attribute ‘intro_text’

    and it was directly related to this code:

    world_map = [
        [None,VictoryTile(1,0),None],
        [None,EnemyTile(1,1),None],
        [EnemyTile(0,2),StartTile(1,2),EnemyTile(2,2)],
        [None,EnemyTile(1,3),None]
    ]
    

    The actual reason for the error is I created the code in Excel thinking I could create maps and copy them across to my IDE. I’m using PyCharm.

    Excel must put a format onto the text, it does something very similar if you write SQL and use the TOAD application.

    So if you have written code somewhere else e.g. in another application and are copying and pasting it across to your IDE you might get errors and you don’t understand why.

  29. jake

    ok so i think what i did wrong was when you mentioned about making a new module i made a new file so all of my code is broken into different files how should i go about fixing this?

    I’ve always wanted to make a text based adventure game in python i managed to make one in school but instead of pressing a button to attack you would type it and then the program would choose from 1 to 5 and depending on the number depended on how you killed the enemy but sadly i couldn’t finish it, but now i have some spare time i would love to get this code up and running so i can make more progress on it.

    much appreciated.

  30. Kyle Hoffmann

    how do i make it so that when you enter a loot room it gives you the loot and when you leave and return to the loot room it says that the room is empty

  31. Yuvraj Walia

    Hey Phillip! I’m at my wits end! I have been working through your tutorial for a couple days now, but I’m stuck on the final step, getting it to run! I’ve been using the atom IDE and have written everything on it. https://i.imgur.com/wvjItBN.png that’s my files and how I’ve organized it, as well as the file with my test map. That’s the one thing I can’t get to work! I had everything else up and running, but i still get this error: https://i.imgur.com/53XnAp9.png

    Now i’m running this through the IDE through the run hotkey, is that a possiblity as to why it isn’t working? I went to IDLE and opened up game.py and ran it there, still didn’t work. PLEASE HELP

    1. Phillip Johnson Post author

      Yes, it is likely that the IDE is causing problems. You can confirm this by running the game from the command line, as described in the tutorial. My guess would be that the IDE is running the code from inside the Declarations folder, which (correctly) does not contain map.txt. You could 1) change how the IDE runs the code (i.e. run it from TestGame as Declarations/game.py), 2) move map.txt inside of Declarations, or 3) change the code to look for ‘../resources/map.txt’ using the os.path.join function.

      1. Yuvraj Walia

        Hi! I got the program working on my IDE, (Atom) but I can’t use the hotkeys to interact within the IDE so I moved onto my command prompt, but I get the previous error about not being able to find TestMap.txt! Any reason why that could be?

  32. Rebecca

    Hi Phillip! I bookmarked this tutorial a while ago when I was reading about game design. Today I stumbled on it again and wanted to say thanks for writing – and for continuing to reply to comments over 3 years later!

    It’s so common to see guides like this with years of unanswered comments. While I don’t feel that bloggers are obligated to reply to comments indefinitely, it can be discouraging when all the resources you can find on a particular topic appear abandoned/outdated. So I really admire your commitment to helping people learn. I imagine it gets frustrating answering the same questions repeatedly, but you always remain courteous and helpful.

    Anyway, thanks again for your work and I hope you have a relaxing holiday season this year 🙂

  33. Dave

    Hello Phillip.

    Thank you for putting the tutorial together. I wrote a similar program back in the mid 80’s using IBM PC BASIC and a book I borrowed from a buddy of mine.
    That being said, I was considering purchasing your book but I’m concerned by the amount of jumping back and forth between files in this tutorial. I’m used to some sort of flowchart, pseudocode, etc to have a plan of attack.
    I went half way through part 3 without noticing that you changed between editing player.py and actions.py. Not really your fault, but just an example of how the hopping back and forth burned me.

    Does the book have more structured content?

    Also, I am confused by your choice to use tabs in the maps.txt file, instead of CSV or something since whitespace matters so much in Python.
    Was there any particular reason for that decision?

    Thanks
    Dave

    1. Phillip Johnson Post author

      Hi Dave,

      The advantage of the book is that it is longer, so it is more guided. It also is geared towards complete beginners, so that way sway you one way or the other. There’s still switching back and forth between files because building and improving the game happens throughout the book. However, the source code for the book contains code for each chapter so you can check yourself as you go.

      I chose to use a simple format for the maps.txt file because it’s easy to create, read, and parse. CSV has additional challenges that I didn’t want to bog down the tutorial with.

      Hope that helps!

  34. RJ

    Hello Phillip,
    Got your book for Christmas and enjoying learning to code! I’m half way through chapter 12 and I keep getting this error when I try to test it:

    Traceback (most recent call last):
      File "C:\Users\jacks\Desktop\Python\Text Game\game.py", line 2, in 
        import world
      File "C:\Users\jacks\Desktop\Python\Text Game\world.py", line 65
        class VictoryTile(MapTile):
            ^
    SyntaxError: invalid syntax
    

    any thoughts? Thanks!

    1. Phillip Johnson Post author

      Hmm sometimes when you get a vague error like that, the problem in your code isn’t actually on that line, but the parser doesn’t detect a problem until that line. If I had to guess, it’s possible you have a bad character (like an extra space) somewhere, or you forgot a character (like a closing parenthesis) a few lines up.

      Did you take a look at the source code for the book on GitHub or Dropbox? Check there and if you can’t figure it out, post your code online (gist, pastebin, etc.) and I will take a closer look.

      1. RJ

        I went through the code from GitHub. Noticed several errors, especially in the world.py. I managed to correct them and now the game works! Thank you much! Can’t wait to finish coding!

  35. Waylon

    So everything else seems to be working fine for me. However when I walk in a room that adds loot into the players inventory, it adds it correctly, but it adds a second copy to my inventory when I leave the room. Also, if I’m standing in the room and I check my inventory, it adds another copy. I keep seem to figure out why and my code for the loot tiles is exactly like yours. Any idea of what might be happening???

    1. Waylon

      Actually, what is happening is when I type “i” to check my inventory, it is adding the loot a second time which doesnt make sense. This is my print_inventory action in the player.py module:

          def print_inventory(self):
              for item in self.inventory:
                  print(item, '\n')
              print("Gold: " + str(self.gold))
      

      (I came up with my own method of tracking gold. I made it a value like HP instead of an item like a weapon)

      1. Waylon

        It turns out the solution is simply finding a way to check if the player has already visited the room. I’ve tried a couple of ideas but I can’t seem to figure out. Can anyon give me a hand?

  36. Josephine P

    Hi Phillip,
    Thanks for the tutorial, was lots of fun!
    Currently having the following issue:

    runfile('C:/Users/Placjo/The Game/game.py', wdir='C:/Users/Placjo/The Game')
    Reloaded modules: world, player, items, tiles, enemies
    Traceback (most recent call last):
    
      File "", line 1, in 
        runfile('C:/Users/Placjo/The Game/game.py', wdir='C:/Users/Placjo/The Game')
    
      File "C:\Users\Placjo\AppData\Local\Continuum\anaconda3\lib\site-packages\spyder\utils\site\sitecustomize.py", line 710, in runfile
        execfile(filename, namespace)
    
      File "C:\Users\Placjo\AppData\Local\Continuum\anaconda3\lib\site-packages\spyder\utils\site\sitecustomize.py", line 101, in execfile
        exec(compile(f.read(), filename, 'exec'), namespace)
    
      File "C:/Users/Placjo/The Game/game.py", line 35, in 
        play()
    
      File "C:/Users/Placjo/The Game/game.py", line 12, in play
        world.load_tiles()
    
      File "C:\Users\Placjo\The Game\world.py", line 23, in load_tiles
        _world[(x, y)] = None if tile_name == '' else getattr(__import__('tiles'), tile_name)(x, y)
    
      File "C:\Users\Placjo\The Game\tiles.py", line 339, in __init__
        super().__init__(x,y,items.RustyKey())
    
    TypeError: __init__() missing 1 required positional argument: 'amt'
    

    Here’s my locker room code:

    class Locker(LootRoom):
        def __init__ (self,x,y):
            super().__init__(x,y,items.RustyKey())
        def intro_text(self):
            return """
            You open the locker to find an iron rusty key. You put it in your pocket.
              
            """
    

    Any help would be greatly appreciated 🙂

    1. Phillip Johnson Post author

      Well the error is referencing a variable amt that is missing. Unless you created that somewhere extra, the only place it should exist is inside of the Gold class. Is it possible you left that variable in the init() of the RustyKey class by mistake? Maybe a copy-paste error?

  37. ANON

    Hello Phillip,

    This whole tutorial has been a life saver! I had no idea where to start till now. I’m running into a problem with the code, which I copied exactly from the GitHub rep. When I run the game, my output is:

    You see a bright light in the distance...
            ... it grows as you get closer! It's sunlight!
            Victory is yours!
    
    Process finished with exit code 0
    

    So it seems that the code is automatically running the player win scenario, I was wondering if you had suggestions on how to troubleshoot this.

    1. Phillip Johnson Post author

      Without seeing the code, my guess is that the player is starting at the incorrect location or that there is an error in the map file that put the end-game tile at the wrong location. My suggestion for debugging is to use print statements in the methods that create the world to verify that the correct rooms are created at the correct locations. Also double check the code where the player’s starting position is.

  38. Jeff Lehman

    I’m helping my son with this code, and he’s getting a different (but possibly similar) attribute error.
    The game starts and gives a description of the starting room. It then prints “Choose an action:” but after that gives a series of tracebacks ending with actions.py, in the __init__ section for the command super().__init__(method = player.move_east, name = ‘Move east’, hotkey = ‘e’)
    AttributeError: module ‘player’ has no attribute ‘move_east’

    We’re fairly stumped.

    1. Phillip Johnson Post author

      That error means that Python doesn’t see the move_east method in the Player class. If you’re sure it’s there, check the indentation, make sure you aren’t forgetting a self, check for typos, etc. If you can’t figure it out, post the code and I will take a look.

  39. Nathan

    Is there any way to get the map to display horizontally? When I run the game the map is viewed as:
    ???
    ???
    ???,
    etc.
    Rather than: ??? ??? ??? ???, etc. The map is very hard to read when the map is displayed vertically.

    1. Phillip Johnson Post author

      It’s probably going to be easiest to just change your map file so that the map is “wide” instead of “tall”. The alternative would be to change the order in which the map is read by swapping the x-axis and y-axis, but I think that is going to be way more confusing.

  40. me

    I can’t pick up items, idk why. The game runs fine, there are no errors, but after entering FindDaggerRoom and checking inventory there’s no dagger.
    Here’s my LootRoom + FindDaggerRoom code

    class LootRoom(MapTile):
        def __init__(self, x, y, item):
            self.item = item
            super().__init__(x, y)
    
        def add_loot(self, player):
            player.inventory.append(self.item)
    
        def modify_player(self, player):
            self.add_loot(player)
    
    
    class FindDaggerRoom(LootRoom):
        def __init__(self, x, y):
            super().__init__(x, y, items.Dagger())
    
        def intro_text(self):
            return """
            Your notice something shiny in the corner.
            It's a dagger! You pick it up.
            """
    And here's the player.inventory code:
    
    class Player():
        def __init__(self):
            self.inventory = [items.Gold(15), items.Rock()]
            self.hp = 100
            self.location_x, self.location_y = world.starting_position
            self.victory = False
    
  41. joel

    File “C:\Users\joelb\AppData\Local\Programs\Python\Python37-32\lib\runpy.py”, line 236, in _get_code_from_file
    code = compile(f.read(), fname, ‘exec’)
    File “c:\Users\joelb\Desktop\bloodline\adventuremodules\player.py”, line 52
    if i.damage > max_dmg:
    ^
    SyntaxError: invalid syntax
    no idea whats happening here any help?

  42. Fernando Moreno

    First of all I’d like to say that your tutorial has helped me understand classes in python.
    I am following your tutorial except I’m doing it without enemies and loot rooms for now, and I have stumbled into an issue where it would only give me the choice to view inventory and not the other choices such as moving east, north, etc.. It allows me to show the inventory and thats it. It seems it won’t see the methods for movement in the Player class and I can’t seems to get it to see them.
    import items, enemies, actions, world

    here is the code for the tiles, player, and actions
    [removed for brevity]

      1. Phillip Johnson Post author

        It could be a problem with the map you’ve created. I would add some print() statements to help you debug this. For example:

        if world.tile_exist(self.x + 1, self.y):
            moves.append(actions.MoveEast())
        else:
            print("No tile at x:{}, y:{}".format(self.x + 1, self.y))
        
        1. Fernando Moreno

          The print statements helped me realize that the other tiles weren’t being loaded properly so I looked into the the world.py where I load the tiles and realized that my last line was in the ‘if’ statment when it shoud’ve been on the outside.
          Thank you by the way.

  43. Casper Sjöberg

    when i tried to run the program i got the error messages “Traceback (most recent call last):
    File “C:/Users/caspe/PyCharm Projects/untitled/adventuretutorial/game.py”, line 2, in
    from player import Player
    File “C:\Users\caspe\PyCharm Projects\untitled\adventuretutorial\player.py”, line 2, in
    import items, world
    File “C:\Users\caspe\PyCharm Projects\untitled\adventuretutorial\items.py”, line 2
    def __init__(self, name, description, 55, 100):
    ^
    SyntaxError: invalid syntax”

  44. Casper

    Where do i start the game? I know that you say “To run the program, navigate to the folder containing the adventuretutorial package in your console and run python adventuretutorial/game.py” but i don’t what you mean by that (i am on windows)

  45. The0Wolf1

    I have another problem.

    In “import items, enemies, actions, world”, the module “items” isn’t found.

    I’ve tried moving it, making it into “item”, but nothing works.

    Please help. I’ve struggled with this on several of the games I’ve tried coding (with this tutorial).

  46. joeby

    I’m getting an error “module ’tiles’ has no attribute ‘LeaveCaveRoom’

    I’ve had a good look at it for a while and it seems that the getattr(__import__(’tiles’), tile_name)(x, y) statement is not recognising the map tiles within the tiles.py file even though they are clearly defined

    The attribute error changes to whatever is on the first line of my map.txt file.

    1. Phillip Johnson Post author

      This usually happens when you aren’t running the program as described above and your resources folder isn’t found. If you are using an IDE, it could be due to PYTHONPATH. Check the advice above and see if that helps.

      1. joeby

        Hi

        I don’t think that’s the issue here, I’ve amended all the variables for the PYTHONPATH but nothing has changed.

        The full output is –
        **Traceback (most recent call last):
        File “game.py”, line 27, in
        play()
        File “game.py”, line 7, in play
        world.load_tiles()
        File “C:\Users\joefu\PycharmProjects\adventuretutorial\world.py”, line 17, in load_tiles
        _world[(x, y)] = None if tile_name == ” else getattr(__import__(’tiles’), tile_name)(x, y)
        AttributeError: module ’tiles’ has no attribute ‘LeaveCaveRoom SlimeRoom’**

        with “LeaveCaveRoom SlimeRoom” changing to whatever tiles I place on the top of the map file.

        Thanks for replying!

        1. Phillip Johnson Post author

          OK that’s a different error than you said in your first comment! You’ve either got a bug in your map file or in the code parsing the map file. Your most recent error is telling you that it is trying to find a tile called “LeaveCaveRoom SlimeRoom” which obviously doesn’t exist because those are two different tile types.

          1. Don Stott

            I have exactly the same problem. I don’t think it’s an issue with the map file. I changed the code to use commas instead of tabs and it does the same thing.
            It looks like something to do with how getattr is reading the file and returning it to tiles.py that isn’t working.

  47. Justyne
    python game.py
    Traceback (most recent call last):
      File "game.py", line 4, in 
        from player import Player
      File "/home/parrot/dev/adventuretutorial/player.py", line 3, in 
        import items, world
      File "/home/parrot/dev/adventuretutorial/items.py", line 22
        class Weapon(Item):
        ^
    SyntaxError: invalid syntax
    

    How fix it?

  48. Cosmo

    I made this using the website repl.it. And I can’t put any folder, directory or file outside of it. What can I do?

  49. Jasmin Nevala

    Hi! I’d like to thank you for your awesome tutorial, it has helped me a lot. First I followed the instructions of this website and then moved to read the book. I’m stuck with my code and can’t find a solution although I have tried to solve it for hours, could you please help me?

    The game works at first: it prints the different options for actions and I’m able to for example print my inventory (even though it also prints the text “Invalid action”). But when I move to different location and try to attack or print the inventory, it raises following error:

    “in choose_action action = available_actions.get(action_input)
    AttributeError: ‘NoneType’ object has no attribute ‘get'”

    As said, I have tried to solve that for hours without success 🙁 I would really appreciate any advice! My code is a bit long, so maybe I just post a link to my GitHub:

    https://github.com/JasminNevala/Text-adventure

    1. Phillip Johnson Post author

      Hi Jasmin,

      That’s a tricky bug, but thanks for sharing your code, I was able to figure it out! First, the error message is telling you that you are trying to call the method “get” on something that does not exist, i.e. “NoneType”. It also shows you the line the error occurred on so we know that when the code ran, available_actions did not point to any object.

      So next I looked into the code that is supposed to set available_actions: the get_available_actions method. That method is supposed to create the dictionary that contains all possible actions. But if you look carefully, you’ll see that line 51 return actions is actually indented inside of the else. That means sometimes the method will return nothing, i.e. NoneType and it will only give back the dictionary of actions if your code reaches that else branch.

      Thankfully, the solution is simple: just unindent line 51 by deleting one of your tabs so that the return statement is always reached! For what it’s worth, this is one of the big complaints about Python: whitespace (tabs, spaces, new lines, etc.) can actually affect how your code runs even though you can’t see it.

      Happy programming!

      1. Jasmin Nevala

        Wow that worked, I’m so happy! Thank you so much for your time, and that you help people to learn! This was a good lesson.

        Have a nice summer!

  50. Nehal Gupta

    Hi,
    I just completed this code and I am getting some unexpected errors. I’m using the zsh terminal in Mac. Please help

    Traceback (most recent call last):
      File "/Users/nehalgupta/Desktop/adventuretutorial/game.py", line 2, in 
        from player import Player
      File "/Users/nehalgupta/Desktop/adventuretutorial/player.py", line 1, in 
        import items,world
      File "/Users/nehalgupta/Desktop/adventuretutorial/items.py", line 18
        value=self.amt)
        ^
    SyntaxError: invalid syntax
    
  51. Alex

    Hi,
    I have 2 problems:
    do_action is an unresolved attribute,
    and my computer can’t find the package for the import ‘import world’.
    Please help?

  52. Jesse

    having an issue,
    action_method(**kwargs)
    ^
    SyntaxError: EOF while scanning triple-quoted string literal

    def do_action(self, action, **kwargs):
    action_method = getattr(self, action.method.__name__)
    if action_method:
    action_method(**kwargs)

    Thanks! this guide has been really helpful for learning

  53. Jesse

    just finished and when i try to run the game.py nothing happens. no errors or anything. checking each of the modules and not getting any errors in any of them. very lost 🙁

    1. Jon

      I have the same problem. Hard to debug nothing. I figure that I am better off learning python enough so I am figuring this all out on my own instead of copying code. At least I learned a little more about Python in the process.

      1. Phillip Johnson Post author

        It would be helpful to know how you are running the application. Also, if you go to the GitHub repo, you will find a link for repl.it that will load the latest code in a temporary environment for you to play with. I just verified it’s working there.

Leave a Reply to Phillip Johnson Cancel reply

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