Navigating through an array of game objects without showing duplicate objects - actionscript-3

I'm working on a game, in my game the user acquires weapons (cupcakes) throughout the levels. I have a button that the player clicks to change to the next cupcake (weapons acquired are stored in an array).
Here's my problem: if the player acquires 2 yellow cupcakes, 1 red cupcake, and 2 blue cupcakes, how do I navigate through the array without showing the same cupcake twice?
My button looks as if it's not changing weapons, when in code it is, it's going to the next element in the array but it's the same weapon [yellow cupcake, yellow cupcake, red cupcake, blue cupcake, blue cupcake].
public function CupcakeChangeButton(e: MouseEvent) {
cakeButtonCounter++;
//Make sure the cakeButtonCounter never exceeds the amount of
//elements in array
if (cakeButtonCounter >= CupcakesArray.length - 1) {
cakeButtonCounter = 0;
}
//NOTE: may need function called "cupcake count", this function should
//be called at the beginning of THIS function and in the constructor function
//Should count the amount of cupcakes and maybe set wasSeen Elements to No
/*
The switch statment makes its decisions based on the type of cupcake
is the current elment. The current element is represented by
"cakeButtonCounter" (CupcakesArray[cakeButtonCounter])
The if statements decides if the cupcake has been seen already.
If it hasnt been seen, it sets the button's text box to show how many of
those kind of cupcakes are left.
After the amount of cupcakes of that type is shown in the text box,
the "unshift" method is used to place "yes" in the first element, of it's
own Array type, WITHIN THE WAS SEEN ARRAY.......confusing!!!!!!
Example:
wasSeen.PinkCupcake.unshift("yes") = wasSeen.PinkCupcake[0] == "yes"
This is done so that we can keep track of what cupcakes has been seen
already
When the game is initialized the was seen elements are set to "no". So when
it's set to "yes", after being seen, The initial value,"no", is placed in the
next element. It's no longer needed, so we use the "splice" method to delete it
and HOPEFULLY, remove it from memory
The "else" block of code takes place if the cupcake has already been seen.
It increments the cakeButtonCounter so that the button will display the next
cupcake in the array, that has NOT been seen
After all decisions are made,(which cupcakes are next and which cupakes have
been seen)
Update the button's face by displaying the next cupcake that hasnt been seen
(button goes to and stop and next element)
NOTE: The ACTUAL case will be the dynamic var cupcake type (the NAME not the actual
cupcake)
*/
switch (CupcakesArray[cakeButtonCounter]) {
case "PinkCupcake":
if (wasSeen.PinkCupcake[0] == "no") {
CupcakeNavigationBox.countBox.text = "x" + PinkCupcakeCount;
wasSeen.PinkCupcake[0] == "yes";
trace("element change? " + wasSeen.PinkCupcake[0]);
} else {
cakeButtonCounter++;
trace("if yes...its starting that way " + wasSeen.PinkCupcake[0]);
}
break;
case "YellowCupcake":
if (wasSeen.YellowCupcake[0] == "no") {
CupcakeNavigationBox.countBox.text = "x" + YellowCupcakeCount;
wasSeen.YellowCupcake[0] == "yes";
} else {
cakeButtonCounter++;
}
break;
case "GreenCupcake":
if (wasSeen.GreenCupcake[0] == "no") {
CupcakeNavigationBox.countBox.text = "x" + GreenCupcakeCount;
wasSeen.GreenCupcake[0] == "yes";
} else {
cakeButtonCounter++;
}
break;
case "PurpleCupcake":
if (wasSeen.PurpleCupcake[0] == "no") {
CupcakeNavigationBox.countBox.text = "x" + PurpleCupcakeCount;
wasSeen.PurpleCupcake[0] == "yes";
} else {
cakeButtonCounter++;
}
break;
}
CupcakeNavigationBox.buttonFace.gotoAndStop(CupcakesArray[cakeButtonCounter]);
changeCupcake();
}

You should instead store available types of weapons in the array from where you choose the weapon type. Also, that array should contain ints, because apparently your cupcakes are spent on fire/action, thus you will have the ammo storage array at your disposal. If you need unlocked array, by all means make another array. (Make sure there's a default weapon, though, or else cake selection function can go into an infinite loop)
var cupcakesAmmo:Array=[0,0,0,0,0,0,0]; // as many as you have types of cupcakes
var cupcakesTypes:Array=[RedCupcake,YellowCupcake,PinkCupcake,GreenCupcake,BlueCupcake,PurpleCupcake,BlackCupcake];
// let's say we have seven cupcakes
var currentCupcake:int; // the selected weapon
function cupcakeChangeButton(e: MouseEvent) {
// first check if we actually have anything to change to
var weHave:int=0; // how many different cupcakes we have
var i:int;
for (i=0;i<cupcakesAmmo.length;i++) if (cupcakesAmmo[i]>0) weHave++;
if (weHave<2) return; // hehe, we either have no cupcakes or just one type of em
// otherwise let's change
do { // we have to do this at least once
currentCupcake++;
if (currentCupcake==cupcakesAmmo.length) currentCupcake=0; // change type
} while (cupcakesAmmo[currentCupcake]==0); // change until we find the type with nonzero ammo
// okay, type selected, let's get proper display
CupcakeNavigationBox.countBox.text = "x" + cupcakesAmmo[currentCupcake];
// see, no switch is needed! Just get the data off your array :)
// TODO change the displayed cupcake too (I don't see where you do this)
}

Related

How to push textbox value into Array and recall it

This is the first time I ask in this website, if there's any mistake and inappropriate thing apologize in advance
I was trying to make my own basketball score board using Action Script 3 but I am stuck on the way of calling a player fouls score and show it individually.
In the picture, the second box is where to type a player number who made a foul and the third box is where the number shows how many times this player has fouled.
I need to know how to code an array store that receives a value from the 'Player' textbox as the player number and stores the fouls count with the specific player's number too (if I type another player number it will count a foul separately and next time I type the exist number it will call out how many times he fouls)
You could use an array, or a dictionary, or even dynamic properties.
Let's assume your text fields are called txtTeam1fouls, txtPlayer, txtFouls, txtTeam2fouls. Let's also say you have a var called curTeam that stores an integer identifier for the team whose player number you enter (for this example, either 1, or 2).
Here is an example of storing a basic object in an Array:
var fouls:Array = []; //create a new empty array
//add a listener for when you type something into the player text input
txtPlayer.addEventListener(KeyboardEvent.KEY_UP, updatePlayer);
//this function retries a foul record from the array for a specific player
function getFouls(player:int, teamId:int):Object {
//loop through the array until you find a match
for(var i:int=0;i<fouls.length;i++){
if(fouls[i].player === player && fouls[i].team === teamId){
return fouls[i];
}
}
//if no record in the array, return 0
return null;
}
//this function updates the foul text field when you change the what's in the player text field
function updatePlayer(e:Event):void {
var foulRecord = getFouls(int(txtPlayer.text), curTeam);
//if a foul record exists, use it's foul count, if not use 0
txtFouls.text = foulRecord ? foulRecord.fouls.toString() : 0;
}
//call this function whenever you add a new foul record.
function addFoul(player:int, teamId:int):void {
//first, see if there is an existing foul record in the array
var foulObj:Object = getFouls(player, teamId);
if(!foulObj){
//if there was no record, create one, then push (add) it to the array
foulObj = {team: teamId, player: player, fouls: 1};
fouls.push(foulObj);
}else{
//if there is an existing record, increment it.
foulObj.fouls++;
}
//now update the totals for each team
var team1Ctr:int = 0;
var team2Ctr:int = 0;
for(var i:int=0;i<fouls.length;i++){
switch(fouls[i].team){
case 1:
team1Ctr++;
break;
case 2:
team2Ctr++;
break;
}
}
txtTeam1Fouls.text = team1Ctr.toString();
txtTeam2Fouls.text = team2Ctr.toString();
}

Word randomization in Actionscript

In Actionscript, I'm trying to find a way to produce strings and string variables which spit out different text each time they're brought up. To purely visualize:
var text:String "Red||Blue"; //Whenever the variable is called, it's either Red or Blue
textoutput("You spot a grey||black cat."); //A function equivalent of the same issue
I can produce a function which does this effect, but it seems a variable cannot be a function, as far as I can tell.
I've considered array variables, but I have no idea how to use an array to spit out a single entry when the variable is called, and I don't know how to make this work for a string that isn't a variable -- assuming I can get away with a single system that works for both situations.
Edit
To expand upon the issue expressed in Batman's answer, using his result on a variable produces a result that 'sticks' to whichever it randomly chooses. Example:
var shoes:String = grabRandomItem("Red shoes||Crimson shoes");
trace("You have " + shoes + ".") //Whichever result is chosen it stays that way.
Moreover, I may want to change this variable to something else that is entirely not-random:
var secondshoes:String = "Blue shoes";
function newshoes():
{
shoes = secondshoes;
}
You want a random value from a list of possible values. Rather than call the variable, you can reference it dynamically...
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;
}
var options:Array = ["red", "blue", "grey", "black"];
trace("You spot a " + options[random(0, options.length-1)] + " cat.")
//You spot a black cat.
Alternatively, you can use a function in place of a variable to remove the inline logic...
function catColor():String { return options[random(0, options.length-1)]; }
trace("You found a " + catColor() + " key.")
// You found a red key.
Or generalize it to a generic function with arguments.
var options:Object = {
"cat":["grey", "black"],
"key":["gold", "silver"],
"door":["blue", "red", "green"]
}
function get(item:String):String {
return options[item][random(0, options[item].length-1)];
}
trace("You found a " + get("door") + " door.")
// You found a green door.
There are a ton of ways to do this, but to align with the way you'd like to do it, here is the simplest way to accomplish this:
//populate your string: (remove the word private if using timeline code)
private var text_:String = "Red||Blue||Green||Yellow";
//create a getter to use a function like a property
function get text():String {
//create an array by splitting the text on instances of ||
var arr:Array = text_.split("||");
//return a random element of the array you just made
return arr[Math.floor(Math.random() * arr.length)];
}
trace(text);
Even better, create a common function to parse your string:
function grabRandomItem(str:String):String {
var arr:Array = str.split("||");
return arr[Math.floor(Math.random() * arr.length)];
}
//make a getter variable that regenerates everytime it's called.
function get label():String {
return "You spot a " + grabRandomItem("grey||black||blue||red||purple") + " cat";
}
trace(label); //anytime you access the label var, it will re-generate the randomized string
trace(label);
trace(label);
trace(label);
// ^^ should have different results
Of course, this way I think only works best if the text comes from user input. If you are hard coding the text into the app, you might as well just create it in an array directly as show in another answer you have as there's less overhead involved that way.

