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

This is an abbreviated version of the book Make Your Own Text Adventure With Python. If you’d prefer to stick with the condensed tutorial, click here for the introduction.

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

import world
from player import Player

def play():
    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 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():
    player = Player()
    #These lines load the starting room and display the text
    room = world.tile_exists(player.location_x, player.location_y)
    while player.is_alive() and not player.victory:
        room = world.tile_exists(player.location_x, player.location_y)
        # 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:
            action_input = input('Action: ')
            for action in available_actions:
                if action_input == action.hotkey:
                    player.do_action(action, **action.kwargs)

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

if __name__ == "__main__":

To run the program, navigate to the folder containing the adventuretutorial package in your console and run python adventuretutorial/ 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 Text Adventure With Python:

  • 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

Click here to get the book!

Tagged on: , , , ,

65 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, 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/ If you get warnings about packages, try setting your PYTHONPATH environment variable manually. Have fun!

      Happy programming!

  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.
    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:
    elif direction == 1:
    elif direction == 2:
    elif direction == 3:
    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?

  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 “”, line 23, in
    File “”, line 5, in play
    File “/home/sinfulstyle/”, 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/”

      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/”, 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

    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

    [Mod edit: Removed for brevity]

    and this is what I have on 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):
            if world.tile_exists(self.x - 1, self.y):
            if world.tile_exists(self.x, self.y - 1):
            if world.tile_exists(self.x, self.y + 1):
            return moves
        def available_actions(self):
            """Returns all of the available actions in this room."""
            moves = self.adjacent_moves()
            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():
    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.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

    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 from the terminal, I get the error from

    line 19, in __init__
    TypeError: super() takes at least 1 argument (0 given)

    My code for this part of the script in is:

    class Gold(Item):
         def __init__(self, amt):
              self.amt = amt
              #superclass constructor, always called by a subclass constructor
    		                    description="A round coin with {} stamped on the front.".format(str(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 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.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’

    It’s very frustrating! Please help!

  17. Kei


    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/", line 35, in 
      File "/Users/KJ/Desktop/adv_game/", line 30, in play
        player.do_action(action, **action.kwargs)
      File "/Users/KJ/Desktop/adv_game/", line 18, in do_action
      File "/Users/KJ/Desktop/adv_game/", line 39, in move_west
        self.move(dx=-1, dy=0)
      File "/Users/KJ/Desktop/adv_game/", line 27, in move
        print(world.tile_exists(self.location_x, self.location_y).intro_text())
      File "/Users/KJ/Desktop/adv_game/", 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 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/", line 136, in intro_text
        if self.enemy.is_alive(self):
      File "/Users/KJ/Desktop/adv_game/", 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 module or module? I think I am following the tutorial closely so I thought it should work.


    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/”, line 27, in
    File “at/”, line 12, in play
    File “C:\Users\Tom\Desktop\Python\escape from (insert name here)\Tom\at\tiles.
    py”, line 20, in modify_player
    raise NotImplementedError()

    What appears to be the problem?


    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!

  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/”, line 23, in
        File “/Users/pierre/Desktop/AdventureTime/”, line 5, in play
        File “/Users/pierre/Desktop/AdventureTime/”, 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!


  23. Ross

    When I run the code I get the error:

    Traceback (most recent call last):
    File “”, line 23, in
    File “”, line 7, in play
    File “/home/ross/Desktop/TextAdv/”, 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?



    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 “”, line 23, in
      File “”, line 10, in play
      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, because that’s all I’m seeing after an enemy is dead. Because in 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\”, line 26, in
    File “C:\Users\Alfie\Desktop\Python\Programs\RPG\”, line 5, in play
    File “C:\Users\Alfie\Desktop\Python\Programs\RPG\”, 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\”, 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, and it gives me this:

    Traceback (most recent call last):
    File “”, line 30, in
    File “”, 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

    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!

Leave a Reply

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