Actionscript 3 while loop with indexOf - actionscript-3

Is there any reason why this loop is getting stuck? I can't quite get my head around it:
var i:Number = -1;
do
{
i = Math.round(Math.random() * _totalQuestions);
}
while(_usedQuestions.indexOf(i));
Where _usedQuestions is an array of numbers. This array starts empty.
Thanks!
Edit: I want the loop to end if i is NOT found in the array.. this way I know the question I have selected has not previously been asked.

There are really two answers here.
The loop never exits because Array.indexOf() returns -1 if the argument is not found, and -1 is true in a boolean context. The only way your loop will ever exit is if i happens to be equal to _usedQuestions[0].
It may not be obvious, but even if you fix the above problem your loop will still fail to exit once all the questions have been used... and that's your real problem - you're using a confusing algorithm to do something simple.
It would make a lot more sense to simply keep two arrays - one of unseen questions and one of seen questions. Every time you choose a new question, simply remove it from unseen and add it to seen. Like this:
if (unseen.length > 0) {
var i:int = Math.floor( Math.random() * unseen.length );
seen.push( unseen[i] );
unseen.splice(i, 1);
} else // all questions seen...
Remember: writing programs that work correctly is merely the minimum requirement of programming. A good programmer writes programs that are easily understood by people as well! And a big part of that is making sure that simple things are done in simple ways. (Unless performance is an inescapable factor - but in this case, I absolutely promise that it won't!)

Try this.
var i:Number = -1;
var found:Boolean;
do
{
i = Math.round(Math.random() * _totalQuestions);
found = false;
for (var j:int = 0; j < _usedQuestions.length; j++)
{
// assume that _usedQuestions is an array which holds the question
// number that has been selected before
if (_usedQuestions[j] == i)
{
found = true;
// get out of for loop if found, we'll need to get another
// random number
break;
}
}
}
while (found);
I'm not really proficient with AS3 since just started learning it so someone might have a more efficient way to do the above.

indexOf will return -1 if there is nothing found so use that value, also for avoiding an infinite loop check that _useQuestions does not contains all your totalQuestions :
Edit : a more complete version to illustrate:
var checked:int = 0;
var seen:Dictionary = new Dictionary();
while (checked < _totalQuestions) {
var i:int = int(Math.random() * _totalQuestions);
if (_usedQuestions.indexOf(i) < 0) {
break;
} else if (seen[i] === undefined) {
seen[i] = i;
checked++;
}
}
if (checked < _totalQuestions) {
//ok you have found a non used questions
} else {
// all questions have been used
}

Related

AS3 .addEventListener in a loop?

I am creating a line of Sprite elements. Every sprite element has different job, when it is clicked. How can I make the function inside the addEventListener to know which button was clicked?
In this case, the traced value of i when it is cliicked is always 6. Which is wrong, because 6 is only the last element of the array. What about the rest of them, the beginning?
for (var i:int = 0; i < 6; i++) {
var barPart:Sprite = new Sprite();
barPart.x = i * (30);
barPart.y = 0;
barPart.graphics.beginFill(0x000000, 0.2);
barPart.graphics.drawRect(0, 0, 10, 10);
barPart.graphics.endFill();
barPart.addEventListener(MouseEvent.CLICK, function(_event:MouseEvent):void {
trace(i);
});
}
When the application is build and the listeners are added, the loop has already executed, so the index "i" will always be six by the time the user ends up clicking the button.
To distinguish between the different items, use their "name" property (prop of DisplayObject) like shown ...
Try not to have listener function as a method closure in a loop, instead do this:
for (...)
{
... code
barPart.name = "barPart-" +i;
barPart.addEventListener(MouseEvent.CLICK, barPart_clickHandler);
}
and implement the function (event handler separately) like:
private function barPart_clickHandler(e:MouseEvent):void
{
// the event's target will let you know who dispatched the function
var name:String = Sprite(e.currentTarget).name;
name = name.replace("barpart-", "");
switch(name)
{
case '0':
// your code
break;
.
.
}
}
#Shally Virk - My mistake. I was thinking of MovieClip which is a dynamic object so it allows adding arbitrary fields. You are right, there are lots of ways to get around this problem, but your suggestion works fine.
While the cause is not clear to me, the answer is a little more simple,
The events are registered correctly, but flash takes the last computed value.
Knowing that we can work around.
The work around can either do like Shally Virk wrote, but that tends to get confusing on bigger scale. So we want something more general and simple.
Now here are the steps taking this in mind:
1. We know sprite are not dynamic, so we make class to extend spirte and make it dynamic. Since the class has basically 0 code, the only difference being the dynamic , the amount of memory added is small
2. Having the class here's the code:
for (var i:int = 0; i < 6; i++) {
var barPart:CustomSprite = new CustomSprite();
barPart.x = i * (30);
barPart.y = 0;
barPart.graphics.beginFill(0x000000, 0.2);
barPart.graphics.drawRect(0, 0, 10, 10);
barPart.graphics.endFill();
barPart.i = i;
barPart.addEventListener(MouseEvent.CLICK, function(_event:MouseEvent):void {
trace(_event.currentTarget.i);
});
}
:)

