Python Forum
[IndexError: List Index out of Range] RPG Game
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[IndexError: List Index out of Range] RPG Game
#1
Hello everyone, I am following a guide that is for an RPG Game. It's titled, 'How to Write a Text Adventure in Python'

I have finished the tutorial and have fixed most of the errors I was receiving, now I am stuck on this one. Any help is appreciated, thank you!

Traceback (most recent call last):
  File "C:\*\rpggame\game.py", line 26, in <module>
    play()
  File "C:\*\rpggame\game.py", line 5, in play
    world.load_tiles()
  File "C:\*\rpggame\world.py", line 13, in load_tiles
    tile_name = cols[x].replace('\n', '') # Windows users may need to replace '\r\n'
IndexError: list index out of range
I shortened the file path so that it's easier to read. This is the structure of the file system.

Directory Hierarchy

Quote:|->Learning-Py Projects
--|->rpggame
----|->__pycache__
----|->resources
----|__init__.py
----|actions.py
----|enemies.py
----|game.py
----|items.py
----|player.py
----|tiles.py
----|world.py

You play the game by running the game.py file. That is the module that contains the overall game-loopu!. I hope that this tutorial is teaching me a good practice in OOP programming with Python 3. I could really use advice on anything I may have done wrong or could do better with a link or reference to the material / documentation so I can start learning. I have no problem studying! I appreciate any feedback from anyone willing to take the time, I know these things are free contribution. I will provide reputation to the person who helps me out!

Here is the python code to all the files.

actions.py
from player import Player

class Action():
    def __init__(self, method, name, hotkey, **kwargs):
        self.method = method
        self.hotkey = hotkey
        self.name = name
        self.kwargs = kwargs

    def __str__(self):
        return "{}: {}".format(self.hotkey, self.name)

class MoveNorth(Action):
    def __init__(self):
        super().__init__(method=Player.move_north, name='Move North', hotkey='n')

class MoveSouth(Action):
    def __init__(self):
        super().__init__(method=Player.move_north, name='Move South', hotkey='s')

class MoveEast(Action):
    def __init__(self):
        super().__init__(method=Player.move_north, name='Move East', hotkey='e')

class MoveWest(Action):
    def __init__(self):
        super().__init__(method=Player.move_north, name='Move West', hotkey='w')

class ViewInventory(Action):
    """Prints the player's inventory"""
    def __init__(self):
        super().__init__(method=Player.print_inventory, name='View Inventory', hotkey='i')

class Attack(Action):
    def __init__(self, enemy):
        super().__init__(method=Player.attack, name="Attack", hotkey="a", enemy=enemy)

class Flee(Action):
    def __init__(self, tile):
        super().__init__(method=Player.flee, name="Flee", hotkey="f", tile=tile)
enemies.py
class Enemy:
    'Base class for all mobs: \'Enemy\' class'

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

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

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)
game.py
import world
import player

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('[CommandLine] >>> ')
            for action in available_actions:
                if action_input == action.hotkey:
                    player.do_action(action, **action.kwargs)
                    break

if __name__ == "__main__":
    play()
items.py
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 ('Name: {}\nDescription: {}\nValue: {}\n'.format(self.name, self.description, self.value))


class Weapon(Item):
    'This is a subclass of Item: A class for all Weapons'

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

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

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

class Dagger(Weapon):
    def __init__(self):
        super().__init__(name = "Dagger",
                                  description = "A dull dagger with some rust. It's better than nothing..",
                                  value = 20,
                                  damage = 4.0)

class Gold(Item):
    'This is the \'GoldCoin\' class'
    def __init__(self, amt):
        self.amt = amt
        super().__init__(name = 'Gold',
                                  description = 'A loot of \'Gold Coins\'. This is certainly a valuable stash.',
                                  value = self.amt)
player.py
import random
import items, world

class Player():
    def __init__(self):
        self.inventory = [items.Gold(random.randrange(0, 100)), items.Rock()]
        self.hp = random.randrange(100, 300)
        self.location_x, self.location_y = world.starting_position
        self.victory = False

    def flee(self, tile):
        """Moves the player randomly to an adjacent tile"""
        available_moves = tile.adjacent_moves()
        r = random.randint(0, len(available_moves) - 1)
        self.do_action(available_moves[r])

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

    def print_inventory(self):
        for item in self.inventory:
            print(item, '\n')

    def move(self, dx, dy):
        self.location_x += dx
        self.location_y += dy
        print(world.tile_exists(self.location_x, self.location_y).intro_text())
 
    def move_north(self):
        self.move(dx=0, dy=-1)
 
    def move_south(self):
        self.move(dx=0, dy=1)
 
    def move_east(self):
        self.move(dx=1, dy=0)
 
    def move_west(self):
        self.move(dx=-1, dy=0)

    def attack(self, enemy):
        best_weapon = None
        max_dmg = 0
        for i in self.inventory:
            if isinstance(i, items.Weapon):
                if i.damage > max_dmg:
                    max_dmg = i.damage
                    best_weapon = i

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

        print("You use {} against {}!".format(best_weapon.name, enemy.name))
        enemy.hp -= best_weapon.damage
        if not enemy.is_alive():
            print("You killed {}!".format(enemy.name))
        else:
            print("Opponent: {}\nHit Point's Left: {}.".format(enemy.name, enemy.hp))