Save Number with Shared Object and Add to that Saved Number.

Hey everyone so basically what I am trying to accomplish is saving a number with the Shared Object which the the coins that the player collects in the game and if they player quits out of the game and comes back to play again the amount of coins he had when he quit will still show and he will be able to add to that amount if the player picks up more coins. I had this working for a bit but then the textfield started displaying "NAN".
Here is how I have it set up.
The Variable private var nCoins:Number;
In the constructor function I have:
sharedObjectCoins = SharedObject.getLocal("CoinsData");
nCoins = 0 + sharedObjectCoins.data.tCoins;
if (sharedObjectCoins.data.tCoins == null)
{
sharedObjectCoins.data.tCoins = nCoins;
}else
{
trace("Save data found."); // if we did find data...
loadDataTimeAttack(); // ...load the data
}
and in the games Enter.Frame Loop I have the function saveDataCoins which is setup like so:
private function saveDataCoins():void
{
if (nCoins > sharedObjectCoins.data.tCoins )
{
sharedObjectCoins.data.tCoins = nCoins;
}
coinsGraphic.coinsText.text = " " + sharedObjectCoins.data.tCoins;
sharedObjectCoins.flush();
}
not sure if you need the function to where the hitTest takes place between the coins and player but here it is:
private function checkPlayerHitCoins():void
{
for (var i:int = 0; i < aCoinsArray.length; i++)
{
//get current point in i loop
var currentCoins:mcCoin = aCoinsArray[i];
//test if player is hitting current point
if(player.hitTestObject(currentCoins))
{
nCoins += 1;
updatecoinsTextScore();
updateCoinsPauseScreen();
//Add points sound effects
var coinsSEffect:Sound = new coinsSound();
coinsSEffect.play();
//remove point on stage
currentCoins.destroyCoins();
//remove points from array
aCoinsArray.splice(i, 1);
trace("Hit: " + aCoinsArray.length);
}
}
}
Please if anyone could help me with this maybe point something out that I am doing wrong. This code worked perfect one time and when I closed the screen and came back to re test it the textfield displayed NAN and thats it when I hitTest the coins sometimes the NAN switches to a number for like a second but then goes back to NAN.
The first time (or rather every time it creates a new shared object) you will be trying to add undefined to 0, which will result in either a runtime error or NaN.
You need to check if the value exists before attempting to do addition with it.
if(sharedObjectCoints.data && sharedObjectCoins.data.tCoins && !isNaN(sharedObjectCoins.data.tCoins)){
nCoins = Number(sharedObjectCoins.data.tCoins); //there's not point in adding 0
trace("Save data found."); // if we did find data...
loadDataTimeAttack(); // ...load the data
}else{
sharedObjectCoins.data.tCoins = nCoins;
}
Also, if you don't manually set a value to a number var, it will start off life as NaN. eg var nCoins:Number will be NaN until you set it to something.
That said, working with the sharedObject directly like this is a very sloppy way to code your program. Really you should just use shared object to load and save the value, and everything in between use a strongly typed variable.
var nCoins:int = 0;
var tCoins:int = 0;
sharedObjectCoins = SharedObject.getLocal("CoinsData");
if(sharedObjectCoins.data && sharedObjectCoins.data.tCoins && !isNaN(sharedObjectCoins.data.tCoins){
tCoins = int(sharedObjectCoins.data.tCoins);
}else{
//no shared object, use default value for tCoins
tCoins = 0; //or whatever it should start off as.
}
Then write a save function
private function saveSharedObject():void {
sharedObjectCoins.data.tCoins = tCoins;
sharedObjectCoins.flush();
}
Then replace all other instances of sharedObjectCoins.data.tCoins with the var tCoins
It's probably best not to flush the shared object every frame for performance purposes.
Also, shared objects may or may not actually save, depending on user preferences, storage space available, etc. They should not be relied upon for critical data retention.
You can listen for problems with the shared object with AsyncErrorEvent.ASYNC_ERROR I believe (It's been a while since I've worked with AS3 Shared Objects)

Using two eventlisteners for one function in Actionscript

I'm making a dots and boxes game in flash using actionscript 3.
Currently I'm completely stuck as I want the player to click two buttons before a line is displayed. I planned to do something like this using the if statement
if button1 and button2 clicked
line1 visible = true
I've also tried adding eventlisteners with one function
function showLine(e:Event):void {
blue0001.visible = true
}
dot00.addEventListener(MouseEvent.CLICK, showLine);
But as far as I know this can only be used when you want to click one button. Is there anyway to have two eventlisteners satisfyed before the function is carried out?
Also how would I (if i can) use the if statements to carry out this?
You would probably do something like this, pseudo-code:
Assume all of the dots are in a dots array.
for (var i: Number = 0; i < dots.length; i++) {
dots.addEventListener(MouseEvent.CLICK, dotClicked, false, 0, true);
}
dotSelected = null;
function dotClicked(evt:MouseEvent):void {
if (dotSelected && isNeighbor(evt.target, dotSelected)) {
showLineConnecting(evt.target, dotSelected)
dotSelected = null;
} else if (!dotSelected) {
highlightDot(evt.target);
dotSelected = evt.target;
} else {
showError("You must click an adjacent dot");
}
}
At the request of the OP, this is what's going on.
for (var i: Number = 0; i < dots.length; i++) {
dots.addEventListener(MouseEvent.CLICK, dotClicked, false, 0, true);
}
Add an event listener to every dot. Here I am assuming you already have an array of dots defined. Each instance in the Array would be a MovieClip (likely), Sprite, or another DisplayObject.
dotSelected = null;
We will use a variable to keep track of any currently selected dot. Since no dot will be selected when the game starts, we set it to null.
function dotClicked(evt:MouseEvent):void {
if (dotSelected && isNeighbor(evt.target, dotSelected)) {
showLineConnecting(evt.target, dotSelected)
dotSelected = null;
} else if (!dotSelected) {
highlightDot(evt.target);
dotSelected = evt.target;
} else {
showError("You must click an adjacent dot");
}
}
This is the function that will get called when any dot is clicked. For explanation's sake, let's take the first click of the game. dotSelected is null, so the first if is false. The second if though is true, because (!dotSelected) is true. So, we run some function I called highlightDot with the dot as the argument. That function could look something like this:
function hightlightDot(dot:Dot):void {
dot.gotoAndStop("selected");
}
Now a second click is made. Now the first part of the first if, dotSelected, is true. The second part is now evaluated. Again I put in a made up function isNeighbor. The isNeighbor function takes two arguments, the dot that was just clicked and the dot that has already been clicked. This function needs to make sure the two dots are adjacent. This could be something like...
function isNeighbor(dot1:Dot, dot2:Dot):void {
return ((dot1.xGrid == dot2.xGrid && Math.abs(dot1.yGrid - dot2.yGrid) == 1) || (Math.abs(dot1.xGrid - dot2.xGrid) == 1) && dot1.yGrid == dot2.yGrid));
}
The above function assumes that the instances of Dot have some properties xGrid and yGrid which defines where in the playing board they sit. If they are in the same row and 1 column apart they are neighbors. If they are in the same column and 1 row apart they are neighbors.
The last thing that happens in a function showLineConnecting is called. That function will again take the two adjacent dots as arguments. It will then draw a line between them in whatever way you choose to do that. Finally, dotSelected is set back to null, allowing another set of dots to be selected.
One thing I just realized, it would probably be helpful to have an additional property that gets triggered on a Dot when it is connected to all of its neighbors so it could no longer be selected.
You will also need logic to handle knowing that a box has been created. For that you would likely just iterate the possibilities given the line that was just drawn. For each line drawn there are only two possible boxes that have been created. So check them both. Watch out for edges.

