AS3 - Auto refreshing display after updating array - arrays

So, basically I'm reinventing the wheel by trying to make a sort of spreadsheet in flash for tracking member growth in a game. In this one section I'm adding member names to an array and then placing the names into dynamically created tiles with text fields attached to display the name.
I have a save button which saves the array, and if I save, close, and reopen, then I can see the members I have added. However, I would like it to refresh the stage as soon as the array is changed to reflect the changes made (update the spreadsheet). I will also be adding and removing other tiles dynamically, but I can extrapolate the solution to this problem to all of those later.
Here's the code I have to add members and create the display:
public var mainArray:Array = new Array;
public var i1:Number = 0;
public var memberBox:MovieClip = new MovieClip();
public var MAX_ROWS = 0;
public var MAX_COLS = 0;
public function Tracker() {
MAX_COLS = mainArray.length - 1;
addBtn.addEventListener(MouseEvent.CLICK, addFun); //atchaed to button on stage
public function addFun($e:MouseEvent):void{
mainArray[i1] = [];
mainArray[i1][0] = addNameTxt.text //attached to textfield on stage
i1++;
loadMembers();
public function loadMembers():void{
var multiDimensionalArray:Array = new Array();
//initalize the arrays
for (var row = 0; row <= MAX_ROWS; row++)
{
var boolArray:Array = new Array();
for (var col = 0; col <= MAX_COLS; col++){
boolArray.push(false);
}
multiDimensionalArray.push(boolArray);
}
//now we can set the values of the array as usual
for (var row = 0; row <= MAX_ROWS; row++)
{
for (var col = 0; col <= MAX_COLS; col++){
multiDimensionalArray.push(1); ;
}
}
//create a column of tiles based on mainArray length with a textfield attached to each
buildLevel(multiDimensionalArray);
}
public function buildLevel(s:Array){
var txtArray:Array = new Array();
memberBox.name = "tileHolder";
for(var i=0; i < MAX_ROWS + 1; i++){
for(var o=0; o < MAX_COLS + 1; o++){
var currentTile:MemberBox = new MemberBox();
currentTile.x = i*150;
currentTile.y = o*25;
currentTile.name = "b"+o;
memberBox.addChild(currentTile);
//currentTile.gotoAndStop(int(s[o][i]));
var memberTxt:TextField=new TextField();
currentTile.addChild(memberTxt);
memberTxt.width = 150;
memberTxt.height = 25;
txtArray[o] = memberTxt;
txtArray[o].text = mainArray[o][0];
}
}
memberBox.x = 60;
memberBox.y = 170;
addChild(memberBox);
}
}

The ideal solution would be to attach event listeners to the Array, however, Arrays don't fire events.
Solution #1: Managed Updates
Rather than allowing any piece of code to modify your array directly, write a function that handles updating your array. This way, you can know when to update the display at the same time.
public var mainArray:Array = new Array;
public var i1:Number = 0;
public var memberBox:MovieClip = new MovieClip();
public var MAX_ROWS = 0;
public var MAX_COLS = 0;
public function Tracker() {
MAX_COLS = mainArray.length - 1;
addBtn.addEventListener(MouseEvent.CLICK, addFun); //attached to button on stage
public function addFun(e:MouseEvent):void {
mainArray[i1] = [];
mainArray[i1][0] = addNameTxt.text //attached to textfield on stage
i1++;
loadMembers();
}
public function loadMembers():void {
var multiDimensionalArray:Array = new Array();
//initalize the arrays
for (var row = 0; row <= MAX_ROWS; row++) {
var boolArray:Array = new Array();
for (var col = 0; col <= MAX_COLS; col++) {
boolArray.push(false);
}
multiDimensionalArray.push(boolArray);
}
//now we can set the values of the array as usual
for (var row = 0; row <= MAX_ROWS; row++) {
for (var col = 0; col <= MAX_COLS; col++) {
multiDimensionalArray.push(1); ;
}
}
//create a column of tiles based on mainArray length with a textfield attached to each
buildLevel(multiDimensionalArray);
}
public function buildLevel(s:Array) {
var txtArray:Array = new Array();
memberBox.name = "tileHolder";
for (var i:int = 0; i < MAX_ROWS + 1; i++) {
for(var o=0; o < MAX_COLS + 1; o++) {
var currentTile:MemberBox = new MemberBox();
currentTile.name = i + "_" + o;
currentTile.x = i*150;
currentTile.y = o*25;
memberBox.addChild(currentTile);
//currentTile.gotoAndStop(int(s[o][i]));
var memberTxt:TextField=new TextField();
currentTile.addChild(memberTxt);
memberTxt.width = 150;
memberTxt.height = 25;
txtArray[o] = memberTxt;
txtArray[o].text = mainArray[o][0];
}
}
memberBox.x = 60;
memberBox.y = 170;
addChild(memberBox);
}
public function modifyArray(row:int, col:int, value:*):void {
// Update our array.
mainArray[row][col] = value;
// Update our tile.
var tile:MemberBox = memberBox.getChildByName(row + "_" + col);
tile.getChildAt(0).text = value;
}
}
When you actually modified your array, you'd do that with your function, rather than a direct mainArray[o][i] approach.
// Instead of this
mainArray[o][i] = "foo";
// You'd do this
modifyArray(o, i, "foo");
Update: I'm not sure how better to explain this, so I've posted working example that you can view. It contains the class, and the document code, the fla, and the swf with working updates to the spreadsheet. Let me know if that resolves the issue: SpreadSheet Test # Dropbox
Solution #2: Poll for Changes
Hold two versions of your array in memory. The first is the one you're actively editing, the second is an untouched duplicate to compare against the first. Every so often (once per second?) you iterate over the entire dataset and look for differences. When one is found, update the duplicate array and the displayed spreadsheet.
Here's an example of how you'd do that:
import flash.utils.*;
var last:Number = flash.utils.getTimer();
this.addEventListener("enterFrame", checkData);
var foo:Array = ["a", "b", "c"];
var fooBackup:Array = [];
function update():void {
var txt:TextField;
for (var i:int = 0; i < foo.length; i++) {
// If we don't have a representation of this index, create one.
if (!this.getChildByName("tile"+i)) {
txt = new TextField();
txt.name = "tile" + i;
txt.text = foo[i];
this[txt.name] = txt;
addChild(txt);
txt.y = i * 20;
}
// Update the data if inconsistent.
txt = this.getChildByName("tile"+i) as TextField;
if (fooBackup.hasOwnProperty(i) == false || foo[i] != fooBackup[i]) {
fooBackup[i] = foo[i]
txt.text = foo[i];
trace("Index " + i + " changed. Updated backup, and display.");
}
}
}
function checkData(e:Event):void {
var now:Number = flash.utils.getTimer();
// If the last time we checked is greater than a second, run the check again.
if (now - last > 1000) {
// Update last checked index;
last += 1000;
// For the sake of argument, let's randomly change an index to a random value.
foo[random(0, foo.length-1)] = random(-1000, 1000);
update();
}
}
function random(low:Number=0, high:Number=1):Number {
/* Returns a random number between the low and high values given. */
return Math.floor(Math.random() * (1+high-low)) + low;
}
Solution #3: Roll your own Array
As outlined by Adobe's Extending the Array class, you can subclass Arrays and from there add your own event dispatches when you add/remove entries. This is the more adventurous solution, and (in my humble opinion) the best as it maintains code consistency, while giving broad and efficient powers.

Related

Generate Random Number Using math.random, But Without Repeats

For a yearly Christmas event for an organization I'm a part of, we usually hold a raffle where people have the opportunity to win free prizes by just attending and getting a number ticket at the door. We use a program written in Flash (Using ActionScript 2.0) that selects a random number utilizing math.random, with some parameters attached to it, as shown below:
//maxNr = 999999999999999;
initRandom = function(){
var nr = Math.ceil(Math.ceil(Math.random()*(maxNr));
var nrString = "";
for( var j=0; j<(maxNr.toString().length-nr.toString().length); j++){
nrString += "0";
}
nrString += nr.toString();
var holder = this.createEmptyMovieClip("holder",1);
for( i=0; i<maxNr.toString().length; i++ ){
var mc = holder.attachMovie("number","n"+i,i+10);
mc._x = i*350;
mc.anim_mc.gotoAndPlay( Math.floor(Math.random()*9) + 1 );
this["iv"+i] = setInterval( this, "revealNumber", 2000 + (500*i), nrString.substr(i,1), i );
}
// scale (if needed) and center
if( holder._width > Stage.width ){
holder._width = Stage.width;
holder._yscale = holder._xscale;
}
holder._x = Stage.width/2 - holder._width/2;
holder._y = 100;
// buttons
back_btn.onRelease = function(){
for(item in holder){
holder[item].removeMovieClip();
}
gotoAndStop("intro");
}
}
revealNumber = function( digit, i ){
clearInterval( this["iv"+i] );
holder["n"+i].gotoAndStop("done");
holder["n"+i]["number_txt"].text = digit;
}
initRandom();
stop();
It's meant to return a random number between 1 and 1000, as defined by:
go_btn.onRelease = function(){
maxNr = Math.max( 1000 , 1 );
gotoAndStop("random");
}
stop();
It was written by a member of our organization who unfortunately passed away during the year, and I have little to no programming knowledge but I am a quick learner and have actually modified some of the code to get to the point it is currently. However, I'm trying to add in a parameter that would disallow the function from repeating a number, ie, excluding an already generated number from being reselected.
I've spent days scouring any resource possible and have only met dead ends.
With the approach currently taken, is it possible to add in this parameter to this existing code, and how can I go about doing that?
Any help, suggestion or reply would be very greatly appreciated!
package
{
public class RandomGenerator
{
private var _st:Number;
private var _en:Number;
private var _len:Number;
private var _pos:Number;
private var _numPos:Number;
private var _myNums:Array;
private var _randNums:Array;
public function RandomGenerator(en:Number, st:Number = 0)
{
_st = st;
_en = en;
// just in case if params order mixup:
if(en < st){
_st = en;
_en = st;
}
_len = _en - _st + 1;
shuffle();
}
public function getNum():Number
{
// if passed last item:
if(_numPos == _len)shuffle();
var myResult:Number = _randNums[_numPos];
_numPos++;
return myResult;
}
private function shuffle():void
{
_numPos = 0;
_randNums = [];
_myNums = [];
// Creating Numbers Array:
var i:Number;
for(i = 0; i<_len; i++){ _myNums[i] = _st + i; }
// Creating shuffled Numbers Array:
i = 0;
while(_myNums.length > 0){
_pos = Math.round(Math.random()*(_myNums.length-1));
_randNums[i] = _myNums[_pos];
i++;
_myNums.splice(_pos,1);
}
}
public function get len():Number
{
return _len;
}
}
}
make an object from this class for example cardGenerator:RandomGenerator = new RandomGenerator(51, 0) and finally you can get random number without repeat with this codes:
for (var i:int = 0; i < cardGenerator.len; i++) {
trace(cardGenerator.getNum());
}
My AS2 is a bit rusty, but I think the following script explains the general idea. You need some registry to record generated numbers so you can skip them next time you generate one. I used the _global object so that this logic transcends even multiple instances of the following script.
// Create an Array to record generated numbers.
// The _global object is always present and can be accessed from anywhere.
if (_global.uniqueNr == undefined)
{
_global.uniqueNr = [];
}
function initRandom()
{
var nr;
do
{
nr = Math.ceil(Math.ceil(Math.random()*(maxNr));
}
while (_global.uniqueNr[nr] == true);
// Record the newly generated number so it would never drop again.
_global.uniqueNr[nr] = true;
// At this point you can be sure that "nr" contains a unique value.

My for loop won't display my object, but no errors show up

For some reason my for loop isn't working, the enemies won't spawn and nothing appears in the Output when I used trace. However, there also is no error, so I'm wondering what the issue is.
Here is my code:
var playerX = 0;
var playerY = 0;
var mapWidth = 5000;
var mapHeight = 5000;
//enemy
var myEnemies:Array = new Array();
var enemySprite:Sprite;
var Enemy:enemy;
var enemyCount:int = 0;
//event listeners
stage.addEventListener(Event.ENTER_FRAME, spawnEnemies);
//spawn enemies
function spawnEnemies(spawn:Event) {
if (enemyCount < 20) {
for (var i = 0; i < myEnemies.length; i++) {
enemySprite = new Sprite();
this.addChild(enemySprite);
Enemy = new enemy();
Enemy.x = (Math.random() * this.width);
Enemy.y = (Math.random() * this.height);
enemySprite.addChild(Enemy);
enemyCount++;
myEnemies[enemyCount] = enemySprite;
trace(myEnemies.length);
}
stage.addEventListener(Event.ENTER_FRAME, moveEnemy);
}
}
//move the enemies
function moveEnemy(enemyMovement:Event){
for (var k = 0; k < myEnemies.length; k++) {
trace("move enemy");
if (myEnemies[k].y > playerY) {
myEnemies[k].y -= 1;
myEnemies[k].rotation = 0;
}
else if (myEnemies[k].x < playerX) {
myEnemies[k].x += 1;
myEnemies[k].rotation = 90;
}
else if (myEnemies[k].y < playerY) {
myEnemies[k].y += 1;
myEnemies[k].rotation = 180;
}
else {
myEnemies[k].x -= 1;
myEnemies[k].rotation = 270;
}
}
}
Thank you for your help!
OK, I did not work with AS3 for a long time, but... Why do you expect new enemies to be created if myEnemies length is 0?
Also, you created two different ENTER_FRAME functions and there is no need to do that. Create only one function and call it for exmaple update:
private function update(e:event)
{
}
stage.addEventListener(Event.ENTER_FRAME, update);
You should not create new sprites using for loop inside ENTER_FRAME function, because this function runs 30 or more times in a second.
Create for loop inside "init" or "create" function, unless you want to update code on each frame.
Add 10 enemies:
for (var i = 0; i < 10; i++) {
Enemy = new enemy();
Enemy.x = (Math.random() * this.width);
Enemy.y = (Math.random() * this.height);
this.addChild(Enemy);
// add it to array
myEnemies.push(Enemy);
}
You cannot use myEnemies to create new Enemy sprite because it's empty, so you create 0 enemies. If you want to create 10 enemies use this code, or simple change number 10 to any number you want.

as3 how to remove/ move position of graphics from reset array

I am trying to reset a scene an move every thing to its original position the reset function resets the array adds the nape bodies back to the stage and attaches the graphics but the original graphics still are on the stage in whatever position they were in when reset was called
private var brickGraphic:MovieClip = new Brick();
private var brick:Body;
private var brickArray:Array;
private function setUp():void
{
brickArray = new Array ;
for (var i:int = 0; i < 10; i++)
{
var brick:Body = new Body(BodyType.DYNAMIC);
var brickShape:Polygon = new Polygon(Polygon.box(10,25));
var brickGraphic = new Brick();
brickGraphic.width = 10;
brickGraphic.height = 25;
addChild(brickGraphic);
brickGraphic.cacheAsBitmap = true;
brick.shapes.add(brickShape);
brick.position.setxy(450, ((ag ) - 30 * (i + 0.5)));
brick.angularVel = 0;
brick.shapes.at(0).material.elasticity = .5;
brick.shapes.at(0).material.density = 150;
brick.cbTypes.add(brickType);
brick.space = space;
brickGraphic.stop();
brick.userData.sprite = brickGraphic;
brick.userData.sprite.x = brick.position.x;
this.brickArray.push(brick);
}
private function reset():void
{
if (contains(brickGraphic)) removeChild(brickGraphic);
space.clear();
setUp();
}
}
this is the final issue i am having on this app and your help would be greatly appreciated
That's because you are not removing them with removeChild.
You need to call removeChild for each brickGraphic object you add to the stage.
Something like :
private function setUp():void
{
brickArray = [];
for (var i:int = 0; i < 10; i++)
{
var brick:Body = new Body(BodyType.DYNAMIC);
var brickShape:Polygon = new Polygon(Polygon.box(10,25));
var brickGraphic = new Brick();
brickGraphic.width = 10;
brickGraphic.height = 25;
addChild(brickGraphic);
brickGraphic.cacheAsBitmap = true;
brick.shapes.add(brickShape);
brick.position.setxy(450, ((ag ) - 30 * (i + 0.5)));
brick.angularVel = 0;
brick.shapes.at(0).material.elasticity = .5;
brick.shapes.at(0).material.density = 150;
brick.cbTypes.add(brickType);
brick.space = space;
brickGraphic.stop();
brick.userData.sprite = brickGraphic;
brick.userData.sprite.x = brick.position.x;
this.brickArray.push(brick);
}
}
private function removeAllBricks():void
{
for(var i:int=0; i<brickArray.length; i++)
{
var dp:DisplayObject = brickArray[i].userData.sprite as DisplayObject;
if(dp && dp.parent)
dp.parent.removeChild(dp);
}
}
private function reset():void
{
removeAllBricks();
space.clear();
setUp();
}

[AS3]Randomly do something without repeat

I've 3 movieclip on stage which is mc1,mc2,mc3
at first they are alpha=0
What I want is when i click on revealBtn, 1 of them will show up as alpha=1.
But with my code below, sometimes I need to click about 5 times or more only can make all those mc show up.
Is there any solution for what I wanted? I've try splice but it's still not working well.
var mcArray:Array = [mc1,mc2,mc3];
for (var j:int = 0; j < mcArray.length; j++)
{
mcArray[j].alpha = 0;
}
revealBtn.buttonMode = true;
revealBtn.useHandCursor = false;
revealBtn.addEventListener(MouseEvent.CLICK, revealClick);
function revealClick(event:MouseEvent):void
{
var i:Number = Math.floor(Math.random() * mcArray.length);
var movieClipToEdit:MovieClip = mcArray[i] as MovieClip;
movieClipToEdit.alpha = 1;
}
Here's one of the many possible solutions. It destroys the initial array though. If you don't want to change the initial array, the rest depends on what you actually want to achieve.
var invisibleList:Array = [mc1,mc2,mc3];
for (var j:int = 0; j < invisibleList.length; j++)
{
invisibleList[j].alpha = 0;
}
revealBtn.buttonMode = true;
revealBtn.useHandCursor = false;
revealBtn.addEventListener(MouseEvent.CLICK, revealClick);
function revealClick(event:MouseEvent):void
{
if (invisibleList.length == 0) {
return;
}
var i:Number = Math.floor(Math.random() * invisibleList.length);
var movieClipToEdit:MovieClip = invisibleList[i] as MovieClip;
invisibleList.splice(i, 1);
movieClipToEdit.alpha = 1;
}
Make a second array to use as your selection source. Every time you pick an item, Splice it from the second array. Also, since all your items are MovieClips you should use a Vector instead.
var mcVector:Vector.<MovieClip> = [mc1,mc2,mc3];
var vector2:Vector.<MovieClip> = mcVector.Slice(0); // This just copies the Vector
for (var j:int = 0; j < mcVector.length; j++)
{
mcVector[j].alpha = 0;
}
revealBtn.buttonMode = true;
revealBtn.useHandCursor = false;
revealBtn.addEventListener(MouseEvent.CLICK, revealClick);
function revealClick(event:MouseEvent):void
{
var i:Number = Math.floor(Math.random() * mcVector.length);
// Retrieves and deletes the item in one step:
var movieClipToEdit:MovieClip = vector2.Splice(i, 1);
movieClipToEdit.alpha = 1;
}

Objects stuck at top of stage and won't fall down

I am using math.random to randomly drop objects from the top of the stage. I had it working with one object. But as I wanted to increase the number to 6 objects, I added the following code: But I am "stuck" and so are the 6 objects at the top of the stage. What am I doing wrong here? I appreciate the help.
private function bombInit(): void {
roachBombArray = new Array();
for (var i:uint =0; i < numBombs; i++) {
roachBomb= new RoachBomb();
roachBomb.x = Math.random() * stage.stageWidth;
roachBomb.vy = Math.random() * 2 -1;
roachBomb.y = -10;
addChild(roachBomb);
roachBombArray.push(roachBomb);
}
addEventListener(Event.ENTER_FRAME, onEntry);
}
private function onEntry(event:Event):void {
for (var i:uint = 0; i< numBombs; i++) {
var roachBomb = roachBombArray[i];
vy += ay;
roachBombArray[i] += vy;
if (roachBombArray[i] > 620) {
removeChild(roachBombArray[i]);
removeEventListener(Event.ENTER_FRAME, onEntry);
You are trying to add the velocity to the RoachBomb rather than to the RoachBomb y position.
roachBombArray[i] += vy;
should be
roachBombArray[i].y += vy;
Additionally you create a local variable:
var roachBomb = roachBombArray[i];
but you never manipulate it.
Perhaps you meant to do something like this?
var roachBomb:RoachBomb = roachBombArray[i]; // I added the type to the local variable
roachBomb.vy += ay;
roachBomb.y += vy; // Manipulate the local variable
if (roachBomb.y > 620) {
removeChild(roachBomb);
}
You're removing your enterFrame listener when the first bomb goes off the bottom, at which point you're no longer listening for ENTER_FRAME events and updating any of your bombs.
You don't want to remove this listener until you're done animating ALL the bombs.
UPDATE: How I would expect things to look, incorperating Ethan's observation that you ought to use the local roachBomb that you declare...
public class BombDropper extends Sprite {
private static const GRAVITY:int = 1; // Set gravity to what you want in pixels/frame^2
private static const BOTTOM_OF_SCREEN:int = 620;
private var numBombs:int = 6;
private var roachBombArray:Array;
// ... constructor and other class stuff here
private function bombInit(): void
{
roachBombArray = new Array();
for (var i:int =0; i < numBombs; ++i)
{
var roachBomb:RoachBomb = new RoachBomb();
roachBomb.x = Math.random() * stage.stageWidth;
roachBomb.vy = Math.random() * 2 -1;
roachBomb.y = -10;
this.addChild(roachBomb);
roachBombArray.push(roachBomb);
}
this.addEventListener(Event.ENTER_FRAME, onEntry);
}
private function onEntry(event:Event):void
{
for each ( var roachBomb:RoachBomb in roachBombArray)
{
roachBomb.vy += GRAVITY;
roachBomb.y += vy;
if (roachBomb.y > BOTTOM_OF_SCREEN)
{
this.removeChild(roachBomb);
roachBombArray.splice(roachBombArray.indexOf(roachBomb),1);
if (roachBombArray.length == 0)
{
this.removeEventListener(Event.ENTER_FRAME, onEntry);
}
}
}
}
}

Resources