AS3 Closure Confusion - actionscript-3

I have a small loop
var a:Array = [{name:Test1},{name:Test2},{name:Test3},{name:Test4}]
var b:GenericButton; //A pretty basic button component
for(var i:int = 0; i < a.length; i++){
b = new GenericButton(a[i].name, function():void { trace(i) });
this.addChild(b);
}
The function supplied to the GenericButton is executed when the button is pressed.
The problem I am having is that when the no matter what button I press the value of 4 (the length of the array) is always output.
How would I ensure that I trace 0 when the first button is pushed, 1 when the second is pushed, etc?

Well, you can simply do:
var f:* = function():void { trace(arguments.callee.index) };
f.index = i;
b = new GenericButton(a[i].name, f);
Better still:
function createDelegate(obj:Object, func:Function):Function
{
var f:* = function ():* {
var thisArg:* = arguments.callee.thisArg;
var func:* = arguments.callee.func;
return func.apply(thisArg, arguments);
};
f.thisArg = obj;
f.func = func;
return f;
}
...
for (...) {
b = new GenericButton(a[i].name,
createDelegate({index: i}, function():void { trace(this.index) }));
}
And in some (most?) cases it would be even better if you created a separate class and passed i into the constructor.

This is most basic error when using closures. You might be thinking that i is set when GenericButton is created. But closure just get direct link to variable i and uses this link when anonymous function is called. By this time, cycle is finished, and all links to i are pointing to same integer with value = 4.
To fix this, just pass value of i somehow - for example, as an additional argument to GenericButton constructor. In this case, a copy of i will be created on each step, with values of 0, 1, 2, 3 - just like you need.
...
b = new GenericButton(a[i].name, function(i:int):void { trace(i); }, i);
...
Store i in GenericButton and pass into function - this causes anonymous function to stop using context variable i (cycle counter) and forces it to use argument i.

Make a function that returns a function. Here's a FlexUnit test method that demonstrates it.
[Test]
public function closureWin():void
{
var functions:Array = [];
var mkFn:Function = function(value:int):Function
{
return function():int
{
return value;
}
}
var i:int;
for (i = 0; i < 10; i++)
{
functions.push(mkFn(i));
}
var j:int;
for(j = 0; j < 10; j++)
{
assertEquals(j, functions[j]());
}
}
Here's a test method demonstrating the behavior you are seeing:
[Test]
public function closureFail():void
{
// basically to see if this works the same way in as3 as it does in javascript
// I expect that all the functions will return 10
var i:int;
var functions:Array = [];
for (i = 0; i < 10; i++)
{
functions.push(function():int{return i});
}
var j:int;
for each (var f:Function in functions)
{
assertEquals(10, f());
}
}

Related

actionscript 3.0 how to incorporate algorithm to my code?

I am new to actionscript 3.0 and I am trying to make a deck of cards shuffle, I have succeeded with this but my problem is that my cards are being repeated, so I have duplicates of the same card in a 52 card deck after shuffling. I am trying to create a texas holdem game.
I found this discussion Randomize or shuffle an array but it does not tell me how to incorporate the Fisher–Yates algorithm into my code. I have tried several different methods suggested here and else where over the web and nothing is working (Think the problem is defiantly my lack of experience).
Can someone please give me an example of how to incorporate this into my code or a link to somewhere that will explain how to do this correctly.
Thanks in advance.
Paul
package src.CardDeck
{
public class CardDeck
{
public var allCards:Array = [];
public var cardNames:Array;
public var cardValues:Array;
public var gameType:String;
public var drawnCards:uint = 0;
public function CardDeck(game:String)
{
gameType = game;
cardNames = ["Ace","Two","Three",
"Four","Five","Six",
"Seven","Eight","Nine",
"Ten","Jack","Queen","King"];
if(gameType == "texasholdem")
{
cardValues = [1,2,3,4,5,6,7,8,9,10,10,10,10];
}
makeSuit("Spade");
makeSuit("Heart");
makeSuit("Diamond");
makeSuit("Club");
}
private function makeSuit(suitString:String):void
{
var card:Object;
for(var i:uint = 0; i < cardNames.length; i++)
{
card = {};
card.cardType = suitString;
card.cardName = cardNames[i];
card.cardValue = cardValues[i];
card.isDrawn = false;
allCards.push(card);
}
}
public function shuffle():Array
{
var shuffledArray:Array = [allCards.length];
var randomCard:Object;
do
{
randomCard = getRandomCard();
if(shuffledArray.indexOf(randomCard) == -1)
{
shuffledArray.push(randomCard);
}
}
while(shuffledArray.length < allCards.length)
return shuffledArray;
}
private function getRandomCard():Object
{
var randomIndex:int = Math.floor(Math.random()* allCards.length);
return allCards[randomIndex];
}
}
}
Bug Note:
var shuffledArray:Array = [allCards.length];
Makes an array with a single element which shuffledArray[0] = allCards.length.
In fact you do not need to pre allocate it just say:
var shuffledArray: Array = [];
Here is the classical Fisher–Yates version:
public function shuffleFisherYates():Array {
var shuffledArray:Array = [];
var randomCardIndex: int;
do {
randomCardIndex = Math.floor(Math.random()* allCards.length);
shuffledArray.push(allCards[randomCardIndex]); // add to mix
allCards.splice(randomCardIndex,1); // remove from deck
}while(allCards.length); // Meaning while allCards.length != 0
return shuffledArray;
}
Here is Durstenfeld's (in place) version:
public function shuffleDurstenfeld():Array {
var swap:Object;
var countdown:int = allCards.length-1;
var randomCardIndex: int;
for(i = countdown; i > 0; i--){
randomCardIndex = Math.floor(Math.random()* countdown);
swap = allCards[countdown];
allCards[countdown] = allCards[randomCardIndex];
allCards[randomCardIndex]= swap;
}
return allCards; // shuffled in place
}
Assuming your shuffling code is okay, I think a reason why you are seeing repeated cards is that within your getRandomCard() method, you are not accounting for cards that have been drawn. You randomly generate an index and return the card there in the array....but that card is still in the array and it's possible that same index can be randomly generated again, resulting in the same card being returned.

Error joining AS2 with AS3

I have problems joining two scripts into one.
This is main part of the script: AS3.
And this is already joined script.
And here is part of the code that I need to import (AS2) :
stop();
var banners:Array = new Array();
var imagePaths:Array = new Array();
var links:Array = new Array();
var bodyTexts:Array = new Array();
var imageTime:Number;
var numberOfBanners:Number;
var isRandom:String;
var showHeader:String;
var bannersXML:XML = new XML();
bannersXML.ignoreWhite = true;
bannersXML.load("banners.xml");
bannersXML.onLoad = function(success) {
if (success) {
trace("XML LOADED");
imageTime = parseInt(this.firstChild.firstChild.firstChild)*1000;
numberOfBanners = parseInt(this.firstChild.childNodes[1].firstChild);
isRandom = this.firstChild.attributes["isRandom"];
showHeader = this.firstChild.childNodes[2].attributes["showHeader"];
var bannerSequence:Array = new Array();
if (isRandom == "true") {
//Make a random sequence
while (bannerSequence.length<numberOfBanners) {
newRandomNumber = random(numberOfBanners);
//Make sure that the random one chosen is not already chosen
for (var i = 0; i<=bannerSequence.length; i++) {
if (newRandomNumber != bannerSequence[i]) {
alreadyThere = false;
} else {
alreadyThere = true;
break;
}
}
//Add only random values that aren't in the array
if (!alreadyThere) {
bannerSequence.push(newRandomNumber);
}
}
} else {
for (var i = 0; i<numberOfBanners; i++) {
bannerSequence.push(i);
}
}
}
//Read XML in the Random Order Chosen
for (var i = 0; i<numberOfBanners; i++) {
banners.push(this.firstChild.childNodes[2].childNodes[bannerSequence[i]].firstChild.firstChild.toString());
bodyTexts.push(this.firstChild.childNodes[2].childNodes[bannerSequence[i]].childNodes[1].firstChild.nodeValue);
imagePaths.push(this.firstChild.childNodes[2].childNodes[bannerSequence[i]].childNodes[2].firstChild.nodeValue);
links.push(this.firstChild.childNodes[2].childNodes[bannerSequence[i]].childNodes[3].firstChild.nodeValue);
}
play();
};
//Start the image counter at 0
var imageCounter = 0;
I get erorr in this part of the code
function doRandArray(a:Array):Array {//make random array
var nLen:Number = a.length;
var aRand:Array = a.slice();
var nRand:Number;
var oTemp:Object;
for (var i:Number = 0; i < nLen; i++) {
oTemp = aRand[i];
nRand = i + (random(nLen – i));
aRand[i] = aRand[nRand];
aRand[nRand] = oTemp;
}
return aRand;
}
When I run it, I get an error in this place:
nRand = i + (random(nLen – i));
Scene 1, Layer 'Layer 1', Frame 1, Line 265 1084: Syntax error: expecting rightparen before i.
as2 random(random(nLen – i)); is generate 0,1,...nLen-i-1. not floating only int value.
correct as3 code is int(Math.random()*(nLen-i)); or Math.floor(Math.random()*(nLen-i));
as2: random()
as3: Math.random()
In ActionScript 3 the random function is a little bit different from what it was in as2 code, just change the offending line to:
nRand = i + Math.random()*(nLen-1);
This should fix all errors and work just the same.
EDIT: as #bitmapdata.com indicated, for this to run the same as in as2 the random value must be truncated (stripped of its decimal values). Besides the couple of possibilities he suggested, I would personally just change nRand's type to uint on declaration:
var nRand:uint;
You can also change the iterator type to var i:uint. Less memory usage is always good ;)

Can't removeEventlistener

I cant remove Event of rute_button, rute_button.removeEventlistener is not work.
is there something wrong about function tampil_rute(t) ? thanks..
function tampil(t)
{
rute_button.addEventListener(MouseEvent.CLICK, tampil_rute(t));
//tampil_rute(t);
var _loadertampil:URLLoader = new URLLoader();
var _datatampil:XML = new XML();
_loadertampil.addEventListener(Event.COMPLETE, readXMLtampil);
_loadertampil.load(new URLRequest("http://localhost/mall_baru/tampil2.php?id="+t));
function readXMLtampil(evttampil:Event)
{
_datatampil = new XML(evttampil.target.data);
var tampilanx = _datatampil..tenant_name;
tampilan.text = String(tampilanx);
trace("tampilan ="+tampilanx);
}
}
function tampil_rute(t)
{
return function( f:MouseEvent )
{
var c = t.split("_", 2);
var d:String = String(c[0]);
var e:Number = Number(c[1]);
for(var i:Number=1; i<=e; i++)
{
tambahan_tampil_rute(d,i);
}
rute_button.removeEventListener(MouseEvent.CLICK, tampil_rute(t));
}
}
function tambahan_tampil_rute(d, i)
{
this["rute_"+d+"_"+i].visible=true;
}
The problem is in the returned function by tampil_rute(t), each time you call this function this is returning a new object of type Function, if you want to remove the event listener, you must be sure to pass the same object (Function) to the removeEventListener function.
You can fix it as follow:
function tampil_rute(t)
{
var listener:*; // create a variable to store your listener reference
listener = function( f:MouseEvent )
{
var c = t.split("_", 2);
var d:String = String(c[0]);
var e:Number = Number(c[1]);
for(var i:Number=1; i<=e; i++)
{
tambahan_tampil_rute(d,i);
}
rute_button.removeEventListener(MouseEvent.CLICK, listener); // Put the reference here
}
return listener; // Return the listener here
}
fenixkim is right, the event handler returned by the anonymous function is not accessible because there is no reference to it.
An alternative solution though is to store t in a variable.
That way, we eliminate the need for an anonymous function and simplify the removal of the event handler.
var t;
function tampil(t)
{
this.t = t;
rute_button.addEventListener(MouseEvent.CLICK, tampil_rute);
// rest of code
}
function tampil_rute(e:MouseEvent)
{
var c = t.split("_", 2);
var d:String = String(c[0]);
var e:Number = Number(c[1]);
for(var i:Number=1; i<=e; i++)
{
tambahan_tampil_rute(d,i);
}
rute_button.removeEventListener(MouseEvent.CLICK, tampil_rute);
}
You don't pass in the parameter for add/remove eventListener.
rute_button.addEventListener(MouseEvent.CLICK, tampil_rute);
rute_button.removeEventListener(MouseEvent.CLICK, tampil_rute);

