2011-07-17 78 views
2

我正在嘗試創建一個導彈命令克隆來挑戰/擴展我的Python和Pygame技能和知識。下面是遊戲到目前爲止的代碼(這是最基本的,我是一個初學者):Pygame,Python列表索引問題

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()調用開始時對列表進行排序會有所幫助,但是並沒有。有任何想法嗎?

回答

4

您正在修改列表並重復它,這通常是一個錯誤。在這種情況下,爲了使代碼工作的最簡單的方法是通過遍歷倒退:

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

這工作,因爲你永遠只刪除當前項目中循環 - 當你遍歷倒退,刪除當前項隻影響您已經看過的那些項目的索引。

+0

,完美的工作。我還沒有想到我在修改它的事實,而它仍然在迭代它。感謝您的快速回復。 –

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] 

他們的工作就是這樣,每個鍵的在< <句法成分>>:

[ <<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)第一而不是最後。 (但是請注意,它仍然只是執行如果<<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] 

請讓我知道你是否需要任何進一步的解釋。我認爲列表理解是語言中非常有用和重要的部分。你應該儘快學習它們。

+0

列表理解仍然是多一點我的頭在這一點上,雖然我希望能換我的大腦周圍的未來。每次我嘗試過對它們進行試驗時,最終都意識到我仍然不明白髮生了什麼。謝謝。 –

+1

@Matt喜歡,我明白,但看看上面。列表理解非常重要,值得儘快學習,而不是晚一點。 – senderle

+0

很好的解釋。雖然通過閱讀關於「事物(物品)」何時發生的位,但是在使用該過濾器的列表理解時不會執行更多操作嗎? – thegrinner

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)