Python Forum
Pygame deleting list index.
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Pygame deleting list index.
#1
I'm working on this space invaders game and the tail end of the logic is confusing me. There is a nested loop in the conclusion.
i = 0
    while i <len(badguys):
        j = 0
        while j < len(missiles):
            if badguys[i].touching(missiles[j]):
                del badguys[i]
                del missiles[j]
                i -= 1
                break
            j += 1
        i += 1
The program will delete a badguy and missile if they touch but I'm confused as to why you have to use i = i - 1 if you're already deleting the object by its index. Additionally, I don't follow the logic here too well. You break and then add 1 to i? Ugh, it's just confusing me right now. I've tried to find the answer elsewhere but I just dont' get it. I get about the collision detection but not why or how i-= 1 is working or j += 1 or the 1 +=1

Here is the full code.

import pygame,sys,random,time
from pygame.locals import *
pygame.init()
sprite = pygame.image.load("badguy.png")
plane = pygame.image.load("fighter.png")
missile_image = pygame.image.load("missile.png")
missile_image.set_colorkey((255,255,255))
plane.set_colorkey((255,255,255))
sprite.set_colorkey((0,0,0))
clock = pygame.time.Clock()

spawn_time = 0
title = pygame.display.set_caption("Space Invaders")
screen = pygame.display.set_mode((600,600))


class Missile:
    def __init__(self,x):
        self.x = x
        self.y = 510

    def move(self):
        self.y -= 5

    def off_screen(self):
        return self.y < -8

    def draw(self):
        screen.blit(missile_image,(self.x,self.y))

class Fighter:
    def __init__(self):
        self.x = 300
        self.y = 530

    def move(self):
        if pressed_keys[K_LEFT] and self.x > 0:
            self.x -= 5

        if pressed_keys[K_RIGHT] and self.x < 500:
            self.x += 5

    def draw(self):
        screen.blit(plane,(self.x,self.y))

    def fire(self):
        missiles.append(Missile(self.x+50))

class Badguy:
    def __init__(self):
        self.x = random.randint(0,500)
        self.y = 200

    def touching(self,missile):
        return pygame.Rect((self.x,self.y),(70,45)).collidepoint(missile.x,missile.y)

    def move(self):
        self.y += 3

    def draw(self):
        screen.blit(sprite,(self.x,self.y))

    def off_screen(self):
        return self.y > 590


badguys = []
missiles = []
f = Fighter()

while True:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
    if event.type == KEYDOWN and event.key == K_SPACE:
        f.fire()

    pressed_keys = pygame.key.get_pressed()

    screen.fill((0,0,0))


    if time.time() - spawn_time > 0.5:
        badguys.append(Badguy())
        spawn_time = time.time()


    i = 0
    while i < len(badguys):
        badguys[i].move()
        badguys[i].draw()
        if badguys[i].off_screen():
            del badguys[i]
            i = i - 1
        i = i + 1


    i = 0
    while i < len(missiles):
        missiles[i].move()
        missiles[i].draw()
        if missiles[i].off_screen():
            del missiles[i]
            i = i - 1
        i = i + 1


    i = 0
    while i <len(badguys):
        j = 0
        while j < len(missiles):
            if badguys[i].touching(missiles[j]):
                del badguys[i]
                del missiles[j]
                i -= 1
                break
            j += 1
        i += 1

    f.move()
    f.draw()
Reply
#2
When you delete an object from a list. The list rebuild it self and now that index has a new object in it. That why drop the index count to read it.

Adding to index count to read next object in list.

There are different ways to handle this.
1. You could use pygame.sprite.Group.
2. You could create new list.
3. You could create a list to remove objects.
99 percent of computer problems exists between chair and keyboard.
Reply
#3
I always avoid python time. Because pygame has it own system.
Here an example using pygame Sprites and Groups.
import pygame
import random

class Timer:
    ticks = 0

    def __init__(self, interval, callback):
        self.next_tick = pygame.time.get_ticks() + interval
        self.interval = interval
        self.callback = callback

    def elapse(self):
        if self.interval <= 0:
            if Timer.ticks > self.next_tick:
                self.callback(self)

        elif Timer.ticks > self.next_tick:
            self.count = 0
            while Timer.ticks > self.next_tick:
                self.next_tick += self.interval
                self.count += 1

            self.callback(self)

# Simple Scene Interface
class Scene:
    def draw(self, surface, game): pass
    def event(self, event, game): pass
    def update(self, game): pass

class Game:
    def __init__(self, caption, width, height):
        # Basic pygame setup
        pygame.display.set_caption(caption)
        self.rect = pygame.Rect(0, 0, width, height)
        self.surface = pygame.display.set_mode(self.rect.size)
        self.clock = pygame.time.Clock()
        self.running = False
        self.fps = 60
        self.delta = 0

        # Scene Interface
        self.scene = Scene()
        # Set class variable for quick access
        Game.info = self

    def mainloop(self):
        self.running = True
        while self.running:
            for event in pygame.event.get():
                self.scene.event(event, self)

            self.keys = pygame.key.get_pressed()
            Timer.ticks = pygame.time.get_ticks()
            self.scene.update(self)
            self.scene.draw(self.surface, self)
            pygame.display.flip()
            self.delta = self.clock.tick(self.fps)

class Missle(pygame.sprite.Sprite):
    def __init__(self, image, position):
        pygame.sprite.Sprite.__init__(self)
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.topleft = position
        self.speed = 6
        self.timer = Timer(30, self.timer_call)

    def timer_call(self, timer):
        self.rect.y -= self.speed

    def update(self):
        self.timer.elapse()