Object removed from screen before the relevant command is called (Error #1009)

I'm sorry if it's asking for too much, but I'm too confused by now. I'm making this really simple shooter game for my nephew in AS3. It all seems to be working just fine, except for one really annoying error that keeps popping up every second or third time the game is launched.
IT is Error #1009: Cannot access a property or method of a null object reference. The problem is always with parent.removeChild(this) command in the relevant class (EnemyClass, BulletClass or MissileClass). This happens in two cases: either when checkFinishConditions method in Main is called and the EnemyClass instance needs to be deleted. So if I get the #1009 error does this mean the instance has already been deleted? The second situation is when inst.hitTestObject(enemyInstance) is checked in Main class. Does this mean the EnemyClass instance has already been deleted somehow? I'm totally lost here to be honest.
private function checkCollision():void
{
//loop through missiles
for (var i:int = 0; i < aMissileArray.length; i++) {
//get the current missile
var currentMissile:missileClass = aMissileArray[i];
//loop through enemies
for (var j:int = 0; j < aEnemyArray.length; j++) {
var thisEnemy:EnemyClass = aEnemyArray[j];
if (currentMissile.hitTestObject(thisEnemy)) {
var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);
addChild(thisExplode);
currentMissile.destroyThis();
aMissileArray.splice(i,1);
thisEnemy.deleteEnemy();
aEnemyArray.splice(j, 1);
aDamageArray.splice(j, 1);
scoreValueText += 1;
j--;
i--;
}
//break;
}
}
//loop through bullets
for (var l:int = 0; l < aBulletArray.length; l++) {
//get the current missile
var currentBullet:BulletClass = aBulletArray[l];
//loop through enemies
for (var k:int = 0; k < aEnemyArray.length; k++) {
var currentEnemy:EnemyClass = aEnemyArray[k];
if (currentBullet.hitTestObject(currentEnemy)) {
currentBullet.destroyThis();
aBulletArray.splice(l, 1);
aDamageArray[k] -= 1;
l--;
if (aDamageArray[k] < 1) {
//create an explosion
var thisBulletExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);
addChild(thisBulletExplode);
currentEnemy.deleteEnemy();
aEnemyArray.splice(k, 1);
aDamageArray.splice(k, 1);
scoreValueText += 1;
k--;
}
break;
}
}
}
}
There is alot of code to look over, but one potential issue I see is here :
for each(var currentDieEnemy:EnemyClass in aEnemyArray) {
aEnemyArray.splice(0, 1);
currentDieEnemy.deleteEnemy();
}
The potential issue is that you are 'assuming' that the order of this loop is sequential based on the actual index order of the Array. You might want to stick a trace in there to verify that is actually what is happening, because I believe it's possible that they can be out of order.
See this question for details -- For-Each Loop AS3: Is the direction guaranteed?
So imagine the scenario where the 3rd item in the array is first and you splice the item at the 0 index. Now, you have an item in the array that is removed from the display list, but not from the array. So what happens when you get to that item ? Pretty much what you are describing will happen since the parent property is null, since you already removed it from the display list.
The way to fix that is to do something like this :
while (aEnemyArray.length > 0)
{
var currentDieEnemy:EnemyClass = aEnemyArray[0];
currentDieEnemy.deleteEnemy();
aEnemyArray.splice(0,1);
}
I found another issue in this block of code :
for (var j:int = 0; j < aEnemyArray.length; j++) {
var thisEnemy:EnemyClass = aEnemyArray[j];
if (currentMissile.hitTestObject(thisEnemy)) {
var thisExplode:ExplosionClass = new ExplosionClass(thisEnemy.x,thisEnemy.y);
addChild(thisExplode);
currentMissile.destroyThis();
aMissileArray.splice(i,1);
thisEnemy.deleteEnemy();
aEnemyArray.splice(j, 1); // now array composition is different.
aDamageArray.splice(j, 1);
scoreValueText += 1;
}
}
When you loop through an Array and splice an item at the loop index, you have to realize what happens to the composition of the array. Here's an example
Suppose enemy3 needs to be spliced because of a collision. Given your code, this is what happens :
Before (j == 2)
(0) enemy1
(1) enemy2
(2) enemy3
(3) enemy4
(4) enemy5
AFTER
(0) enemy1
(1) enemy2
(2) enemy4
(3) enemy5
So when the loop continues, j will now increment and equal 3.
The result is that enemy4 doesn't get evaluated as the composition of the array collapses to fill the hole created by the splice and you didn't adjust your loop index variable.
So, what you can do in that situation is simply decrement the loop index variable at the end of the block like so :
aEnemyArray.splice(j, 1); // now array composition is different.
aDamageArray.splice(j, 1);
scoreValueText += 1;
j--; // decrement loop index variable.
So while this is one solution, the main point to take away here is the change in the composition of your Array after a splice.
Yep, you are splicing your objects from arrays in a weird way. First, you are traversing the array forwards and splicing in the loop. This can lead to issues of several bullets not trigger collisions when they should, or say not move. See, if you are running your loop for bullets at index i, once you call bullets.splice(i,1); the bullet that was to be iterated next becomes at position i, then you increment i and that bullet remains unprocessed.
Next, you have nested loops, and in the inner loop you are removing an object from OUTER loop. This means once you did a removal of the outer loop's object, your inner loop is now invalidated, you have to preemptively terminate inner loop.
//loop through bullets
for (var l:int = aBulletArray.length-1; l>=0; l--) {
// first, traverse both arrays backwards
//get the current missile
var currentBullet:BulletClass = aBulletArray[l];
//loop through enemies
for (var k:int = aEnemyArray.length-1; k>=0; k--) {
var currentEnemy:EnemyClass = aEnemyArray[k];
if (currentBullet.hitTestObject(currentEnemy)) {
currentBullet.destroyThis();
aBulletArray.splice(l, 1);
aDamageArray[k] -= 1;
if (aDamageArray[k] < 1) {
//create an explosion
var thisExplode:ExplosionClass = new ExplosionClass(currentEnemy.x,currentEnemy.y);
addChild(thisExplode);
currentEnemy.deleteEnemy();
aEnemyArray.splice(k, 1);
aDamageArray.splice(k, 1);
scoreValueText += 1;
}
// and since we don't have "currentBullet" anymore, do this
break;
}
}
}
Fix all the other iterations through your arrays where you do splicing, like I did in this part of code, and you should avoid 1009 errors in these loops.
Also, you should not post your entire project code, but instead post only relevant parts, say an entire function that's reported as throwing an error, and explain which line is producing the error - it's written as number in the error's stack trace.

