简体   繁体   中英

How to fix “IndexError: List index is out of range” in my code?

I am quite new to Python, and I wanted to test out a tilemap system. When I try to load the tiles, i get the error message IndexError: list index out of range . What do I need to do in order to solve this issue, and others like it in the future? Exact error:

Traceback (most recent call last):
  File "N:/Python/Projects/Projects/First Topdown/First Topdown.py", line 114, in <module>
    pygame.draw.rect(screen, colors[tilemap[row][column]], (column*tilesize, row*tilesize, tilesize, tilesize))
IndexError: list index out of range

I have heard that it means I am trying to access a value which is none or empty. Here is the part of the code that is about the tilemap . Without the last drawing bit, the rest works fine. (It's not a lot of other code, just movement.)

import pygame

pygame.init()

tilesize = 64
mapwidth = 16
mapheight = 13
screen = pygame.display.set_mode((mapwidth*tilesize, mapheight*tilesize))


pygame.display.set_caption("Aspen")

WALLTOP = 0
WALLBOT = 1
GRASS = 2
wallTop = (128, 128, 128)
wallBot = (210, 105, 30,)
grass =   (50, 205, 50)

colors = {
WALLTOP : wallTop,
WALLBOT: wallBot,
GRASS : grass
}

tilemap = [
            [WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP,
            WALLTOP, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, GRASS, WALLTOP,
            WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP]
]


running = True
while running:

    screen.fill((64, 64, 64))

    for row in range(mapheight):
        for column in range(mapwidth):
            pygame.draw.rect(screen, colors[tilemap[row][column]], (column*tilesize, row*tilesize, tilesize, tilesize))

    pygame.display.update()

You're getting the IndexError because you've only defined one row in your tilemap , but the outer for loop supplies row indices from 0 to mapheight-1 because of this statement:

for row in range(mapheight):

To avoid that error, try changing it to:

for row in range(len(tilemap)):

Update

I think I've figured out what you're trying to do. As I said previously, the IndexError is because the number of rows in tilemap didn't match the number you were trying to display with the nested for loops.

Below I've fixed the way tilemap is defined so it has the proper number of row and columns (and also streamlined a few other definitions and added some event processing to the display loop to allow the script to be gracefully shutdown instead of running in an infinite loop).

import pygame

pygame.init()

tilesize = 64
mapwidth, mapheight = 16, 13

screen = pygame.display.set_mode((mapwidth*tilesize, mapheight*tilesize))

pygame.display.set_caption("Aspen")

WALLTOP, WALLBOT, GRASS = range(3)

wallTop = (128, 128, 128)
wallBot = (210, 105, 30,)
grass =   (50, 205, 50)

colors = {
    WALLTOP : wallTop,
    WALLBOT : wallBot,
    GRASS   : grass
}

tilemap = [
    [WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP],
    [WALLTOP, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLBOT, WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   GRASS,   WALLTOP],
    [WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP, WALLTOP],
]

running = True
while running:

    screen.fill((64, 64, 64))

    for row in range(mapheight):
        for column in range(mapwidth):
            pygame.draw.rect(screen, colors[tilemap[row][column]], (column*tilesize, row*tilesize, tilesize, tilesize))

    # Process user-events.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # User clicked to close window.
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE: # Press escape key to quit.
                running = False

    pygame.display.update()

Ok I finally understood what you are doing.

You tried to do a second slice of your tilemap, even though your tilemap is one dimensional. For multidimensional arrays, it's best to use numpy arrays imo. You'll see what I mean when you print out the tmap in my code, it's much more readable. Also, since python works with indents, it's a good idea to organize long function calls by separating different arguments to separate lines. Othewise you get a really unreadable long line in your code.

import pygame
import numpy as np

pygame.init()

# Define base values for tiles
tsize, width, height = 64,16,13

# Set the stage with an empty 2D array full of zeroes
# Create the tilemap dynamically from width and height.
tmap = np.zeros((height,width),dtype=int)

# defines the screen I guess?
screen = pygame.display.set_mode((width*tsize, height*tsize))
pygame.display.set_caption("Asspain")

# You can now change the tilemap manually.
# You have three unique rows, so you can
# just make lists of those and later insert
# them into your np.array
r1 = [1]*width

r2 = [1]
r2.extend([2]*14)
r2.extend([1])

r3 = [1]
r3.extend([0]*14)
r3.extend([1])

# turn your lists into np.arrays so they work
# with your tmap.
r1, r2, r3 = np.array(r1),np.array(r2),np.array(r3)

# insert the rows at the appropreate positions
# mind you, indexing works a bit different in
# np.arrays than in python builtin lists.
tmap[0] = r1.copy()
tmap[1] = r2.copy()
tmap[2:12] = r3.copy()
tmap[12] = r1.copy()

# You now have a nice and readable tmap.
# You can make the map creation process
# as manual as you want, I just used shortcuts
# based on symmetry.
print(tmap)

# Now you can just iterate over every element
# via double iteration, or using indices.
# I've heard iterating over indices using range
# is always faster.

# define colormap
cmap = {0:(50, 205, 50),
        1:(128, 128, 128),
        2:(210, 105, 30,)}

# this is unneccessary, but it's good to know
# that numpy arrays also have a len() like
# attribute called shape.
width, heigth = tmap.shape[0], tmap.shape[1]

# Gameloop
while True:

    # Is this the default color of a tile?
    # Do you need that?
    screen.fill((64, 64, 64))

    for row in range(width):
        for col in range(heigth):
            pygame.draw.rect(screen, 
                             cmap[tmap[row][col]],
                             (col*tsize,
                              row*tsize,
                              tsize,
                              tsize))
    pygame.display.update()

EDIT: I noticed that you do a lot of dictionary slices, which are very slow. If you do not need to repeatedly slice a dictionary inside the game loop (ie the map doesn't change), don't. I've written a bit of extra code, which produces the same tmap array as above as a template for your tiles, but this time, I'm also casting an array, which maps color triplets to these positions.

This way you can create your whole map once using fast numpy arrays, which utilize dtype typecasting of your array, while still benefitting from the readibility of the previously specified tmap.

import numpy as np
import matplotlib.pyplot as plt

tsize, width, height = 64,16,13
tmap = np.zeros((height,width),dtype=int)
cmap = np.zeros((height,width,4),dtype=int)

r1 = [1]*width

r2 = [1]
r2.extend([2]*14)
r2.extend([1])

r3 = [1]
r3.extend([0]*14)
r3.extend([1])

r1, r2, r3 = np.array(r1),np.array(r2),np.array(r3)

tmap[0] = r1.copy()
tmap[1] = r2.copy()
tmap[2:12] = r3.copy()
tmap[12] = r1.copy()

cdct = {0:np.array((50, 205, 50, 200),dtype=int),
        1:np.array((128, 128, 128, 200),dtype=int),
        2:np.array((210, 105, 30, 200),dtype=int)}

for posy, row in enumerate(tmap):
    for posx, col in enumerate(row):
        cmap[posy,posx] = cdct[col].copy()

fig, ax = plt.subplots()
plt.axis('off')

ax.imshow(cmap)

As you can see, matplotlib can easily visualise X,Y,Z arrays, which the imshow() function can read as a bitmap. X are the rows, Y are the columns and Z are the color triplets. I actually made Z four elements long to toggle transparency with the fourth element. This way you can create your bitmap once as a background and then just iterate directly over the color quadruples, without doing 16*13 slow dictionary slices at every iteration of the while loop. The plotting, obviously, is just a visual demonstration of what it will look like and is a nifty little tool for modelling your game map.

And because it uses typecasted numpy arrays and avoids a lot of slow operations, it should be lightning fast compared to before.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM