FOREACH/DIMPUSH/Extended type and/or DELETE "problem" ?

Previous topic - Next topic

MrTAToad

In my BallZ game, I have some exploding spikes, which generate other spikes.  I found that when calling the initialisation routine using a value return from a passed array, the value is invalid - ie corrupted.

The routine is something like the following :

Code (glbasic) Select
FOREACH loop in self.shapes[]
   AddStuff(loop)

   DELETE loop
NEXT

FUNCTION AddStuff:item as TBall
LOCAL temp as TBall

    temp.Initialise(item.ReturnSomeValue())  <--- Return value from ReturnSomeValue is corrupt (a large negative number)
    DIMPUSH self.shapes[],temp
ENDFUNCTION


I think what is happening is that the initial loop position is getting changed due to the DIMPUSH and then DELETE...

Kitty Hello

Exaclty. FOREACH and ALIAS offer a reference (pointer) to an element in an array. That's very fast.
However, if you change the array (only really harmful way is adding elements or removing them all), the reference might get corrupt. Even worse: There's no way to check this. It might crash, if you're lucky. It might just overwrite some bytes on other game-data in worst case.

Every time you have a reference, be sure _not_ to add something to the array containing this referenced object. Nor the array containing the array containing the object. You get the idea.

MrTAToad


BdR

I'm also working on a game that uses a array of enemies and a custom type. It is an Asteroids type game, so when a big enemy (=asteroid) is destroyed, it will spawn 2 smaller ones.

While the program is looping the Enemies[] array with a FOREACH loop, it also adds new enemies into the Enemies[] array. This works and doesn't crash, but I suspect this isn't the correct way to do it. Anyone have suggestions for the structure of the program?

Here is my example code:
Code (glbasic) Select

CONSTANT SCREEN_XSIZE = 640
CONSTANT SCREEN_YSIZE = 480

CONSTANT SCREEN_XSIZE_H = 320
CONSTANT SCREEN_YSIZE_H = 240

TYPE TEnemy
  x% = 0 // position
  y% = 120
  iSpeed% = 1 // x-axis speed
  iSize = 64 // 16=small 40=medium 64=big
  iHealth% = 3
 
  FUNCTION Update%:
    // move
    self.x = self.x + self.iSpeed
    IF (self.x < 0)            THEN self.x = SCREEN_XSIZE
    IF (self.x > SCREEN_XSIZE) THEN self.x = 0

    // if no health left
    IF (self.iHealth <= 0)
      RETURN FALSE // enemy is dead
    ELSE
      RETURN TRUE // enemy lives
    ENDIF
  ENDFUNCTION
 
  FUNCTION Draw%:
    PRINT self.iHealth, self.x, self.y-16
    DRAWRECT self.x, self.y, self.iSize, self.iSize, RGB((self.iSize*4)-1,256-(self.iSize*4),0)
  ENDFUNCTION
ENDTYPE

GLOBAL Enemies[] AS TEnemy

// -------------------------------------
// main program
// -------------------------------------
RunTestGame()
END

// -------------------------------------
// main test game loop
// -------------------------------------
FUNCTION RunTestGame%:

  // variables
  LOCAL tmpEnemy AS TEnemy
  LOCAL bFire%
 
  // initialise big enemies
  DIMPUSH Enemies[], tmpEnemy
  tmpEnemy.x = 100
  DIMPUSH Enemies[], tmpEnemy
  tmpEnemy.x = 200
  tmpEnemy.y = 160
  tmpEnemy.iSpeed = -1
  DIMPUSH Enemies[], tmpEnemy
  tmpEnemy.x = 300
  DIMPUSH Enemies[], tmpEnemy
   
  WHILE KEY(1) = FALSE
    // very simple player fire
    IF (INKEY$() = " ")
      DRAWRECT SCREEN_XSIZE_H, 0, 1, SCREEN_YSIZE, RGB(255, 0, 0)
      bFire = TRUE
    ELSE
      bFire = FALSE
    ENDIF
   
    // update all enemies
    FOREACH e IN Enemies[]
      // check if enemy hit
      IF (bFire = TRUE) AND (e.x < SCREEN_XSIZE_H) AND (e.x+e.iSize > SCREEN_XSIZE_H)
        e.iHealth = e.iHealth - 1
      ENDIF
      // update all enemies
      IF (e.Update() = FALSE)
        // spawn new enemies?
        LOCAL newEnemy AS TEnemy
        SELECT e.iSize
          CASE 64
            // big enemy destroyed, spawn 2 medium enemies
            newEnemy.x = e.x
            newEnemy.y = e.y
            newEnemy.iSize = 40
            DIMPUSH Enemies[], newEnemy
           
            newEnemy.iSpeed = -1
            DIMPUSH Enemies[], newEnemy
          CASE 40
            // medium enemy destroyed, spawn 2 small enemies
            newEnemy.x = e.x
            newEnemy.y = e.y
            newEnemy.iSize = 16
            DIMPUSH Enemies[], newEnemy

            newEnemy.iSpeed = -1
            DIMPUSH Enemies[], newEnemy
          CASE 16
            // small enemy destroyed, do nothing
        ENDSELECT

        // remove current enemy
        DELETE e
      ENDIF
    NEXT // e

    // draw all enemies
    FOREACH e IN Enemies[]
      e.Draw()
    NEXT // e
   
    // showall
    SHOWSCREEN
  WEND
 
ENDFUNCTION

MrTAToad

In BallZ I have a list of objects that need to be added, which is then deleted as each is added.