Chaque fois que j'exécute ce programme, j'obtiens cette erreur:
ValueError: list.remove(x): x not in list
J'essaie de réduire la santé d'un seul étranger chaque fois qu'il est touché par un boulon. Cet étranger unique devrait également être détruit si sa santé est de <= 0
. De même, le verrou serait également détruit. Voici mon code:
def manage_collide(bolts, aliens):
# Check if a bolt collides with any alien(s)
for b in bolts:
for a in aliens:
if b['rect'].colliderect(a['rect']):
for a in aliens:
a['health'] -= 1
bolts.remove(b)
if a['health'] == 0:
aliens.remove(a)
# Return bolts, aliens dictionaries
return bolts, aliens
ValueError
se produit sur la ligne aliens.remove(a)
. Juste pour clarifier, les aliens
et bolts
sont des listes de dictionnaires.
Qu'est-ce que je fais mal?
Vous ne devez pas supprimer des éléments d'une liste que vous parcourez. Créez plutôt une copie:
for a in aliens[:]:
et
for b in bolts[:]:
La modification d'une liste tout en bouclant dessus affecte la boucle:
>>> lst = [1, 2, 3]
>>> for i in lst:
... print i
... lst.remove(i)
...
1
3
>>> lst
[2]
La suppression d'éléments d'une liste que vous parcourez deux fois rend les choses encore plus compliquées, ce qui entraîne une erreur ValueError:
>>> lst = [1, 2, 3]
>>> for i in lst:
... for a in lst:
... print i, a, lst
... lst.remove(i)
...
1 1 [1, 2, 3]
1 3 [2, 3]
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: list.remove(x): x not in list
Lorsque vous créez une copie des listes que vous modifiez au niveau chaque de vos boucles, vous évitez le problème:
>>> lst = [1, 2, 3]
>>> for i in lst[:]:
... for i in lst[:]:
... print i, lst
... lst.remove(i)
...
1 [1, 2, 3]
2 [2, 3]
3 [3]
En cas de collision, il suffit de retirer le boulon b
ne fois, pas dans la boucle où vous blessez les extraterrestres. Nettoyez les extraterrestres séparément plus tard:
def manage_collide(bolts, aliens):
for b in bolts[:]:
for a in aliens:
if b['rect'].colliderect(a['rect']) and a['health'] > 0:
bolts.remove(b)
for a in aliens:
a['health'] -= 1
for a in aliens[:]:
if a['health'] <= 0:
aliens.remove(a)
return bolts, aliens
Il y a un bogue dans votre code qui cause cela. Votre code, simplifié, ressemble à:
for b in bolts:
for a in aliens:
for a in aliens:
bolts.remove(b)
Cela vous oblige à faire une boucle sur aliens
plusieurs fois pour chaque entrée dans b
. Si le b est supprimé sur la première boucle sur aliens
alors, quand il boucle dessus une deuxième fois, vous obtiendrez une erreur.
Quelques choses à corriger. Commencez par changer la boucle interne sur aliens
pour utiliser autre chose que a
, donc:
for b in bolts:
for a in aliens:
for c in aliens:
if hit:
bolts.remove(b)
Deuxièmement, ne supprimez b
de bolts
qu'une seule fois. alors:
for b in bolts:
for a in aliens:
should_remove = False
for c in aliens:
if hit:
should_remove = True
if should_remove:
bolts.remove(b)
Il y a aussi d'autres problèmes avec ce code, je pense, mais c'est la cause de votre problème principal. Le message de Martijn peut également aider.
Donnez également aux boulons une "santé", initialisée à 1. Ensuite, vous pouvez faire une boucle imbriquée pour calculer tous les dégâts, et deux "boucles" non imbriquées distinctes pour supprimer tout ce qui est "mort". Sauf, ne le faites pas tout à fait comme ça, car vous ne voulez toujours pas modifier la liste que vous parcourez. Faire une copie est encore trop compliqué. Ce que vous voulez vraiment faire est directement construire une nouvelle liste uniquement des choses encore "vivantes", et vous pouvez le faire de manière descriptive avec des compréhensions de liste (ou comme indiqué ici, avec filter
).
# for example
class Alien:
# ... other stuff
def damage(self): self.hp -= 1
def alive(self): return self.hp > 0
# similarly for Bolt
def collide(an_alien, a_bolt):
# etc.
def handle_collisions(aliens, bolts):
for a in aliens:
for b in bolts:
if collide(a, b):
a.damage()
b.damage()
return list(filter(Alien.alive, aliens)), list(filter(Bolt.alive, bolts))