Spawn 1 of each value only randomly

In my game I want to have 3 spawns appear next to each other (basically in a row covering 3 columns). I want the game to spawn 1 cross, 1 circle, 1 rectangle in the row of 3 and not 2 of the same shape (example 1 cross, 1 circle, 1 cross). They then get destroyed before the spawn looks to make a new batch, so it just loops on from there. 

I can’t get a proper solution to first: Spawn a line of enemies (3 objects only) and then preventing doubles.

I have managed to get different patterns of colours, and spawning them in a line side by side, but it isn’t efficient enough since I created hard copies of each object and manually position the X myself, thus creating a huge batch of code - but it does work.

As you will see from the code below, Im making my own patterns from the ® value to make sure I don’t get any repeat shapes. Ive also set the X values of each shape so that they are in 3 separate columns but in a line from left to right. I know this isn’t the right way to do this, and that there is probably a much simpler way but Im stuck. 

local function spawnEnemy() local moveRate = 8000 - (100 \* wave) local r = math.floor(math.random() \* 3) if(r == 0) then blue = display.newSprite(imageSheetRectangle, sequenceDataRectangle ) blue.name = "Blue" physics.addBody(blue, { isSensor = true }) blue.x = 40 blue:setSequence("blueRect") blue:setFrame(3) blue.y = display.contentHeight - 550 blue:toFront() enemyMovement = transition.to( blue, { time=moveRate, y=600} ) enemyGroup:insert(blue) red = display.newSprite(imageSheetCross, sequenceDataCross) red.name = "Red" physics.addBody(red, {isSensor = true }) red.x = 160 red:setSequence("blueCross") red:setFrame(1) red:toFront() red.y = display.contentHeight - 550 enemyMovement = transition.to( red, { time=moveRate, y=600} ) enemyGroup:insert(red) green = display.newSprite(imageSheetCircle, sequenceDataCircle ) green.name = "Green" physics.addBody(green, { isSensor = true }) green.x = 270 green:setSequence("blueCirc") green:setFrame(2) green:toFront() green.y = display.contentHeight - 550 enemyMovement = transition.to( green, { time=moveRate, y=600} ) enemyGroup:insert(green) elseif(r == 1) then red = display.newSprite(imageSheetCross, sequenceDataCross) red.name = "Red" physics.addBody(red, {isSensor = true }) red.x = 40 red:setSequence("blueCross") red:setFrame(1) red:toFront() red.y = display.contentHeight - 550 enemyMovement = transition.to( red, { time=moveRate, y=600} ) enemyGroup:insert(red) green = display.newSprite(imageSheetCircle, sequenceDataCircle ) green.name = "Green" physics.addBody(green, { isSensor = true }) green.x = 160 green:setSequence("blueCirc") green:setFrame(2) green:toFront() green.y = display.contentHeight - 550 -- 550 default enemyMovement = transition.to( green, { time=moveRate, y=600} ) enemyGroup:insert(green) blue = display.newSprite(imageSheetRectangle, sequenceDataRectangle ) blue.name = "Blue" physics.addBody(blue, { isSensor = true }) blue.x = 270 blue:setSequence("blueRect") blue:setFrame(3) blue.y = display.contentHeight - 550 blue:toFront() enemyMovement = transition.to( blue, { time=moveRate, y=600} ) enemyGroup:insert(blue) elseif(r == 2) then --green = display.newRect(0,0,20,20) green = display.newSprite(imageSheetCircle, sequenceDataCircle ) green.name = "Green" physics.addBody(green, { isSensor = true }) green.x = 40 green:setSequence("blueCirc") green:setFrame(2) green:toFront() green.y = display.contentHeight - 550 -- 550 default enemyMovement = transition.to( green, { time=moveRate, y=600} ) enemyGroup:insert(green) blue = display.newSprite(imageSheetRectangle, sequenceDataRectangle ) blue.name = "Blue" physics.addBody(blue, { isSensor = true }) blue.x = 160 blue:setSequence("blueRect") blue:setFrame(3) blue.y = display.contentHeight - 550 blue:toFront() enemyMovement = transition.to( blue, { time=moveRate, y=600} ) enemyGroup:insert(blue) red = display.newSprite(imageSheetCross, sequenceDataCross) red.name = "Red" physics.addBody(red, {isSensor = true }) red.x = 270 red:setSequence("blueCross") red:setFrame(1) red:toFront() red.y = display.contentHeight - 550 enemyMovement = transition.to( red, { time=moveRate, y=600} ) enemyGroup:insert(red) end