tiles.py
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()

    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 alll of the available actions in this room."""
        moves = self.adjacent_moves()
        moves.append(actions.ViewInventory())

        return moves

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

class StartingRoom(MapTile):
    def intro_text(self):
        return """

You come into conciousness.. Awakened by the sudden drops of rain falling from the midnight sky. You check the time..

The time is: 3:12 A.M

What happened? Your character is confused and still soaked of ale from the previous evening.

Note: It seems you were knocked unconcious. While you were out all your possesions were stolen. You are left with nothing but the clothes on your back.

                    """
    def modify_player(self, player):
        #Room has no action on player
        pass

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 EnemyRoom(MapTile):
    def __init__(self, x, y, enemy):
        self.enemy = enemy
        super().__init__(x, y)
 
    def modify_player(self, the_player):
        if self.enemy.is_alive():
            the_player.hp = the_player.hp - self.enemy.damage
            print("Enemy does {} damage. You have {} HP remaining.".format(self.enemy.damage, the_player.hp))

    def available_actions(self):
        if self.enemy.is_alive():
            return [actions.Flee(tile=self), actions.Attack(enemy=self.enemy)]
        else:
            return self.adjacent_moves()


class EmptyCavePath(MapTile):
    def intro_text(self):
        return """
        Another unremarkable part of the cave. You must forge onwards.
        """
 
    def modify_player(self, player):
        #Room has no action on player
        pass
 
class GiantSpiderRoom(EnemyRoom):
    def __init__(self, x, y):
        super().__init__(x, y, enemies.GiantSpider())
 
    def intro_text(self):
        if self.enemy.is_alive():
            return """
            A giant spider jumps down from its web in front of you!
            """
        else:
            return """
            The corpse of a dead spider rots on the ground.
            """
 
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.
        """
world.py
_world = {}
starting_position = (0, 0)


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', '') # 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))
Reply
#2
it would make it so much easier if you put this on a repo for us to download the entire thing. Because the error leads possibly back to the map file, that is also required to pinpoint the problem.

I would work backwords. An easy way to diagnose is to print things to make sure they are what is expected.
        for x in range(x_max):
            tile_name = cols[x].replace('\n', '') # Windows users may need to replace '\r\n'
x is a higher number than cols has if it is giving you this error. Print x to find out what you really are getting for x, and print cols to see if it really is what you expect it to be. Because you are getting an index error, one of those is going to be different than what you think it is going ot be.
Recommended Tutorials:
Reply
#3
Ooof, I did not think to post the map file. My bad! It is in the /resources directory. Here it is:

The first attachment is the spreadsheet I used to template the map.txt file. I lined out the tables and then copied / pasted them into a text file which is the second attachment.

Also, thank you for the quick explaination. I should be able top diagnose the problem from here! I appreciate it! If I can't I will respond back.

Attached Files

.xlsx   mapexcel.xlsx (Size: 15.33 KB / Downloads: 145)
.txt   map.txt (Size: 223 bytes / Downloads: 129)
Reply
#4
Copying every code snippet and turning them in files is more work than i am willing to do. That is why i said to put it in a repo. It is more prone to error in reconstructing your program instead of just downloading it from a repo with so many files. I have often recreating these structures to find the user has left out a required piece of code such as an import, etc. Plus if you were to change anything it is only on your side, then i have to to the whole process over again instead of pulling your changes to a repo.

Is this what you expect rows to be?
>>> rows
['\t\t\tLeaveCaveRoom\n', '\tFind5GoldRoom\tEmptyCavePath\tGiantSpiderRoom\n', '\t\tEmptyCavePath\t\t\n', '\t\tEmptyCavePath\t\t\n', 'GiantSpiderRoom\tEmptyCavePath\tStartingRoom\tEmptyCavePath\tFindDaggerRoom\n', '\t\tFind5GoldRoom\n', '\t\tEmptyCavePath\n', '\t\tSnakePitRoom']
I think you should take a look at our text adventure tutorial here. I think the dictionary approach is better than a text file with tabs as delimiters.
Recommended Tutorials:
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Pygame deleting list index. TheHumbleIdiot 4 4,870 Apr-10-2019, 05:30 PM
Last Post: metulburr
  Raspberry Pi pygameui - Fails with error index out of range erhixon 0 2,883 Sep-19-2017, 01:36 PM
Last Post: erhixon

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020