deathMetalWorker
deathMetalWorker

Reputation: 31

Why does this list index go out of range?

I'm taking some baby steps with pygame, and I have gotten this program to work. However it throws a "List index out of range" message after looping through main() several hundred times. The list doesn't change size once it grows to a certain limit, so I am not sure where the error is happening.

Once you run the program, just move the mouse across the display, and a bunch of circles are drawn and grow as the clock ticks. Eventually, it will crash.

I have omitted all comments except the lines where the error is happening, hopefully that will make it easier to find out the cause.

Stacktrace:

    Traceback (most recent call last):
  File "C:\Users\Devo\AppData\Local\Programs\Python\Python37-32\test files\Psychedelic Circles.py", line 89, in <module>
    main()
  File "C:\Users\Devo\AppData\Local\Programs\Python\Python37-32\test files\Psychedelic Circles.py", line 49, in main
    drawCircles(snakeLength)
  File "C:\Users\Devo\AppData\Local\Programs\Python\Python37-32\test files\Psychedelic Circles.py", line 75, in drawCircles
    pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?
IndexError: list index out of range

 

     import pygame, sys, random
from pygame.locals import*
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
FUSCHIA = (255,   0, 240)
GRAY = (80, 80, 80)
YELLOW = (255, 255, 0)
ORANGE = (255, 127, 0)
BLUE = (0, 0, 255)
INDIGO = (75, 0, 130)
VIOLET = (148, 0, 211)

FPS = 20
WWIDTH  = 1000
WHEIGHT = 700


BIGBUTTON = pygame.Rect(0, 0, 1000, 700)
rainbowTuple = (RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET)


def main():
    global SCREEN, FPS, colorList, coords, snakeLength
    pygame.init()
    clock = pygame.time.Clock()
    size = (WWIDTH, WHEIGHT)    
    SCREEN = pygame.display.set_mode(size)    
    pygame.display.set_caption('Psychedelic Circles')   
    colorList = []
    coords = []
    snakeLength = 50 ### Change this value to make the circles disappear more   quickly or slowly
    while True:
        clickedButton = None
        SCREEN.fill(GRAY)
        drawButtons()
        checkForQuit()        
        for event in pygame.event.get():
            if event.type == MOUSEMOTION: 
                mousex, mousey = event.pos
                clickedButton = getButtonClicked(mousex, mousey)
                if clickedButton == FUSCHIA:
                    sendCoords(mousex, mousey)

        drawCircles(snakeLength)        

        pygame.display.update()
        clock.tick(FPS)

def terminate():
    pygame.quit()
    sys.exit()

def sendCoords(x, y):
    coords.append((x, y))
    colorList.append(random.choice(rainbowTuple))

def checkForQuit():
    for event in pygame.event.get(QUIT):
        terminate()
    for event in pygame.event.get(KEYUP):
        if event.key == K_ESCAPE:
            terminate()
        pygame.event.post(event)

def drawButtons():
    pygame.draw.rect(SCREEN, FUSCHIA, BIGBUTTON)

def drawCircles(snakeLength):
    for i in range(len(coords)):
        pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?
        if i > snakeLength :   
            popList()

def popList():
    coords.pop(0)
    colorList.pop(0)

def getButtonClicked(x, y):
    if BIGBUTTON.collidepoint((x, y)):
        return FUSCHIA
    return None

if __name__ == '__main__':
    main()

Upvotes: 0

Views: 101

Answers (1)

Kevin
Kevin

Reputation: 76194

I suspect this error only occurs when there are multiple mouse move events in the event queue. Normally, pygame is fast enough to render the screen while accruing no more than one new user input event, so sendCoords will only be called once in between drawCircles calls. In that case, coords never exceeds a size of 52. But if several mouse move events accrue (perhaps due to system lag, or because the user is jiggling his mouse really fast), then sendCoords may be called many more times in a row. So by the time drawCircles executes, coords can have 53 elements, or even more.

This becomes a problem when you reach drawCircles:

def drawCircles(snakeLength):
    for i in range(len(coords)):
        pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?
        if i > snakeLength :   
            popList()

Let's say this function executes when coords contains 53 elements, and snakeLength is 50. The loop will iterate normally until i equals 51. Then the i > snakeLength will evaluate to True, and popList will be called. Now coords is one element smaller, and has a length of 52. That iteration of the loop ends, and the next iteration starts. i will equal 52. The pygame.draw.circle line will attempt to access coords[i], but because coords no longer has 53 elements, coords[i] will raise an IndexError trying to access the 53rd element.

Python is not smart enough to understand that the for i in range(len(coords)) loop should end one iteration earlier than usual if coords reduces in size by one. It happily iterates all the way up to the original length of the list, heedless of whether this might cause a crash.

One possible solution is to move popList outside of the list, so the size of coords doesn't change while you're iterating over it.

def drawCircles(snakeLength):
    for i in range(len(coords)):
        pygame.draw.circle(SCREEN, colorList[i], coords[i], abs(i-(len(coords))) * 5, 0)#### Why does the list index go out of range?

    while len(coords) > snakeLength :   
        popList()

You might be thinking "But why is it OK to modify coords' length in this while loop, when it wasn't OK to do it in the for loop?" The critical distinction is the evaluation time of the two statements. range(len(coords)) exectues exactly once before the loop starts, so modifications to coords won't be noticed. but len(coords) > snakeLength executes at the beginning of very iteration of the while loop, so changes to coords gets noticed right away.

Upvotes: 4

Related Questions