Reputation: 31
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
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