Pygame Python 列表索引问题
我正在尝试制作一个“导弹指挥”游戏的克隆版,以挑战和提升我的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 个回答
你在遍历一个列表的同时还在修改它。这种做法有点“偷懒”,因为你是通过索引来遍历的。除了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)
最好的解决办法可能是先用列表推导式来过滤一下 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]
如果你需要进一步的解释,请告诉我。我认为列表推导式是这个语言中非常有用和重要的一部分。你应该尽快学习它们。
你在遍历一个列表的时候同时修改它,这通常是个错误。在这种情况下,让代码正常工作的最简单方法就是反向遍历:
for i in range(len(self.missileList)-1, -1, -1)
这样做是有效的,因为你只会删除当前循环中的项目。当你反向遍历时,删除当前项目只会影响你已经看到的那些项目的索引。