0

I'm having a different error this time when following Tech with Tim's Pygame tutorial (https://www.youtube.com/watch?v=Q-__8Xw9KTM) about creating Space Invaders with Pygame. I'm 1hr36m into the video. There's something that's going wrong with the lasers. These are the versions, code and errors. What am I doing wrong?

Pycharm 2020.3.5., pygame 2.0.1 (SDL 2.0.14, Python 3.8.10)

Errors

Traceback (most recent call last):
File "C:/PycharmProjects/Pygames/Space Invaders/main.py", line 222, in <module>
main()
  File "C:/PycharmProjects/Pygames/Space Invaders/main.py", line 219, in main
    player.move_lasers(-laser_vel, enemies)
  File "C:/PycharmProjects/Pygames/Space Invaders/main.py", line 110, in move_lasers
    self.lasers.remove(laser)
ValueError: list.remove(x): x not in list

Process finished with exit code 1

Code


    import pygame
    import os
    import time
    import random
    pygame.font.init()
    
    WIDTH, HEIGHT = 750, 620
    WIN = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Space Invaders")
    
    # load images
    RED_SPACE_SHIP = pygame.image.load(os.path.join("assets", "pixel_ship_red_small.png"))
    GREEN_SPACE_SHIP = pygame.image.load(os.path.join("assets", "pixel_ship_green_small.png"))
    BLUE_SPACE_SHIP = pygame.image.load(os.path.join("assets", "pixel_ship_blue_small.png"))
    
    # Player player
    YELLOW_SPACE_SHIP = pygame.image.load(os.path.join("assets", "pixel_ship_yellow.png"))
    
    # Lasers
    RED_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_red.png"))
    GREEN_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_green.png"))
    BLUE_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_blue.png"))
    YELLOW_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_yellow.png"))
    
    # Background
    BG = pygame.transform.scale(pygame.image.load(os.path.join("assets", "background-black.png")), (WIDTH, HEIGHT))
    
    class Laser:
        def __init__(self, x, y, img):
            self.x = x
            self.y = y
            self.img = img
            self.mask = pygame.mask.from_surface(self.img)
    
        def draw(self, window):
            window.blit(self.img, (self.x, self.y))
    
        def move(self, vel):
            self.y += vel
    
        def off_screen(self, height):
            return not (self.y <= height and self.y >= 0)
    
        def collision(self, obj):
            return collide(self, obj)
    
    class Ship:
        COOLDOWN = 30
    
        def __init__(self, x, y, health=100):
            self.x = x
            self.y = y
            self.health = health
            self.ship_img = None
            self.laser_img = None
            self.lasers = []
            self.cool_down_counter = 0
    
        def draw(self, window):
            window.blit(self.ship_img, (self.x, self.y))
            for laser in self.lasers:
                laser.draw(window)
    
        def move_lasers(self, vel, obj):
            self.cooldown()
            for laser in self.lasers:
                laser.move(vel)
                if laser.off_screen(HEIGHT):
                    self.lasers.remove(laser)
                elif laser.collision(obj):
                    obj.health -= 10
                    self.lasers.remove(laser)
    
        def cooldown(self):
            if self.cool_down_counter >= self.COOLDOWN:
                self.cool_down_counter = 0
            elif self.cool_down_counter > 0:
                self.cool_down_counter += 1
    
        def shoot(self):
            if self.cool_down_counter == 0:
                laser = Laser(self.x, self.y, self.laser_img)
                self.lasers.append(laser)
                self.cool_down_counter = 1
    
        def get_width(self):
            return self.ship_img.get_width()
    
        def get_height(self):
            return self.ship_img.get_height()
    
    class Player(Ship):
        def __init__(self, x, y, health=100):
            super().__init__(x, y, health)
            self.ship_img = YELLOW_SPACE_SHIP
            self.laser_img = YELLOW_LASER
            self.mask = pygame.mask.from_surface(self.ship_img)
            self.max_health = health
    
        def move_lasers(self, vel, objs):
            self.cooldown()
            for laser in self.lasers:
                laser.move(vel)
                if laser.off_screen(HEIGHT):
                    self.lasers.remove(laser)
                else:
                    for obj in objs:
                        if laser.collision(obj):
                            objs.remove(obj)
                            self.lasers.remove(laser)
    
    class Enemy(Ship):
        COLOR_MAP = {
                    "red": (RED_SPACE_SHIP, RED_LASER),
                    "green": (GREEN_SPACE_SHIP, GREEN_LASER),
                    "blue": (BLUE_SPACE_SHIP, BLUE_LASER)
                     }
    
        def __init__(self, x, y, color, health=100):
            super().__init__(x, y, health)
            self.ship_img, self.laser_img = self.COLOR_MAP[color]
            self.mask = pygame.mask.from_surface(self.ship_img)
    
        def move(self, vel):
            self.y += vel
    
    def collide(obj1, obj2):
        offset_x = obj2.x - obj1.x
        offset_y = obj2.y - obj1.y
        return obj1.mask.overlap(obj2.mask, (offset_x, offset_x)) != None
    
    def main():
        run = True
        FPS = 60
        level = 0
        lives = 5
        main_font = pygame.font.SysFont("comicsans", 50)
        lost_font = pygame.font.SysFont("comicsans", 60)
    
        enemies = []
        wave_length = 5
        enemy_vel = 1
    
        player_vel = 5
        laser_vel = 5
    
        player = Player(350, 500)
    
        clock = pygame.time.Clock()
    
        lost = False
        lost_count = 0
    
        def redraw_window():
            WIN.blit(BG, (0, 0))
            # draw text
            level_label = main_font.render(f"Level: {level}", 1, (255, 255, 255))
            lives_label = main_font.render(f"Lives: {lives}", 1, (255, 255, 255))
    
            WIN.blit(lives_label, (10, 10))
            WIN.blit(level_label, (WIDTH - level_label.get_width() - 10, 10))
    
            for enemy in enemies:
                enemy.draw(WIN)
    
            player.draw(WIN)
    
            if lost:
                lost_label = lost_font.render("You lost!", 1, (255, 255, 255))
                WIN.blit(lost_label, (WIDTH/2 - lost_label.get_width()/2, HEIGHT/2 - lost_label.get_height()/2))
    
    
            pygame.display.update()
    
        while run:
            clock.tick(FPS)
            redraw_window()
    
            if lives <= 0 or player.health <= 0:
                lost = True
                lost_count += 1
    
            if lost:
                if lost_count > FPS * 3:
                    run = False
                else:
                    continue
    
            if len(enemies) == 0:
                level += 1
                wave_length += 5
                for i in range(wave_length):
                    enemy = Enemy(random.randrange(50, WIDTH - 100), random.randrange(-1500, -100), random.choice(["red", "green", "blue"]))
                    enemies.append(enemy)
    
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    run = False
    
            keys = pygame.key.get_pressed()
            if keys[pygame.K_LEFT] and player.x - player_vel > 0:
                player.x -= player_vel
            if keys[pygame.K_RIGHT] and player.x + player_vel + player.get_width() < WIDTH:
                player.x += player_vel
            if keys[pygame.K_UP] and player.y - player_vel > 0:
                player.y -= player_vel
            if keys[pygame.K_DOWN] and player.y + player_vel + player.get_height() < HEIGHT:
                player.y += player_vel
            if keys[pygame.K_SPACE]:
                player.shoot()
    
            for enemy in enemies[:]:
                enemy.move(enemy_vel)
                enemy.move_lasers(laser_vel, player)
                if enemy.y + enemy.get_height() > HEIGHT:
                    lives -= 1
                    enemies.remove(enemy)
    
            player.move_lasers(-laser_vel, enemies)
    
    
    main()

Iris S.
  • 11
  • 2
  • In `Player.move_lasers` you may attempt to remove the same laster from `self.lasers` multiple times if it collides with more than one object. Not necessarily related, it's not safe to remove objects from a list you are iterating over, as this will cause the iterator to not see the next object after the one you remove. – chepner Jun 18 '21 at 18:17
  • I think your issue is trying to remove things from a list while you are iterating through it and/or removing items twice. One easy solution is to loop through the list twice. First, just do the collisions and keep track of which items *should* be removed, like by setting a `is_deleted` property to `True` on each one. Then, cull all the ones that should be deleted with either a `while` loop or a list comprehension. Something like `self.items = [x for x in self.items if not x.is_deleted]`. That will fix both your issues – QuinnFreedman Jun 18 '21 at 18:20
  • That issue makes sense! I did some more reading and first tried the 'break' command after every 'self.lasers.remove(laser)' to avoid it from looping again when the laser has been removed. It has fixed this error but now I run into another funny bug. The laser only shows when I miss the enemy. When I'm going to hit the enemy, the laser is already removed before shooting it while the enemy is removed to. So you wont see the laser when Pygame already somehow knows it's going to hit, only when it will miss its target. It's curious how it knows beforehand when the masks haven't even overlapped. – Iris S. Jun 18 '21 at 18:37

0 Answers0