开发者

Pygame, Python list index problem

开发者 https://www.devze.com 2023-03-20 18:31 出处:网络
I\'m attempting to create a Missile Command clone to challenge/expand my Python and Pygame skills and knowledge. Below is the code for the game so far (it\'s rudimentary, I\'m a beginner):

I'm attempting to create a Missile Command clone to challenge/expand my Python and Pygame skills and knowledge. Below is the code for the game so far (it's rudimentary, I'm a beginner):

import math
import pygame

class Missile:
def __init__(self, surface):
    self.surface = surface
    self.missileList = []
    self.color = (0, 255, 0)

def draw(self):
    if len(self.missileList) > 0:
        self.missileList.sort()
        for i in range(0, len(self.missileList)):
            if self.missileList[i][1] < self.missileList[i][4]:
                self.missileList.pop(i)
            else:
                self.update(i)
                self.surface.set_at((int(self.missileList[i][0]), int(self.missileList[i][1])), self.color)

def update(self, i):
            self.missileList[i][0] -= self.missileList[i][3] * math.cos(self.missileList[i][2])
            self.missileList[i][1] -= self.missileList[i][3] * math.sin(self.missileList[i][2])

width = 640
height = 480
BGCOLOR = (0, 0, 0)
mousex = 0
mousey = 0
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
startx = int(width / 2)
starty = height - 10
speed = 2
missile = Missile(screen)

running = True
开发者_运维技巧while running:
    screen.fill(BGCOLOR)
    missile.draw()

    for event in pygame.event.get():
    if event.type == pygame.QUIT:
        running = False
        pygame.quit()
    elif event.type == pygame.MOUSEMOTION:
        mousex, mousey = event.pos
    elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
        endx = mousex
        endy = mousey
        trajectory = math.atan2(height - endy, (width / 2) - endx)
        missile.missileList += [[startx, starty, trajectory, speed, endy]]

    pygame.display.flip()
    clock.tick(75)

So each time I click the mouse button, missile.missileList is appended with all the information that is needed to get the 'missile' from start to finish. When a missile reaches its endpoint then those list entries are deleted. The problem is, this throws off the list indices thereafter and throws an error (list index out of range) if there is another missile being tracked. I thought sorting the list at the beginning of each draw() call would help, but it didn't. Any ideas?


You're modifying a list while iterating it, which is usually a mistake. In this case, the simplest way to make the code work is by iterating backwards:

for i in range(len(self.missileList)-1, -1, -1)

That works because you only ever delete the current item in the loop - when you iterate backwards, deleting the current item only affects the indices of those items you've already seen.


The best solution is probably to filter the missileList with a list comprehension first.

self.missileList = [m for m in self.missileList if m[i] >= m[4]]

Then do

for i, m in enumerate(self.missileList): # enumerate instead of range
    # process missile

As is well documented, removing items from lists while iterating over them doesn't work, even (especially) with range, since you wind up with more indices than list items. If you need the list to be altered in-place, see here.


List comprehensions are really quite simple, and I highly recommend playing around with them some more. Here's a quick primer. List comprehensions simply transform one list into another by applying a function or expression to each item in turn:

[1, 2, 3, 4, 5, 6] -> [1, 4, 9, 16, 25, 36]

They can also filter lists:

[1, 2, 3, 4, 5, 6] -> [4, 5, 6]

They work like this, with each of the key syntactic components in <<>>:

[ <<thing_to_do_for_each_item(item)>> <<for item in ['list', 'of', 'items']>> ]

Optionally you can add a predicate at the end to filter

[ <<thing(item)>> <<for item in ['l', 'o', 'i']>> <<if boolean_test(item)>> ]

Where boolean_test is any kind of expression or function that can be interpreted as resulting in a boolean value.

You can see that although they move the bits around, these are really very similar, syntactically speaking, to for statements:

newlist = []
<<for item in ['l', 'o', 'i']>>:
    <<if boolean_test(item)>>:
        newlist.append( <<thing(item)>> )

Note that the order of keywords is exactly the same -- for, then if. The only difference is that thing(item) comes first instead of last. (But note that it is still only executed if <<bolean_test(item)>> returns true.) This rule generalizes fairly cleanly to more complex list comprehensions (but I won't get into that here).

All this means the following code:

old_list = [1, 2, 3, 4, 5, 6]
new_list = []
for i in old_list:
    if i > 3:    
        new_list.append(i ** 2)

Is equivalent to this:

new_list = [i ** 2 for i in old_list if i > 3]

In both cases, the result is a filtered list of squares of the items in the original list:

>>> print old_list, new_list
[1, 2, 3, 4, 5, 6] [16, 25, 36]

Please let me know if you need any further explanation. I think list comprehensions are a really useful and important part of the language. You should learn them as soon as possible.


you're modifying a list in the same loop that you iterate through it. You've (sort of) cheated yourself out of the problem by iterating over the indices. Aside from RichieHindle's solution (which ain't bad), you could separate the modifying portion out from the actual logic.

missleList[] = [missile for missile in missileList if missile[1] >= missile[4]]
for i, missile in enumerate(missileList):
    self.update(i)
    self.surface.set_at((int(self.missile[0]), int(self.missile[1])), self.color)
0

精彩评论

暂无评论...
验证码 换一张
取 消