How to pass argument in addEventListener (Action Script)

How do I pass arguments using ActionScript's event-listener?
I have code, as given below, which creates a label, and I want, when clicked on the label it should pass the toolTip associated with that label.
This is what I was trying to do:
public function create_folderpath():void
{
for(var i:int = 0; i < fm_model.absolute_path_ac.length; i++)
{
var absolutePathToolTip:String = new String;
for(var j:int = 0; j <= i; j++)
{
absolutePathToolTip += fm_model.absolute_path_ac[j].path.toString() + '/';
}
var textItem:Label = new Label();
textItem.data = absolutePathToolTip;
textItem.toolTip = absolutePathToolTip;
textItem.text = fm_model.absolute_path_ac[i].path.toString() + ' /';
textItem.addEventListener(MouseEvent.CLICK, testing)
directoryPathHBox.addChild(textItem);
}
}
public function testing(e:MouseEvent)
var direcoryLabel:Label = e.target as Label;
Alert.show(direcoryLabel.data +"");
}
This does not work, nor do I get any errors.
Please, I need help with this.
Thanks in advance
Zeeshan
Try to use "currentTarget" instead of "target":
var direcoryLabel:Label = e.currentTarget as Label;
Alert.show(direcoryLabel.data +"");
And be sure to add a trace in the listener, to know for sure if it's called or not.

Closure problem? - passing current value of a variable