Flex: spark DataGrid displays value other than the current value in dataProvider

I have 2 flex datagrids on a screen. The user can select one or more rows from either datagrid and move them to the other datagrid. The lower table is empty when entering the application. For example:
Item Color Price
--------------------
item57 red $5.55
item62 blue $5.29
item808 green $2.21
Row Item Color Price
---------------------------
Note there is a column that numbers the rows on the bottom datagrid (only).
When I enter the application and move, for example, 3 rows from the top to bottom grid, the row numbers are fine (they show rows 1, 2, and 3). For example:
Item Color Price
--------------------
Row Item Color Price
---------------------------
1 item57 red $5.55
2 item62 blue $5.29
3 item808 green $2.21
If I then move, for example, row 1 in the bottom grid back to the top...
Item Color Price
--------------------
item57 red $5.55
Row Item Color Price
---------------------------
1 item62 blue $5.29
2 item808 green $2.21
and then back again to the bottom grid...
Item Color Price
--------------------
Row Item Color Price
---------------------------
1 item62 blue $5.29
2 item808 green $2.21
1 item57 red $5.55
the row number is supposed to display 3 because it inserts into the bottom grid at the end of the list, but when it does this, it displays the (old) row number value of 1.
When I debug and look at the dataprovider = _myData, I see the rowNumber value for the row in question (for item57 above) equals 3 (as it should). However, it is displayed in the lower datagrid as 1.
How can the dataprovider value be different than what is displayed in the DataGrid?
[I can also debug and look at gridLower column information, and it also shows the correct value of 3 for rowNumber for the data in question.]
The lower datagrid is similar to the following (although I'm using a custom itemRenderer, removed for simplicity here):
[Bindable]
private var _myData:ListCollectionView=new ListCollectionView(new ArrayList());
...
<s:DataGrid dataProvider="{_myData}">
<s:columns>
<fx:Array>
<s:GridColumn id="gridLower" headerText="myHeader" dataField="rowNumber"/>
...
The function that adds the upper table's row(s) to the lower table is:
private function addRow():void {
var selectedIndices:Object=gridUpper.grid.selectedIndices;
for (var i:int=selectedIndices.length-1;i>=0;i--) {
var item:Object=_upperTableData.removeItemAt(selectedIndices[i]);
item.rowNumber=_myData.length+1;
_myData.list.addItem(item);
}
// I tried adding "_myData.refresh();" here and it had no effect
// I tried adding "ListCollectionView(gridLower.dataProvider).refresh();" and it had no effect
// I tried adding "grid2.dataProvider.refresh();" here but it had no effect
}
UPDATE 1: If I re-sort any column in the lower table, the correct values appear. I seem to be observing what's reported in this link:
http://www.barneyb.com/barneyblog/2007/06/23/another-flex-weirdnessgotcha/
Haven't found a solution yet though. See attempts in my addRow() function above. Am I on the right track?
UPDATE 2: While re-sorting manually corrects the data in the lower grid, I haven't found a way to do this programmatically. I tried inserting:
_myData.sort=null;
var complete:Boolean=_myData.refresh();
just before the end of addRow() function above, but it didn't resolve my issue. When debugging, complete is true, but still the lower grid displays the stale data.
New answer :) I will delete the old one if this helps.
I haven't used Spark DataGrid yet, was expecting it to behave like a List.
Found this in this in some comments in the source for this method of DataGrid:
public function invalidateCell(rowIndex:int, columnIndex:int):void
You can invalidate an entire row/col by passing -1 for the other value. In the quote from the docs below, you can also use dataProvider.itemUpdated()
If the specified cell is visible, it is redisplayed. If
variableRowHeight=true, then doing so may cause the height of the
corresponding row to change. If columnIndex is -1, then the entire row
is invalidated. Similarly if rowIndex is -1, then the entire column is
invalidated.
This method should be called when there is a change to any aspect of
the data provider item at rowIndex that might have some impact on the
way the specified cell is displayed. Calling this method is similar to
calling the dataProvider.itemUpdated() method, which advises the Grid
that all rows displaying the specified item should be redisplayed.
Using this method can be relatively efficient, since it narrows the
scope of the change to a single cell.
Now I finally know where the itemUpdated() method on collections (ArrayCollection, ListCollectionView) can actually be used!
[edit]
Give your grid an id:
<s:DataGrid id="lowerDataGrid" dataProvider="{_myData}">
Then you should be able to do this in your addRow() method after updating the collection:
lowerDataGrid.invalidateCell(item.rowNmber -1, 1); // assuming rowNumbers are 0 based
In my case, in my main class that contains the grid as an element, whenever I want to force a refresh of all the cells:
gridsDataProvider.addItemAt(new Object(), 0);
gridsDataProvider.removeItemAt(0);
But my GridItemRenderer needs to cater for the change in the data. So I created an updateDisplay method:
private function updateDisplay() : void {
if (transWindow != null) {
if (data == transWindow.total) {
if (field != "vat" && field != "total") {
textInput.visible = false;
} else {
line.visible = true;
textInput.enabled = false;
textInput.setStyle("fontWeight", "bold");
textInput.setStyle("fontSize", 14);
}
} else {
line.visible = false;
textInput.visible = true;
textInput.enabled = true;
textInput.setStyle("fontWeight", "normal");
textInput.setStyle("fontSize", 12);
}
}
}
This method is called from both the creationComplete handler ...
protected function init(event:FlexEvent):void
{
getTransWindow(event.target.automationOwner);
updateDisplay();
}
... well as the data setter of the GridItemRenderer
override public function set data(v:Object):void {
super.data = v;
if (v == null) {
textInput.text = "";
return;
}
var value : * = v[field];
if (value == null || value == undefined) {
textInput.text = "";
return;
}
if (value is Number)
textInput.text = Number(value).toFixed(2);
else
textInput.text = ""+value;
updateDisplay();
}
The mxml body of my renderer is quite simple:
<s:Line id="line" width="100%" visible="false" yFrom="1" yTo="1">
<s:stroke>
<s:SolidColorStroke color="0" weight="0"/>
</s:stroke>
</s:Line>
<s:TextInput id="textInput" y="2" restrict="0123456789." width="100%" height="100%" minWidth="10" borderVisible="false" textAlign="right"/>
O, in case you wondered, the getTransWindow method is just a method to retrieve the main calling class that contains the grid:
private function getTransWindow(par : *) : void {
if (transWindow != null)
return;
transWindow = par;
while (!(transWindow is TransactionWindow)) {
transWindow = transWindow.parent;
}
}
Hope this saves someone some time
Regards
Christo