AS3 (or AS2) - Global Variables as Local inside Functions

I'm assuming there's something easy i'm overlooking here.
But basically, What i'm trying to do is just simply.. I guess code would be a better explanation, here it goes:
tobj = (an Array containing objects.)
for(i = 0; i < tobj.length; i++) {
tobj[i].func = function() {
trace(i);
}
}
Basically, i understand what happens here.. i as the global variable changes, so if i execute that function it'll retrieve the last value of i regardless of what it was when it was assigned. Basically I'm trying to figure out here is what approach to use to convert i as a global, into a local, so that when i execute the function on the object, it'll output the variable as it was assigned.
Hope it's understandable and straight forward. Thanks in advance.
If you add parenthesis after the closing bracket of your function declaration, it will run the function:
var tobj = [new Object(),new Object(), new Object()];
for(var i = 0; i < tobj.length; i++) {
tobj[i].func = function() {
trace(i);
}() // < ------
This would trace: "0", "1", "2"
Simple Solution... There might be another way, but this is what worked for me. Just took walking away from it for a few minutes.
tobj = (an Array containing objects.)
for(i = 0; i < tobj.length; i++) {
tobj[i].func = function() {
assigner(i);
}
}
function assigner(var) {
trace(var);
}

How to change my code to target a list of display objects?

if( listnumber != "listBox1")
this[listnumber].visible = false;
else
this[listnumber].visible = true;
I would like to change this statement to make the new listnumber visible, and make any other invisible.
Thanks to Baris Usakli for suggesting the mentioned code, my question just needed to be clearer
function makeVisible(a:Array,s:String):void{
for (var i:int = 0; i < a.length; i++)
{
if(a[i].name == s)
a[i].visible = true;
else
a[i].visible = false;
}
}
Your question is confusing so I've done the best I can to decipher what you want done. You can call that function from your script passing the array of objects and the string you want tested based on a variable (change the variable "name" to whichever is required) through the parameter. This won't work if there are objects with the same name (or at least it'll turn all objects with that name visible and all others invisible).
Hope it helps.

How can I check if an array contains another array?

At first sight it's very simple, but I'm having some problems to do this without using a lot of nested loops.
Example:
var father:Array = new Array(0,1,2,3,4,5);
var son:Array = new Array(3,4,5);
father.contains(son) // returns true or 4(the starting index if the contained array)
ActionScript 3 actually supports some slightly crazy stuff, due to the fact that, in the early days, Adobe/Macromedia were trying to make it compliant with Ecmascript.
So... you can do this:
var a1:Array = [1,2,3,4,5,6,7,8,9];
var a2:Array = [3,4,5];
// borrow String's indexOf function, and it magically works on Arrays
// but rename it because Array already has a different indexOf function
a1.indexOf2 = String.prototype.indexOf;
trace(a1.indexOf2(a2) > -1); // true
But you need to be a little bit careful because it will convert all the elements to Strings for the equality test. For primitives, it mostly won't matter but it will break badly with objects as they'll all be converted to "[object Object]" or to whatever their toString() returns.
Also, if you wanted to use the actual index for anything, rather than just checking it's not -1, you have to divide by two, as the number is double what you'd expect. I don't exactly know why this is :)
If you need something more general and reliable, you'd be better off writing a function to do an explicit search. This is a quick example, which I just wrote so could easily be bug-ridden:
public function find(haystack:Array, needle:Array):int
{
var index:int = -1;
while(index <= haystack.length - needle.length)
{
index++;
index = haystack.indexOf(needle[0], index);
for( var i:int = 1; i<needle.length; i++)
{
if(haystack[index+i] != needle[i])
{
continue;
}
}
if( i == needle.length)
{
return index;
}
}
return -1;
}
Try this for simplicity:
// Determines if an array contains en element (similar to the PHP function with the same name)
public function in_array(needle:*, haystack:Array):Boolean
{
for each (var element:* in haystack)
{
if (element == needle) {return true;}
}
return false;
}