3

I am trying to make a simple version of space invaders, I keep running into the same error

ValueError: list.remove(x): x not in list

trying to remove the invaders once they've been hit.

Here is the code.

def killEnemies(bullets, enemies):
    for bullet in bullets:
        for x in enemies:
            for y in x:
                if bullet.top <= y.bottom and bullet.top >= y.top:
                    if bullet.left >= y.left and bullet.right <= y.right:
                        x.remove(y)
                        bullets.remove(bullet)

The problem only occurs when the if statements are True, and the console says that the error occurs in the last line

Here is the rest of the code

import pygame, sys
from pygame.locals import *


pygame.init()

FPS = 30
fpsClock = pygame.time.Clock()

DISPLAYSURF = pygame.display.set_mode((800, 660), 0, 32)
pygame.display.set_caption('Space Invaders')

white = (255, 255, 255)
black = (0, 0, 0)
shipx = 50
shipy = 630
DISPLAYSURF.fill(black)

timer = fpsClock.tick()
time = 0
direction = ''
bullets = []
bulletx = shipx + 25
bullety = shipy - 50
enemies = [[], [], [], [], [], [], []]
shields = []


def drawEnemies(enemies):
    y = 0
    for n in enemies:
        x = 0
        for f in range(7):
            enemy = pygame.draw.rect(DISPLAYSURF, white, (30 + x, 40 + y, 75, 20))
            n.append(enemy)
            x += 110
        y += 30
    return enemies

def killEnemies(bullets, enemies):
    for bullet in bullets:
        for x in enemies:
            for y in x:
                if bullet.top <= y.bottom and bullet.top >= y.top:
                    if bullet.left >= y.left and bullet.right <= y.right:
                    x.remove(y)
                    bullets.remove(bullet)


def moveBullets(bullets):
    for bullet in bullets:
         bullet.top -= 15
    for b in bullets:
        pygame.draw.rect(DISPLAYSURF, white, b)

while True:


    if direction == 'left':
        shipx -= 8
        bulletx -= 8
    elif direction == 'right':
        shipx += 8
        bulletx += 8


    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

        key = pygame.key.get_pressed()

        if key[K_LEFT]:
            direction = 'left'
        elif key[K_RIGHT]:
            direction = 'right'
        elif key[K_SPACE]:
            bullet = pygame.draw.line(DISPLAYSURF, white, (bulletx, bullety), (bulletx, bullety - 25), 2)
            bullets.append(bullet)

        if event.type == KEYUP:
            direction = ''

    time += timer        
    DISPLAYSURF.fill(black)
    pygame.draw.polygon(DISPLAYSURF, white, ((shipx, shipy), (shipx + 25, shipy - 50), (shipx + 50, shipy)), 1)
    drawEnemies(enemies)
    moveBullets(bullets)
    killEnemies(bullets, enemies)
    pygame.display.update()
    fpsClock.tick(FPS)
zacksc
  • 39
  • 1
  • 1
  • 4
  • Post full error – Nabin Aug 15 '17 at 05:58
  • 1
    Looks like we didn't post the full code, or we need the full trackback. My feelings are that you're changing the list while iterating over it, this might be a big issue. – Or Duan Aug 15 '17 at 06:01
  • This is a good question but not a very helpful title for someone who tries to solve this similar scenario in future. – Nabin Aug 15 '17 at 06:10
  • in this line `if bullet.top <= y.bottom and bullet.top >= y.top: ` a condition seems off. – Lafexlos Aug 15 '17 at 06:47
  • Also it would be much easier for you to see what's going on if you print contents of x-y-enemies-bullets on each iteration. – Lafexlos Aug 15 '17 at 06:54

1 Answers1

4

You should not try and mutate a list while you are iterating over it. This is the source of your issues.

For a really simple example, try running:

some_list = [1,2,3,4]
for x in some_list:
    some_list.remove(x)
print(some_list) # Prints [2, 4]

Obviously not the expected outcome - you would perhaps expect the entire list to be empty.

The solution is to either use a list comprehension to create a new list with only your required elements, or make a copy of the list to iterate over. If you go the route of creating a copy, this is as simple as:

def killEnemies(bullets, enemies):
    for bullet in bullets[:]:
        for x in enemies:
            for y in x[:]:
                if bullet.top <= y.bottom and bullet.top >= y.top:
                    if bullet.left >= y.left and bullet.right <= y.right:
                        x.remove(y)
                        bullets.remove(bullet)

Note the list[:] creates a shallow copy of the list, and hence the underlying objects in the original list and the copied list should be the same.

The other issue you are having is in the logic of the for loops. For each bullet, you are iterating over multiple enemies, and need to break out of the two inner loops once each bullet is 'spent'. As written, it seems you are trying to remove each bullet multiple times. I would suggest some code refactoring is in order here:

def killEnemies(bullets, enemies):
    for bullet in bullets[:]:
        iterEnemies(bullets, bullet, enemies)

def iterEnemies(bullets, bullet, enemies):
    for x in enemies:
        for y in x[:]:
            if bullet.top <= y.bottom and bullet.top >= y.top:
                if bullet.left >= y.left and bullet.right <= y.right:
                    x.remove(y)
                    bullets.remove(bullet)
                    return
Andrew Guy
  • 9,310
  • 3
  • 28
  • 40
  • I still have the same error, could there be another problem with the code? – zacksc Aug 15 '17 at 06:30
  • Added extra info. Hope this helps. Looks like you also have some problems with updating enemies once they are destroyed, but I'll leave this for you to solve. ;) – Andrew Guy Aug 15 '17 at 07:10