Like MenuScreen, the GameScreen class has an IN and an OUT (see Figure 3).

Figure 3. The GameScreen class is passed a movie clip with a Timeline that looks like this.
Each layer contains one movie clip. The result layer will say YOU WIN! or YOU LOSE!, depending on the outcome of the game. Layers userTxt and opponentTxt are text fields that I'll colorize with the Constants.CHARARACTER_DATA hexadecimal colors. This helps the user identify which character is talking.
Layer choices contains the list of possible insults or comebacks a gamer can choose from (see Figure 4).

Figure 4. This is the choice Timeline.
Note the container layer underneath the mask layer. That's where I'm going to add in movie clips. Each clip I add contains a button and a text field. The button is used to detect when that particular clip is clicked. The text field will be populated with an insult or a comeback from the Constants array. Remember, the user has to learn new insults. If I just looped through adding the entire list there'd be no constraints. No constraints, no fun.
Here's how it happens, step by step:
public function setGameData($knownInsults:Array,$knownComebacks:Array):void
{
_knownInsults = $knownInsults;
_knownComebacks = $knownComebacks;
Main passes a list of known insults and comebacks as arrays. Where do those arrays come from, you ask? Ah, tsk tsk. You're getting ahead of me! I'll reveal the answer to that shortly. These arrays are numbers, and these numbers correlate to the position in the Constants.INSULTS and Constants.COMEBACKS arrays. In other words, if Main passes that the user knew the insult, "You fight like a dairy farmer," and the comeback, "How appropriate, you fight like a cow," and I traced _knownInsults and _knownComebacks, the output would be [7],[7]. Get it? Everything is based off the arrays in Constants, and the arrays in Constants never change.
for(var iii:Number=2;iii>=0;iii--) { _knownInsults.unshift(iii); }
Earlier I mentioned that the user would start with 3 insults. This loop makes sure that the knownInsults array begins with [0,1,2]. So including the dairy farmer insult you and I are pretending the user has already learned, the final known insults array is [0,1,2,7] and the final known comebacks array is [7].
_knownInsultsObject = {};
_knownComebacksObject = {};
for(var i:Number=0;i<_knownInsults.length;i++) { _knownInsultsObject["key_" + _knownInsults[i].toString()] = true; }
for(var ii:Number=0;ii<_knownComebacks.length;ii++) { _knownComebacksObject["key_" + _knownComebacks[ii].toString()] = true; }
};
Oh, a curveball! What on earth is an object doing in the middle of a game based around arrays? Remember when I talked before about excess for loops? Here's where it gets tricky.
I want my game to run fast. That’s a pretty reasonable demand, right? Right. In order for it to run fast, I know I need to avoid any sort of for or while looping. The less looping, the less math. The less math, the faster the game goes.
The problem is that to this point I’m using Arrays to house all of my data. Arrays are a cluster of variables (or values) organized by a numeric key. Arrays are especially useful when you need to access data in some sort of ordered fashion. By that, I mean if you know you'll need one item, then the next item (such as, say, a list of characters or insults), and the next and so on, arrays are wonderful. The problem is that unless you explicitly know the key you need to work with, the only way to figure it out is to loop through the entire array until you find the corresponding data. So I know I’ve got this:
_knownInsults == [0,1,2,7]
Let’s pretend the computer insults the user with the dairy farmer insult—insult Number 7. How can I tell if I need to save this to the database or not? I can loop through _knownInsults every round, but then I’ve got a for loop every round. That’s slow. That’s bad.
Instead, I create an object: _knownInsultsObject. I loop through one time—at the beginning of the game. This object is based on the array, so _knownInsultsObject will look like this:
_knownInsultsObject == { key0: true, key1: true, key2: true, key7: true };
Now I have a way of checking if a key is known or not without looping through to see if it already exists in _knownInsults. Consider this:
someTestVar = 7; // blah blah blah dairy farmer.
if(_knownInsultsObject["key" + someTestVar.toString()) { /* I already know this insult */ };
This is lightweight. This is flexible. I have less looping; thus I have a faster game. And I get the exact same result.
The final, complete initialize function is thus:
public function setGameData($knownInsults:Array,$knownComebacks:Array):void
{
_knownInsults = $knownInsults;
_knownComebacks = $knownComebacks;
// always add the first 3 insults, so the user has something to start with.
for(var iii:Number=2;iii>=0;iii--) { _knownInsults.unshift(iii); }
// let's organize the data in yet another way.
// this time, I'll use an object. I'll store all the known insults and comebacks.
_knownInsultsObject = {};
_knownComebacksObject = {};
for(var i:Number=0;i<_knownInsults.length;i++) { _knownInsultsObject["key_" + _knownInsults[i].toString()] = true; }
for(var ii:Number=0;ii<_knownComebacks.length;ii++) { _knownComebacksObject["key_" + _knownComebacks[ii].toString()] = true; }
};
Now that the program knows what insults to pick from, let's create the menu. When the choice timeline (see Figure 3) finishes animating in, it dispatches an event (AnimationEvent.ANIMATE_IN) that GameScreen is waiting for. Let's step through that:
// choices for the user
private function _onChoicesAnimateIn($evt:AnimationEvent):void
{
Out.info(this,"_onChoicesAnimateIn");
// if I'm a user round then I'll add insults. otherwise I'll use comebacks.
var known:Array = _isUserRound ? _knownInsults : _knownComebacks;
var actual:Array = _isUserRound ? Constants.INSULTS : Constants.COMEBACKS;
_isUserRound switches from true to false after each parry animation, as per our game rules. It's true to start, so let's pretend that it's true right now. That means array known will be _knownInsults, and array actual will be Constants.INSULTS. Okay?
answerDictionary = new Dictionary(true);
for(var i=0;i<known.length;i++)
{
// I always start linked clips "LibraryItem_". You'll find this clip in the library.
clip = new LibraryItem_GameOption();
clip.y = (i * clip.height) + (i * 5);
// what is that dictionary for? hmm, hmm. mysterious.
__answerDictionary[clip] = known[i];
So now I run a for loop for every known insult. Each insult creates a new LibraryItem_GameOption(). This is the ActionScript 3 equivalent of attachMovie() – the GameOption clip is in the Library and will be added to the Display List.
Now, what is this business about new Dictionary()? A Dictionary is the secret brother of Array and Object. As I discussed earlier, arrays have numeric keys and objects have string keys. Dictionaries have objects as keys. Whoa. Heavy. For more on Dictionaries, read Grant Skinner's related post.
In this case, the key is the clip I just added to the Display list. The value is the number of the known insult. So:
_answerDictionary[(first GameOption)] = 0; _answerDictionary[(first GameOption)] = 1; _answerDictionary[(first GameOption)] = 2; _answerDictionary[(first GameOption)] = 3;
When the GameOption clip is triggered, I'll immediately be able to use _answerDictionary[$evt.target.parent] (I'll be adding the listener to a button nested inside the GameOption clip) to figure out which insult was chosen.
_mc.choices_mc.empty.addChild(clip); clip.txt_mc.txt.text = actual[known[i]]; // add our roll and click events clip.btn.addEventListener(MouseEvent.ROLL_OUT,_onChoiceOut,false,0,true); clip.btn.addEventListener(MouseEvent.ROLL_OVER,_onChoiceOver,false,0,true); clip.btn.addEventListener(MouseEvent.CLICK,_onChoiceClick,false,0,true); }
That's all I need. I've got each clip positioned, now I just need to wait for the user to click.
private function _onChoiceClick($evt:MouseEvent):void
{
_lastUserChoiceSelected = _answerDictionary[$evt.target.parent];
Out.debug(this,"Choice Selected: " + _lastUserChoiceSelected.toString());
_mc.choices_mc.gotoAndPlay("OUT");
};
I save the choice—the number of the insult selected, 7 (for "You fight l like a dairy farmer.") —as _lastUserChoiceSelected. And then I tell the choices movie clip to go away. Easy enough. So the choices movie clip animates away (the OUT label), and GameScreen is waiting for it to dispatch an event.
private function _onChoicesAnimateOut($evt:AnimationEvent):void
{
if(_isUserRound) _mc.userTxt_mc.glow_mc.txt.text = Constants.INSULTS[_lastUserChoiceSelected];
else
{
mc.userTxt_mc.glow_mc.txt.text = Constants.COMEBACKS[_lastUserChoiceSelected];
}
// put the text on the screen for a short while, then let the opponent respond.
var t:Timer = new Timer(_mc.userTxt_mc.glow_mc.txt.text.length * Constants.READ_SPEED);
t.addEventListener(TimerEvent.TIMER,_onUserTimer,false,0,true);
t.start();
};
The new Timer class is useful in place of setInterval. In this case, I write the text from the INSULTS array ("… dairy farmer") onto the screen. Finally. Then it stays up for a short while.
Now it's the computer's turn to make a comeback.