I'm trying to pass the current value of a variable when an a dynamically generated navigation 'node' is clicked. This needs to just be an integer, but it always results in the last node's value.. have tried some different methods to pass the value, a custom event listener, a setter, but I suspect it's a closure problem.. help would be appreciated ;-)
function callGrid():void {
for (var i:Number = 0; i < my_total; i++) {
var gridnode_url = my_grid[i].#gridnode;
var news_category= my_grid[i].#category;
var newstitle = my_grid[i].#newstitle;
var news_content = my_grid[i]..news_content;
var news_image = my_grid[i]..news_image;
var gridnode_loader = new Loader();
container_mc.addChild(gridnode_loader);
container_mc.mouseChildren = false;
gridnode_loader.load(new URLRequest(gridnode_url));
gridnode_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, gridLoaded);
gridnode_loader.name = i;
text_container_mc = new MovieClip();
text_container_mc.x = 0;
text_container_mc.mouseEnabled = false;
var textY = text_container_mc.y = (my_gridnode_height+18)*y_counter;
addChild(text_container_mc);
var tf:TextSplash=new TextSplash(newstitle,10,0,4 );
container_mc.addChild(tf);
tf.mouseEnabled = false;
tf.height = my_gridnode_height;
text_container_mc.addChild(tf);
var text_container_mc_tween = new Tween(text_container_mc, "alpha", Strong.easeIn, 0,1,0.1, true);
gridnode_loader.x = (my_gridnode_width+5) * x_counter;
gridnode_loader.y = (my_gridnode_height+15) * y_counter;
if (x_counter+1 < columns) {
x_counter++;
} else {
x_counter = 0;
y_counter++;
}
}
}
function gridLoaded(e:Event):void {
var i:uint;
var my_gridnode:Loader = Loader(e.target.loader);
container_mc.addChild(my_gridnode);
_xmlnewstarget = my_gridnode.name;
//||||||||||||||||||||||||||||||||||||||||
//when a particular grid node is clicked I need to send the current _xmlnewstarget value to the LoadNewsContent function...
//||||||||||||| ||||||||||||||||||||||||
my_tweens[Number(my_gridnode.name)]=new Tween(my_gridnode, "alpha", Strong.easeIn, 0,1,0.1, true);
my_gridnode.contentLoaderInfo.removeEventListener(Event.COMPLETE, gridLoaded);
my_gridnode.addEventListener(MouseEvent.CLICK, loadNewsContent);
}
function loadNewsContent(e:MouseEvent):void {
createNewsContainer();
getXMLNewsTarget();
news_category = my_grid[_xmlnewstarget].#category;
var tfnews_category:TextSplash=new TextSplash(news_category,20,16,32,false,false,0xffffff );
tfnews_category.mouseEnabled = false;
newstitle = my_grid[_xmlnewstarget].#newstitle;
var tftitle:TextSplash=new TextSplash(newstitle,20,70,24,false,false,0x333333 );
news_container_mc.addChild(tftitle);
tftitle.mouseEnabled = false;
news_content = my_grid[_xmlnewstarget]..news_content;
var tfnews_content:TextSplash=new TextSplash(news_content,20,110,20,true,true,0x333333,330);
news_container_mc.addChild(tfnews_content);
tfnews_content.mouseEnabled = false;
news_image = my_grid[_xmlnewstarget].#news_image;
loadNewsImage();
addChild(tfnews_category);
addChild(tftitle);
addChild(tfnews_content);
var news_container_mc_tween = new Tween(news_container_mc, "alpha", Strong.easeIn, 0,1,0.3, true);
news_container_mc_tween.addEventListener(Event.INIT, newsContentLoaded);
}
I'm not going to try to read your code (try to work on your formatting, even if it's just indenting), but I'll provide a simplified example:
for (var i = 0; i < my_total; i++) {
var closure = function() {
// use i here
}
}
As you say, when closure is called it will contain the last value of i (which in this case would be my_total). Do this instead:
for (var i = 0; i < my_total; i++) {
(function(i) {
var closure = function() {
// use i here
}
})(i);
}
This creates another function inside the loop which "captures" the current value of i so that your closure can refer to that value.
See also How does the (function() {})() construct work and why do people use it? for further similar examples.
Umm, as mentioned above, the code is a bit dense, but I think you might have a bit of type conversion problem between string and integers, is the "last value" always 0? try making these changes and let me know how you get on.
// replace this gridnode_loader.name = i;
gridnode_loader.name = i.toString();
// explictly type this as an int
_xmlnewstarget = parseInt(my_gridnode.name);
// replace this: my_tweens[Number(my_gridnode.name)] = new Tween(......
my_tweens[parseInt(my_gridnode.name)] = new Tween();
Oh and I think it goes without saying that you should massively refactor this code block once you've got it working.
Edit: after further study I think you need this
//replace this: my_gridnode.addEventListener(MouseEvent.CLICK, loadNewsContent);
var anonHandler:Function = function(e:MouseEvent):void
{
loadNewsContent(_xmlnewstarget);
};
my_gridnode.addEventListener(MouseEvent.CLICK, anonHandler);
Where your loadNewsContent has changed arguements from (e:MouseEvent) to (id:String)
Firstly, you do not need to call addChild for the same loader twice (once in callGrid) and then in (gridLoaded). Then you can try putting inside loadNewsContent: news_category = my_grid[int(e.target.name)].#category;instead of news_category = my_grid[_xmlnewstarget].#category; As _xmlnewstarget seems to be bigger scope, which is why it is getting updated every time a load operation completes.