How to make a proper nested for loop for checking each item in an array?

im recreating the asteroids game that the coding train did in processing.py. im in the part of the video when daniel is coding the hit detection of the laser ( i named them bullets) to the asteroids. but when i try to do a nested for in range loop in the draw function so that each bullet checks if it hits any asteroids i get a index out of range error in the console.

here is the segment of code im having trouble with (in the draw function)

for x in range(len(bullet)):
        bullet[x].renderbullet()
        bullet[x].update()
        for i in range(len(asteroids)):
            if (bullet[x].hits(asteroids[i])):
                del asteroids[i]

to test if the nested for loop works properly, i’ve planned that if a bullet impacts an asteroid, it would be deleted.

here is the whole code ( i didnt open any tabs)

class Ship:

  def __init__(self):
      self.ssize = 10
      self.facing = 0
      self.rotation = 0
      self.position = PVector(1000/2, 500/2)
      self.velocity = PVector(0, 0)
      self.isthrusting = False
      
      
  def display(self):
      pushMatrix()
      stroke(255)
      fill(0)
      translate(self.position.x ,self.position.y)
      rotate(self.facing + PI/2 )
      triangle(-self.ssize, self.ssize, self.ssize, self.ssize, 0, -self.ssize)
      popMatrix()
      
  def turn(self):
      self.facing  += self.rotation

  def putrotation(self, a):
      self.rotation = a    
      
  def move(self):
      if self.isthrusting == True:
          self.thrust()
      self.position.add(self.velocity)
      self.velocity.mult(0.95)
      
      
  def thrusting(self, b):
      self.isthrusting = b
          
      
  def thrust(self):
      force = PVector.fromAngle(self.facing)
      self.velocity.add(force)    

  def edges(self):
      if (self.position.x > width + self.ssize):
              self.position.x = -self.ssize
      elif (self.position.x < -self.ssize):
              self.position.x = width + self.ssize
      elif (self.position.y > height + self.ssize):
              self.position.y = - self.ssize
      elif (self.position.y < -self.ssize):
              self.position.y = height + self.ssize                          

class Asteroids:
    
    def __init__(self):
        self.pos = PVector(random(1000), random(500))
        self.vel = PVector.random2D()
        self.r = random(5, 55)
        self.reso = floor(random(5, 10))
        self.offset = []
        for i in range(0, self.reso ):
            self.offset.append(random(-12, 6))
            
    def render(self):
        pushMatrix()
        translate(self.pos.x, self.pos.y)
        stroke(255)
        noFill()
        beginShape()
        for i in range(0, self.reso):
           angle = map(i, 0 , self.reso, 0, TWO_PI)
           self.spike = self.r + self.offset[i]
           x = self.spike*cos(angle)
           y = self.spike*sin(angle)
           vertex(x,y) 
        endShape(CLOSE)       
        popMatrix()
        
    def edges(self):
      if (self.pos.x > width + self.r):
              self.pos.x = -self.r
      elif (self.pos.x < -self.r):
              self.pos.x = width + self.r
      elif (self.pos.y > height + self.r):
              self.pos.y = - self.r
      elif (self.pos.y < -self.r):
              self.pos.y = height + self.r    
              
    def update(self):
          self.pos.add(self.vel)
                  

class Bullet:
      def __init__(self, spos, svel):
          self.pos = PVector(spos.x, spos.y)
          self.vel = PVector.fromAngle(svel)
          self.vel.mult(20)
          
      def update(self):
          self.pos.add(self.vel)
          
      def renderbullet(self):
          pushStyle()
          pushMatrix()
          stroke(255)
          strokeWeight(4)
          point(self.pos.x, self.pos.y)
          popMatrix()
          popStyle()      
    
      def hits(self, asteroids):
          d = dist(self.pos.x, self.pos.y, asteroids.pos.x, asteroids.pos.y)
          if ( d < asteroids.r):
              return True
          else:
              return False
          
    
    
                                                                    
    
asteroids = [] 
for i in range (0, 8):       
    asteroids.append(Asteroids())
ship = Ship()
bullet = []

def keyPressed():
    if key == ' ':
        bullet.append(Bullet(ship.position, ship.facing))
    elif keyCode == RIGHT:
        ship.putrotation(0.1)
    elif keyCode == LEFT:
        ship.putrotation(-0.1)
    elif keyCode == UP:
        ship.thrusting(True)
        

   
    
def keyReleased():
    ship.putrotation(0)    
    ship.thrusting(False)                            

def setup():
    size(1000,500)
    
def draw():
    background(0)
    for x in range(len(bullet)):
        bullet[x].renderbullet()
        bullet[x].update()
        for i in range(len(asteroids)):
            if (bullet[x].hits(asteroids[i])):
                del asteroids[i]
                                     
             
      

                            
    
    ship.display()
    ship.turn()
    ship.move()
    ship.edges()
    for i in range(0,len(asteroids)):
        asteroids[i].render()
        asteroids[i].update()
        asteroids[i].edges()

here is the coding train video im basing myself on

Hi @nothome123,

Does the following snippet solve your issue ?

for b in bullet:
        b.renderbullet()
        b.update()
        for i, a in enumerate(asteroids): 
            if b.hits(a): del asteroids[i]

YES, WOW THANK YOU SOOOOOOO MUCH. may i ask you a favor? could you please explain the code you’ve made? i’m in the process of learning python and i would like to learn as much as i can.

Glad you got it working.

for b in bullet:
        b.renderbullet()
        b.update()

I’m just referring to the objects in the bullet and asteroids array list directly instead of manipulating them with their indices (b instead of bullet[x])

for i, a in enumerate(asteroids)

  • enumerate() is a builtin function that adds a counter to an iterable.
  • a is each Asteroids() object contained in the asteroids array list and i is its index.

It’s helpful because you can loop over asteroids and have the corresponding index counter available at the same time.

1 Like

you’re a genius. thank you so much.

The real reason for i in range(len(asteroids)): fails when you use del asteroids[i] is b/c the len() of the asteroids[] list is decreased, and the loop still “thinks” it has the same len() as when the loop started! :open_mouth:

It iterates over the sequence created by the range() function. It isn’t directly aware of asteroids[] at all! :expressionless:

Now, when we iterate over the target container, be it directly or via the function enumerate(), its for loop becomes aware of the current len() of it. :cowboy_hat_face:

Therefore, when we issue a del command this time, it immediately knows its len() had decreased, and it adjusts the number of times it iterates over it accordingly. :ok_hand:

There’s still a small issue here: Is 1 Bullet object supposed to destroy more than 1 Asteroid? :thinking:

Even after using del once, that loop goes on, checking whether the same Bullet may have destroyed the next Asteroid in the list! :flushed:

If that’s an undesirable behavior, we also need to issue a break statement in order to stop that loop to continue after we had used del: :stop_sign:
https://Docs.Python.org/2.7/reference/simple_stmts.html#break

for i,a in enumerate(asteroids):
    if b.hits(a):
        del asteroids[i]
        break

Coincidentally, break woulda avoided the original for loop w/ range() to crash! :rofl:

And while we’re at it, shouldn’t the Bullet which hits() an Asteroid be del along as well? :smiling_imp:

for i,b in enumerate(bullets):
    b.update()
    b.render()

    for j,a in enumerate(asteroids):
        if b.hits(a):
            del asteroids[j], bullets[i]
            break