Hey dip,

there are several ways to achieve this. (especially with a small set of possibilities as in your case, 3! => 6)

I will try to give you an universal aproach. First of all we create an array of values, each representing one of your enemy types. After that we shuffle the array and loop through it to create the enemys.

local function shuffleArray(array)     for i = #array, 2, -1 do         local randomIndex = math\_random(1, i)         array[i], array[randomIndex] = array[randomIndex], array[i]     end     return array end local function spawnEnemys()     local enemyList = {"blue", "red", "green"}     shuffleArray(enemyList)          for i=1, #enemyList do         local enemyName = enemyList[i]         local posX = (i-1)\*120 + 40                  if enemyName == "blue" then             --create blue enemy         elseif enemyName == "red" then             --create red enemy         else             --create green enemy         end     end end spawnEnemys()

You can simplify even more, but that should be good in most cases.

Hope that helps :slight_smile:

Hey torbenratzlaff, 

Thanks for the help there, that was actually perfect and works exactly as I wanted. I appreciate you taking the time to explain the code and how it works too.

Glad it worked for you.

You are welcome :slight_smile:

sorry for reviving this thread from the dead, the code still works perfect so theres nothing wrong with that. I’ve been trying to get a function to re shuffle the pack again by just tapping the screen so that the player can shuffle as much as they want only after the first set is complete and visible. 

I have had a go at this and used a shuffle function which, judging from the console prints, is shuffling my images perfectly on each tap however the images don’t swap with them. I understand I would need to somehow destroy the current images first, or is there another way ?

Here is the function I use to shuffle:

function shuffleColors2(tablename) local t = tablename function table.shuffle(t) print("Shuffling Done") math.randomseed(os.time()) assert(t, "table.shuffle() expected a table, got nil") local iterations = #t local j for i = iterations, 2, -1 do j = math.random(i) t[i], t[j] = t[j], t[i] end end return table.shuffle(t) end function shuffleColors( event ) local colors = enemyList colors = shuffleColors2(colors) end

First of all, you should not the shuffle function inside antoher function.

Just lokalize it and call if from inside (see code below) or add it to the table library an add it from there.

Additionally you only need to set the random seed once at the start of your app.

math.randomseed(os.time()) local function table\_shuffle(t) assert(t, "table.shuffle() expected a table, got nil") for i = #t, 2, -1 do local j = math.random(1, i) t[i], t[j] = t[j], t[i] end print("Shuffling Done") end table.shuffle = table\_shuffle function shuffleColors( event ) local colors = enemyList colors = table\_shuffle(colors) end

Concerning your question there are two main ways to go.

  1. Remove the existing image (display.remove) and create new ones.

  2. Reuse the existing images by simply just repositioning them (better solution).

For both options you would need to store the images. I suggest using a table for that.

I did option 1 where I destroy the original 3 objects using a display.remove and immediately I create a new set but its bit heavy on performance. I agree with you that simply repositioning them every tap would work better. I have no idea how I could approach this though. I have a table created from the code you posted towards the top of this page which was inside the spawnEnemy function.

local enemyList = {"blue", "red", "green"}

And the current spawn positions are :

local posX = (i-1)\*120 + 40

What would you do to make them swap around randomly but never on top of each other ?

Cheers 

Ok, here’s my version for you.

We create the 3 images at the top and hide them for later usage (a technique you should use in many cases).

We also create a copy of the enemy list for the shuffling (letting the original list unchanged).

local enemyList = {"blue", "red", "green"} local enemyGraphics = {} local enemyColors = {} for i=1, #enemyList do local color = enemyList[i] enemyColors[i] = color local enemy if enemyName == "blue" then --create blue enemy enemy = display.newRect(0, 0, 50, 50) enemy:setFillColor(0, 0, 1) elseif enemyName == "red" then --create red enemy  enemy = display.newRect(0, 0, 50, 50) enemy:setFillColor(1, 0, 0) else --create green enemy enemy = display.newRect(0, 0, 50, 50) enemy:setFillColor(0, 1, 0) end enemy.isVisible = false enemyGraphics[color] = enemy end local function table\_shuffle(t) assert(t, "table.shuffle() expected a table, got nil") for i = #t, 2, -1 do local j = math.random(1, i) t[i], t[j] = t[j], t[i] end print("Shuffling Done") end table.shuffle = table\_shuffle function shuffleColors( event ) table\_shuffle(enemyColors) for i=1, #enemyColors do local enemy = enemyGraphics[enemyColors[i]] local posX = (i-1)\*120 + 40 enemy.x = posX enemy.isVisible = true end end