class Fighter(pygame.sprite.Sprite):
    def __init__(self, image, position):
        pygame.sprite.Sprite.__init__(self)
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.topleft = position
        self.speed = 3
        self.timer = Timer(30, self.timer_call)

    def timer_call(self, timer):
        if Game.info.keys[pygame.K_LEFT]:
            self.rect.x -= self.speed
        elif Game.info.keys[pygame.K_RIGHT]:
            self.rect.x += self.speed

    def update(self):
        self.timer.elapse()

class Badguy(pygame.sprite.Sprite):
    def __init__(self, image, position):
        pygame.sprite.Sprite.__init__(self)
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.topleft = position
        self.speed = 2
        self.timer = Timer(40, self.timer_call)

    def timer_call(self, timer):
        self.rect.y += self.speed

    def update(self):
        self.timer.elapse()

class SpaceInvaders(Scene):
    def __init__(self):
        self.load_images()
        self.sprites = pygame.sprite.Group()
        self.badguys = pygame.sprite.Group()
        self.missles = pygame.sprite.Group()
        self.offscreen = pygame.sprite.Group()

        self.badguy_drop = Timer(1500, self.add_badguy)
        self.create_fighter()

    def load_images(self):
        self.images = {
            'fighter': pygame.Surface((20, 20)),
            'badguy': pygame.Surface((18, 18)),
            'missle': pygame.Surface((4, 8)) }

        self.images['fighter'].fill(pygame.Color('Dodgerblue'))
        self.images['badguy'].fill(pygame.Color('Lawngreen'))
        self.images['missle'].fill(pygame.Color('Firebrick'))

    def create_fighter(self):
        position = Game.info.rect.centerx, Game.info.rect.bottom - 30
        self.fighter = Fighter(self.images['fighter'], position)
        self.sprites.add(self.fighter)

    def add_badguy(self, timer):
        position = random.randint(10, Game.info.rect.width - 28), 20
        badguy = Badguy(self.images['badguy'], position)
        badguy.add(self.badguys, self.sprites, self.offscreen)

    def add_missle(self):
        rect = self.fighter.rect
        position = rect.x + 6, rect.y - 4
        missle = Missle(self.images['missle'], position)
        missle.add(self.missles, self.sprites, self.offscreen)

    def update(self, game):
        self.sprites.update()
        self.fighter.rect.clamp_ip(game.rect)
        self.badguy_drop.elapse()

        for obj in self.offscreen:
            if not game.rect.colliderect(obj.rect):
                obj.kill()

        pygame.sprite.groupcollide(self.missles, self.badguys, True, True)

    def draw(self, surface, game):
        surface.fill(pygame.Color('black'))
        self.sprites.draw(surface)

    def event(self, event, game):
        if event.type == pygame.QUIT:
            game.running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                self.add_missle()

if __name__ == '__main__':
    pygame.init()
    game = Game("Invaders", 600, 500)
    game.scene = SpaceInvaders()
    game.mainloop()
    pygame.quit()
99 percent of computer problems exists between chair and keyboard.
Reply
#4
(Apr-10-2019, 04:53 AM)TheHumbleIdiot Wrote:
i = 0
    while i <len(badguys):
        j = 0
        while j < len(missiles):
            if badguys[i].touching(missiles[j]):
                del badguys[i]
                del missiles[j]
                i -= 1
                break
            j += 1
        i += 1

Let's say there's 3 bad guys. If you're at index 1, that's the second bad guy. If you delete that item, you're still at index 1, but the bad guy that used to be at index 2 is now at index 1. So you don't want to change the index, because there's a different bad guy to check.

To be honest, I'd just make a new list. I think that'd be easier to understand.
for enemy in badguys:
    for missile in missiles:
        if enemy.touching(missile):
            enemy.destroyed = True
            missile.destroyed = True
            break # don't need to check any other missiles for this enemy

# purge destroyed actors from the lists
badguys = [bg in badguys if not bg.destroyed]
missiles = [ms in missiles if not ms.destroyed]
The indexing will be faster, but I don't think it'll be noticeable unless you have thousands of them (some sort of bullet-hell game).
Reply
#5
If i needed to loop a list that i was removing elements from i would just loop a copy of that list. This depends on what i am doing and what the actual lists contains. In this way you are not altering the list you are looping mid-loop. Upon next iteration it will be updated.
for enemy in badguys[:]:
    for missile in missiles][:]:
        if enemy.touching(missile):
            badguys.remove(enemy)
            missles.remove(missle)
This is exactly what i do in the tutorials section:

...
    for e in enemies[:]:
        e.update(delta_time, player)
        if e.dead:
            enemies.remove(e)
        e.draw(screen)
...
    def update_bullets(self, player):
        if self.bullets:
            for obj in self.bullets[:]:
                obj.update('down')
                #check collision
                if obj.rect.colliderect(player.rect):
                    offset_x =  obj.rect.x - player.rect.x 
                    offset_y =  obj.rect.y - player.rect.y
                    if player.mask.overlap(obj.mask, (offset_x, offset_y)):
                        player.take_damage(1)
                        self.bullets.remove(obj)
Recommended Tutorials:
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [IndexError: List Index out of Range] RPG Game Donovan 3 2,221 Aug-31-2020, 12:41 AM
Last Post: metulburr
  podsixnet,pyopengl and pygame api list?(full) hsunteik 4 4,802 Feb-17-2017, 09:44 PM
Last Post: nilamo

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020