Pygame,Python列表索引问题

时间:2011-07-17 22:32:02

标签: python pygame

我正在尝试创建一个导弹命令克隆来挑战/扩展我的Python和Pygame技能和知识。下面是到目前为止游戏的代码(它是基本的,我是初学者):

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)

因此,每次单击鼠标按钮时,navigation.missileList都会附加从开始到结束获取“导弹”所需的所有信息。当导弹到达其终点时,那些列表条目将被删除。问题是,这会抛出列表索引,如果有另一个导弹被跟踪,则会抛出错误(列表索引超出范围)。我认为在每次draw()调用开始时对列表进行排序会有所帮助,但事实并非如此。有什么想法吗?

3 个答案:

答案 0 :(得分:4)

您在迭代时修改列表,这通常是一个错误。在这种情况下,使代码工作的最简单方法是向后迭代:

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

这是有效的,因为你只删除循环中的当前项 - 当你向后迭代时,删除当前项只会影响你已经看过的那些项的索引。

答案 1 :(得分:3)

最好的解决方案可能是首先使用列表推导过滤missileList

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

然后做

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

As is well documented,在迭代它们时从列表中删除项目不起作用,甚至(尤其)使用范围,因为你最终得到的索引比列表项更多。如果您需要就地更改列表,请参阅here


列表理解非常简单,我强烈建议再玩一些。这是一个快速入门。列表推导只是通过依次将函数或表达式应用于每个项目,将一个列表转换为另一个列表:

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

他们还可以过滤列表:

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

他们的工作方式与此相同,每个关键语法组件都在&lt;&lt;&gt;&gt;中:

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

您可以选择在末尾添加谓词来过滤

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

其中boolean_test是任何类型的表达式或函数,可以解释为导致布尔值。

你可以看到,尽管它们会移动这些位,但从语法上讲,它们对于语句非常相似:

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

请注意,关键字的顺序完全相同 - for,然后是if。唯一的区别是thing(item)首先而不是 last 。 (但请注意,如果<<bolean_test(item)>>返回true,它仍然只是已执行。)此规则相当干净地概括为更复杂的列表推导(但我不会在此处讨论)。

所有这些意味着以下代码:

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

等同于:

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

在这两种情况下,结果都是原始列表中项目的正方形的过滤列表:

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

如果您需要进一步说明,请与我们联系。我认为列表推导是该语言非常有用和重要的部分。你应该尽快学习它们。

答案 2 :(得分:0)

您正在修改循环中的列表。你曾经(有点)通过迭代索引来欺骗自己。除了RichieHindle的解决方案(也不错),您可以将修改部分与实际逻辑分开。

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)