Try it like this :slight_smile:

Hey torbenratzlaff, i appreciate the help your giving me but the code above doesn’t seem to be working - I think I might have gotten bit lost. 

The code above where you create new objects (local enemy), does that replace the actual spawnEnemy function which I currently have ? as shown below. 

local function spawnEnemys() shuffleArray(enemyList) for i=1, #enemyList do local enemyName = enemyList[i] local posX = (i-1)\*120 + 40 local moveRate = 800 if enemyName == "blue" then --create blue enemy blue = display.newImageRect("Shapes/CB.png", 52,52 ) blue.name = "Blue" physics.addBody(blue, {isSensor=true}) blue.x = posX blue.y = display.contentHeight - 550 enemyMovement1 = transition.to( blue, { time=moveRate, y=210,transition=easing.inOutQuad} ) enemyGroup:insert(blue) elseif enemyName == "red" then -- same as above for the rest

Hey dip,

yeah, I forgot incorporating your already existing spawn mechanics.

I implemented them below and added some comments for better understanding.

local enemyList = {"blue", "red", "green"} --this is the list of enemy names local enemyColors = {} --this is the copy of the enemy list that is used for shuffling local enemyGraphics = {} --this table holds the enemy graphics for positioning them later for i=1, #enemyList do local color = enemyList[i] enemyColors[i] = color --copy enemy name into the enemyClors table local enemy if enemyName == "blue" then --create blue enemy enemy = display.newSprite(imageSheetRectangle, sequenceDataRectangle) enemy.name = "Blue" physics.addBody(enemy, { isSensor = true }) enemy:setSequence("blueRect") enemy:setFrame(3) elseif enemyName == "red" then --create red enemy enemy = display.newSprite(imageSheetCross, sequenceDataCross) enemy.name = "Red" physics.addBody(enemy, {isSensor = true }) enemy:setSequence("blueCross") enemy:setFrame(1) else --create green enemy enemy = display.newSprite(imageSheetCircle, sequenceDataCircle ) enemy.name = "Green" physics.addBody(enemy, { isSensor = true }) enemy:setSequence("blueCirc") enemy:setFrame(2) end   enemyGroup:insert(enemy) enemy.isVisible = false --hide graphic initially enemyGraphics[color] = enemy --add graphic to the enemyGraphics table for later use end local function table\_shuffle(t) --local table shuffling funtion assert(t, "table.shuffle() expected a table, got nil") for i = #t, 2, -1 do local j = math.random(1, i) t[i], t[j] = t[j], t[i] end print("Shuffling Done") end table.shuffle = table\_shuffle function shuffleColors() --call this function whenever you want to position or reposition the enemys table\_shuffle(enemyColors) --colors are shuffled for i=1, #enemyColors do local enemy = enemyGraphics[enemyColors[i]] --the previously created graphic is picked local posX = (i-1)\*120 + 40 --x position is estimated enemy.x = posX enemy.y = display.contentHeight - 550 enemy.isVisible = true --show the graphic if enemy.movement then transition.cancel(enemy.movement); enemy.movement = nil end --if the graphic moved before, we want to cancel that movement enemy.movement = transition.to(blue, { time=moveRate, y=600}) --store transition so it can be cancelled later end end

Hope it’s clearer now :slight_smile:

Aha I see now. Yeah that’s perfect, thanks for the help torbenratzlaff, and also for the great explanations.

You’re welcome :slight_smile:

Hey dip,

there are several ways to achieve this. (especially with a small set of possibilities as in your case, 3! => 6)

I will try to give you an universal aproach. First of all we create an array of values, each representing one of your enemy types. After that we shuffle the array and loop through it to create the enemys.

local function shuffleArray(array)     for i = #array, 2, -1 do         local randomIndex = math\_random(1, i)         array[i], array[randomIndex] = array[randomIndex], array[i]     end     return array end local function spawnEnemys()     local enemyList = {"blue", "red", "green"}     shuffleArray(enemyList)          for i=1, #enemyList do         local enemyName = enemyList[i]         local posX = (i-1)\*120 + 40                  if enemyName == "blue" then             --create blue enemy         elseif enemyName == "red" then             --create red enemy         else             --create green enemy         end     end end spawnEnemys()

You can simplify even more, but that should be good in most cases.

Hope that helps :slight_smile:

Hey torbenratzlaff, 

