[SOLVED] Py.processing bullet and enemy collision


#1

Hello there I am currently dealing with a bullet and enemy collision problem. I’m trying to kill both the enemy and the bullet and remove them from the screen, when they intersect each other. It works once and then I receive the error: “ValueError: list.index(x): x not in list on line 54 in main.py”. If someone could better enhance my code or tell me what I’m doing wrong. That would be much appreciated, thank you. Here is my code: https://trinket.io/python/1c73517ac9

I’m know the problem is here:

for e in enemies: #Iterate through the list of enemies
    e.display() 
    e.goToTarget(myPlayer.xpos, myPlayer.ypos) 
    for l in lasers:
      if l.collision(e.xpos, e.ypos, e.r):
        lasers.remove(l)
        enemies.remove(e) # "ValueError: list.index(x): x not in list on line 54 in main.py"
    if myPlayer.collision(e.xpos, e.ypos, e.r):
      enemies.remove(e)
####################

#2

Hi @warsoldier011,

You just need to add break at the end of your double for loop to terminate it. Otherwise the code keeps calling remove for every iteration.

for e in enemies: #Iterate through the list of enemies
    e.display() 
    e.goToTarget(myPlayer.xpos, myPlayer.ypos) 
    for l in lasers:
      if l.collision(e.xpos, e.ypos, e.r): 
          del lasers[lasers.index(l)]
          del enemies[enemies.index(e)]
          break

#3

Thank you very much. This bug has been stopping me in my progress.


#4

@solub, your solution is buggy! :bug:

When we delete an item from a list, all elements w/ a higher index than the deleted item got their indices subtracted by 1! :face_with_monocle:

They’re left-shifted, and therefore, the next iteration skips an item; which now got the same index as the just deleted item. :arrow_left:

You can see that bug in this sample loop below: :eye: :

nums = list(range(10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for n in nums:
    print n
    nums.remove(n)
print nums # [1, 3, 5, 7, 9] 

The code above was supposed to remove all items from the list. But accomplished only half of it! :crazy_face:

There are some techniques to pull that out though. :bulb:

In Java, my favorite approach is to simply traverse the list backwards instead. :coffee:

Python got reversed(), which returns a backwards iterator for a sequence. :snake:

nums = list(range(10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for n in reversed(nums):
    print n
    nums.remove(n)
print nums # [] 

Now it’s finally worked! All items got backwards removed w/o any errors! :partying_face:

However, remove() ( and also index() ) is a slow method, given it’s gotta search the whole list for the target item. :snail:

A better approach is to use del, since it removes an item by its index. :card_index_dividers:

For that, we’re gonna need to use enumerate() and also turn it into a sequence before calling reversed(): :back:

nums = list(range(10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for i, n in reversed(tuple(enumerate(nums))):
    print i, n
    del nums[i]
print nums # [] 

Here’s @warsoldier011’s excerpt adapted to use a backwards loop w/ del: :angel:

for i, e in reversed(tuple(enumerate(enemies))):
    e.display()
    e.goToTarget(myPlayer.xpos, myPlayer.ypos)

    # for j, l in reversed(tuple(enumerate(lasers))): # no need w/ break
    for j, l in enumerate(lasers):
        if l.collision(e.xpos, e.ypos, e.r):
            del lasers[j]
            del enemies[i]
            break

        l.display()

As a bonus, here’s a more advanced performant example, which replaces the item being removed w/ its list’s tail element; so there’s no index left-shifting at all: :money_mouth_face:

for i in range(len(enemies) - 1, -1, -1):
    e = enemies[i]

    e.display()
    e.goToTarget(myPlayer.xpos, myPlayer.ypos)

    # for j in range(len(lasers) - 1, -1, -1): # no need to be reversed w/ break
    for j in range(len(lasers)):
        l = lasers[j]

        if l.collision(e.xpos, e.ypos, e.r):
            tail = lasers.pop()
            if tail is not l: lasers[j] = tail

            tail = enemies.pop()
            if tail is not e: enemies[i] = tail

            break

        l.display()

#5

Thanks @GoToLoop, really like the reversed - tuple - enumerate combo.


#6

We can also use list() in place of tuple(). :wink:


#7

Just came to my mind: :brain:

Given the inner loop is interrupted by break, got no need for it to be reversed() as the outer loop. :man_facepalming:

So we can replace: :currency_exchange:
for j, l in reversed(tuple(enumerate(lasers))):
w/ just: :upside_down_face:
for j, l in enumerate(lasers):


#8

@GoToLoop Thank you very much for sharing your knowledge. The way you gave examples and showcased the code. Truly helpful, thanks once again.