[SOLVED] Py.processing bullet and enemy collision

@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()
4 Likes