Thanks for the help there, that was actually perfect and works exactly as I wanted. I appreciate you taking the time to explain the code and how it works too.

Glad it worked for you.

You are welcome :slight_smile:

sorry for reviving this thread from the dead, the code still works perfect so theres nothing wrong with that. I’ve been trying to get a function to re shuffle the pack again by just tapping the screen so that the player can shuffle as much as they want only after the first set is complete and visible. 

I have had a go at this and used a shuffle function which, judging from the console prints, is shuffling my images perfectly on each tap however the images don’t swap with them. I understand I would need to somehow destroy the current images first, or is there another way ?

Here is the function I use to shuffle:

function shuffleColors2(tablename) local t = tablename function table.shuffle(t) print("Shuffling Done") math.randomseed(os.time()) assert(t, "table.shuffle() expected a table, got nil") local iterations = #t local j for i = iterations, 2, -1 do j = math.random(i) t[i], t[j] = t[j], t[i] end end return table.shuffle(t) end function shuffleColors( event ) local colors = enemyList colors = shuffleColors2(colors) end

First of all, you should not the shuffle function inside antoher function.

Just lokalize it and call if from inside (see code below) or add it to the table library an add it from there.

Additionally you only need to set the random seed once at the start of your app.

math.randomseed(os.time()) local function table\_shuffle(t) assert(t, "table.shuffle() expected a table, got nil") for i = #t, 2, -1 do local j = math.random(1, i) t[i], t[j] = t[j], t[i] end print("Shuffling Done") end table.shuffle = table\_shuffle function shuffleColors( event ) local colors = enemyList colors = table\_shuffle(colors) end

Concerning your question there are two main ways to go.

  1. Remove the existing image (display.remove) and create new ones.

  2. Reuse the existing images by simply just repositioning them (better solution).

For both options you would need to store the images. I suggest using a table for that.

I did option 1 where I destroy the original 3 objects using a display.remove and immediately I create a new set but its bit heavy on performance. I agree with you that simply repositioning them every tap would work better. I have no idea how I could approach this though. I have a table created from the code you posted towards the top of this page which was inside the spawnEnemy function.

local enemyList = {"blue", "red", "green"}

And the current spawn positions are :

local posX = (i-1)\*120 + 40

What would you do to make them swap around randomly but never on top of each other ?

Cheers 

Ok, here’s my version for you.

We create the 3 images at the top and hide them for later usage (a technique you should use in many cases).

We also create a copy of the enemy list for the shuffling (letting the original list unchanged).

local enemyList = {"blue", "red", "green"} local enemyGraphics = {} local enemyColors = {} for i=1, #enemyList do local color = enemyList[i] enemyColors[i] = color local enemy if enemyName == "blue" then --create blue enemy enemy = display.newRect(0, 0, 50, 50) enemy:setFillColor(0, 0, 1) elseif enemyName == "red" then --create red enemy  enemy = display.newRect(0, 0, 50, 50) enemy:setFillColor(1, 0, 0) else --create green enemy enemy = display.newRect(0, 0, 50, 50) enemy:setFillColor(0, 1, 0) end enemy.isVisible = false enemyGraphics[color] = enemy end local function table\_shuffle(t) assert(t, "table.shuffle() expected a table, got nil") for i = #t, 2, -1 do local j = math.random(1, i) t[i], t[j] = t[j], t[i] end print("Shuffling Done") end table.shuffle = table\_shuffle function shuffleColors( event ) table\_shuffle(enemyColors) for i=1, #enemyColors do local enemy = enemyGraphics[enemyColors[i]] local posX = (i-1)\*120 + 40 enemy.x = posX enemy.isVisible = true end end

Try it like this :slight_smile:

Hey torbenratzlaff, i appreciate the help your giving me but the code above doesn’t seem to be working - I think I might have gotten bit lost. 

The code above where you create new objects (local enemy), does that replace the actual spawnEnemy function which I currently have ? as shown below. 

local function spawnEnemys() shuffleArray(enemyList) for i=1, #enemyList do local enemyName = enemyList[i] local posX = (i-1)\*120 + 40 local moveRate = 800 if enemyName == "blue" then --create blue enemy blue = display.newImageRect("Shapes/CB.png", 52,52 ) blue.name = "Blue" physics.addBody(blue, {isSensor=true}) blue.x = posX blue.y = display.contentHeight - 550 enemyMovement1 = transition.to( blue, { time=moveRate, y=210,transition=easing.inOutQuad} ) enemyGroup:insert(blue) elseif enemyName == "red" then -- same as above for the rest