Pygame Python 列表索引问题

2 投票
3 回答
1209 浏览
提问于 2025-04-16 21:43

我正在尝试制作一个“导弹指挥”游戏的克隆版,以挑战和提升我的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)

每次我点击鼠标按钮时,missile.missileList就会添加所有需要的信息,以便让“导弹”从起点飞到终点。当导弹到达终点时,这些列表中的条目就会被删除。问题是,这样一来,列表的索引就会出错,如果还有其他导弹在跟踪,就会出现错误(列表索引超出范围)。我想在每次调用draw()函数时对列表进行排序会有所帮助,但结果并没有。有没有什么好的建议?

3 个回答

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)
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

正如文档中所说,在遍历列表时删除其中的项目是行不通的,尤其是用范围(range)时,因为这样会导致索引比列表中的项目还多。如果你需要在原地修改列表,可以查看这里


列表推导式其实非常简单,我强烈建议你多尝试一下。这里有个简单的介绍。列表推导式就是通过对每个项目应用一个函数或表达式,把一个列表转换成另一个列表:

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

它们也可以用来过滤列表:

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

它们的工作方式是这样的,每个关键的语法成分在 <<>> 中:

[ <<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 是任何可以被解释为布尔值的表达式或函数。

你可以看到,尽管它们的结构有些不同,但从语法上讲,这些和 for 循环非常相似:

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

注意关键字的顺序是完全一样的 -- 先是 for,然后是 if。唯一的区别是 thing(item) 是在前面,而不是在后面。(但要注意,只有当 <<bolean_test(item)>> 返回真时,它才会被 执行。)这个规则可以很清晰地推广到更复杂的列表推导式(但我这里就不详细讲了)。

这意味着以下代码:

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]

如果你需要进一步的解释,请告诉我。我认为列表推导式是这个语言中非常有用和重要的一部分。你应该尽快学习它们。

4

你在遍历一个列表的时候同时修改它,这通常是个错误。在这种情况下,让代码正常工作的最简单方法就是反向遍历:

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

这样做是有效的,因为你只会删除当前循环中的项目。当你反向遍历时,删除当前项目只会影响你已经看到的那些项目的索引。

撰写回答