I’ll warn you now—this project will be a difficult one and will test your knowledge of ActionScript to the limit! Since the last project, you have learned how to take your projects outside the browser and bring them to the desktop and to mobile devices.
The structure of this project is like the others—you’ll review the project specs, design and kick-off meeting notes, then figure out how to complete the project on your own.
This project adds an unfortunate circumstance—your design comps are flattened files, and there are no assets you can import, meaning you have to re-create them in Flash Pro. D’oh! Although this is a situation that a good team should try to avoid at all costs, it happens, and knowing how to still meet the project requirements is critical.
Up for the challenge? I thought so.
Flipr is a casual game for the Android platform and was designed by an external design agency.
The game is based around a grid of 16 tiles. Each tile changes color when tapped with a finger. To complete a level of the game, the user taps the tiles to display different colors until the pattern of colors matches a randomly generated pattern, taking the user to the next level. The user has 120 seconds to complete as many puzzles as possible.
The difficulty level increases as the number of colors in the patterns increase:
• Level 1: 2 colors
• Level 2 and 3: 3 colors
• Level 4 and 5: 4 colors
• Level 6 and 7: 5 colors
• Levels 8 and beyond: 6 colors
When a user starts the game, he has the option of showing the game instructions, and then starting the game, or quitting the application.
When the game timer expires, the user sees a “Game Over” screen and can either play again or quit the app.
A visual design agency created sample mockups of the screens. Unfortunately, they are compressed JPEGs, so you can use them only as a visual reference.
The first, a splash screen (Figure P3.1), should appear for a brief period when the user starts the app on their device.
Figure P3.1. Flipr title screen
After the delay, the user will see the main menu for the game (Figure P3.2), which gives them three options: play the game, see instructions, or quit the app.
Figure P3.2. Controls for the main menu of the Flipr game
When the user taps “Instructions,” he will see the game’s instructions displayed on the screen (Figure P3.3).
Figure P3.3. Instructions screen
During your review of the Instructions screen, you noticed that the puzzle completion time was wrong, and there was also no way to return to the main controls screen. You note that you’ll need to address this design issue in the final app.
Back on the main controls screen, when the user taps “Start Game,” the game starts with a few elements in it (Figure P3.4).
Figure P3.4. The main game screen
First, you see that the game level is in the background of the window and the tile grid is in the center of the screen. At the bottom are progress bar “themometers” showing the percentage match of the secret puzzle code, and the amount of time left over.
You immediately notice that all the tiles in the comp are yellow; variations on the tile colors which display when the player taps a tile (based on the project specification) are not available, so you note that you’ll have to design those yourself. In addition, the “Game Over” screen design is missing, so—sigh—you will need to make that too.
Grr!
Time to meet with the team and kick things off.
After documenting the lack of designs, you notify the team lead that this project will take longer than expected because of the extra work.
The logic of the game seems to make sense, but determining how to save the pattern and the pattern of the current tiles will need to be managed in an array of some sort.
Since the client is looking only for an Android app, you can save some time in the development ; no need to test iOS-based devices.
Another team member noted that when previewing a previous mobile project, the preview mode used in Flash Professional detects mouse events. A mobile project works based on touch events, so the preview mode can’t decipher between mouse events and touch events. He noted that you can get around this by creating a duplicate event listener for MouseEvent.CLICK
and send it to the same event callback function as the TouchEvent.TOUCH_TAP
event type. The trick is to change the event object type in the callback function definition from TouchEvent
to simply Event
, since both of these are descendants of the generic Event
class.
There was a brief discussion on how to manage the different levels of your application using event objects. Someone mentioned that at times, the object that is broadcasting a user event is at a deeper scope than expected. To resolve this, use the parent
property after an instance name to change the scope to the object’s container. For example, if an object called myObject
is within another object called myContainer
, the statement myObject.parent
would refer to the myContainer
object, thus giving it access to myContainer
properties and methods.
Another tidbit was that you can use a property called currentCount
to track the number of intervals processed of a running timer.
Finally, one team member indicated that on a previous project, she didn’t realize that they could add custom properties to classes that extend MovieClip
or Sprite.
She suggested that it would be very helpful to provide each instance of a MovieClip
or Sprite
with custom information that could be used later in the project.
A challenging product—but that’s why you are here, to solve challenging problems and make cool stuff.
So, give this a try.
Wow...quite a challenge, wasn’t it? In my personal experience, I have encountered projects like this far more often than I would like, but it is a fact of the business of interactive design, and we are all professionals, so pushing through and making it happen is what sets us apart.
Here is one way to solve this challenge. Because there are so many open-ended ways to approach this project, this is just an example—but it may introduce some solutions that you hadn’t considered. The following walkthrough is how I approached the project.
Since you had to start this project from scratch, you needed to redesign the user interface. In this solution, I created parts of the design in Fireworks and imported them into Flash Professional, while others were drawn directly into Flash Professional and converted into Sprites or MovieClips (Figure P3.5).
Figure P3.5. The splash screen
The main project contains a timeline organized into folders for the objects that are used in the game. Each game screen is denoted as a section on the timeline with frame labels to allow you to navigate from screen to screen using ActionScript.
The Library is organized into folders, with most objects saved as Sprites, since they don’t need timelines.
The splash screen displays the game logo in Bodini font, which is also embedded in the project to ensure that it works correctly.
The controls screen (Figure P3.6) contains the same game logo (called the masthead) in the Library, and three buttons, each with a unique design. These have the instance names of startGameButton
, instructionsButton
, and endGameButton
.
Figure P3.6. The controls screen
This screen displays static text for the instructions (Figure P3.7). Since there was no button to allow the user to return back to the main menu in the original design, it has been added with the instance name goBackButton.
Figure P3.7. The instructions screen
The game screen is where most of the action takes place (Figure P3.8). Here is a single object called gameBoard
, which has the gameLevelText
text field, a simple drawing object for the game background, and two MovieClips.
Figure P3.8. The main game screen
The first MovieClip, perComplete
, shows the percentage complete, the other MovieClip is the countdown timer, gameTimer.
Each MovieClip has a timeline that animates the thermometer portion of the control (Figure P3.9).
Figure P3.9. Construction of the percentage complete movie clip
Finally, the game over screen (Figure P3.10), for which there wasn’t a design, contains some static text, and two buttons. The first button, startGameButton
, restarts the game; the second button, endGameButton
, quits the app.
Figure P3.10. The missing game over screen
All the objects that are displayed on the Stage have been set as either Export as Bitmap or Cache as Bitmap, located in the Properties panel when the object is selected to improve on-device performance.
That’s the basics, let’s get to the code!
In my project, the code for the project is broken into five classes. Let’s start the walkthrough by going through the flow of the app.
Here is the contents of the Document class:
package
{
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.display.Sprite;
import flash.events.TouchEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.desktop.NativeApplication;
public class Flipr extends MovieClip
{
public var splashTimer:Timer;
public function Flipr()
{
stop();
_init();
}
private function _init():void
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
_displaySplash();
}
/************************************************************
**SPLASH SCREEN
*/
private function _displaySplash():void
{
splashTimer = new Timer(500);
splashTimer.addEventListener(TimerEvent.TIMER, _endSplash);
splashTimer.start();
}
private function _endSplash(e:TimerEvent):void
{
splashTimer.stop();
splashTimer.removeEventListener(TimerEvent.TIMER, _endSplash);
gotoAndStop("controls");
_setupControls();
}
/***********************************************************
**MAIN CONTROLS
*/
private function _setupControls():void
{
instructionsButton.addEventListener (TouchEvent.TOUCH_TAP, _displayInstructions);
startGameButton.addEventListener(TouchEvent.TOUCH_TAP, _startGame);
endGameButton.addEventListener(TouchEvent.TOUCH_TAP, _endGame);
instructionsButton.addEventListener(MouseEvent.CLICK, _displayInstructions);
startGameButton.addEventListener(MouseEvent.CLICK, _startGame);
endGameButton.addEventListener(MouseEvent.CLICK, _quit);
}
/***************************************************************
**INSTRUCTIONS
*/
private function _displayInstructions(e:Event):void
{
gotoAndStop("instructions");
goBackButton.addEventListener(MouseEvent.CLICK, _removeInstructions);
}
private function _removeInstructions(e:Event):void
{
gotoAndStop("controls");
_setupControls();
}
/***************************************************************
**GAME
*/
private function _startGame(e:Event):void
{
gotoAndStop("game");
gameBoard.newGame();
gameBoard.addEventListener("gameOver", _endGame);
}
/***************************************************************
**END GAME
*/
private function _endGame(e:Event):void
{
gotoAndStop("gameOver");
startGameButton.addEventListener(TouchEvent.TOUCH_TAP, _startGame);
endGameButton.addEventListener(TouchEvent.TOUCH_TAP, _endGame);
startGameButton.addEventListener(MouseEvent.CLICK, _startGame);
endGameButton.addEventListener(MouseEvent.CLICK, _quit);
}
/***************************************************************
** QUIT
*/
private function _quit(e:Event):void
{
NativeApplication.nativeApplication.exit();
}
}
}
You start with all the imports for the project—there are a lot. This means you are learning a lot, so pat yourself on the back.
The first element created in the class is the timer used to move from the splash screen to the main controls screen. Pretty simple opening; let’s move on to the constructor:
public function Flipr()
{
stop();
_init();
}
Not much there. First, you need to stop the playback of the main timeline and then start an initialization method.
private function _init():void
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
_displaySplash();
}
Here, the input mode for the Multitouch gestures are defined, using the TOUCH_POINT
method over GESTURE
, since this game doesn’t require any multi-point gestures to work.
There is then a method call to display the splash screen:
private function _displaySplash():void
{
splashTimer = new Timer(500);
splashTimer.addEventListener(TimerEvent.TIMER, _endSplash);
splashTimer.start();
}
Here you define the timer, which displays for a half-second since the splashTimer
was set with a 500 millisecond duration, and then adds the event listener for the timer. The timer is then immediately started.
When the timer runs out, it executes the _endSplash
callback method:
private function _endSplash(e:TimerEvent):void
{
splashTimer.stop();
splashTimer.removeEventListener(TimerEvent.TIMER, _endSplash);
gotoAndStop("controls");
_setupControls();
}
Here, the timer is stopped, and the event listener is removed. I always try to remove listeners when they aren’t needed anymore as a best practice. I find that when you start creating much larger projects, having “unremoved” listeners can impact performance. The playhead is moved to the “controls” frame label, and the event listeners for the various buttons are added using the _setupControls
method:
private function _setupControls():void
{
instructionsButton.addEventListener(TouchEvent.TOUCH_TAP, _displayInstructions);
startGameButton.addEventListener(TouchEvent.TOUCH_TAP, _startGame);
endGameButton.addEventListener(TouchEvent.TOUCH_TAP, _endGame);
instructionsButton.addEventListener(MouseEvent.CLICK, _displayInstructions);
startGameButton.addEventListener(MouseEvent.CLICK, _startGame);
endGameButton.addEventListener(MouseEvent.CLICK, _quit);
}
Here, the event listeners for the three buttons (instructions, start, stop) are created. Based on the recommendations from the kick-off meeting, there are two sets. This is to allow testing in preview mode with the mouse, as well as on-device tapping with touch gestures. The first is using TouchEvent.TOUCH_TAP.
Then a matching set using MouseEvent.CLICK
with identical callback functions is used to register the mouse event when previewing the product from Flash Professional and not a physical device.
The instructionsButton
, when clicked or tapped, executes the _displayInstructions
method:
private function _displayInstructions(e:Event):void
{
gotoAndStop("instructions");
goBackButton.addEventListener(MouseEvent.CLICK, _removeInstructions);
}
From there, the playhead is moved to the label “instructions” and the event listener is created for the goBackButton
object. When clicked, the instructions need to go away and return to the main controls, this is done with the function _removeInstructions:
private function _removeInstructions(e:Event):void
{
gotoAndStop("controls");
_setupControls();
}
This sets the playhead back to “controls” and then reruns _setupControls.
To display the buttons and make sure that they are set up correctly.
From the main controls screen, if the endGameButton
is pressed, the _quit method
runs:
private function _quit(e:Event):void
{
NativeApplication.nativeApplication.exit();
}
Now you tell the native application to exit and return to the launcher.
If the user clicks the startGameButton
, you get things going by running the _startGame
method:
private function _startGame(e:Event):void
{
gotoAndStop("game");
gameBoard.newGame();
gameBoard.addEventListener("gameOver", _endGame);
}
This is deceptively simple, because all the game functionality has been passed to the GameBoard
class. But first, you move the playhead to the “game” frame label, and then tell gameBoard
to execute a public method named newGame.
You also listen to a custom gameOver
event that will be broadcasted from gameBoard
, which then runs the _endGame
method.
Let’s look at the code in the GameBoard
class that houses the logic and interaction for the game.
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TouchEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class GameBoard extends Sprite
{
// Create arrays for tile objects, values of tiles,
// and the puzzle solution
// Array of Tile MovieClips
var tileArray:Array = new Array();
// Array of the current color shown
var tileValueArray:Array = new Array();
// Array of the solution pattern
var tilePuzzleArray:Array = new Array();
// General game variables
// Current level of the game
var currentLevel:uint;
// Total correct tiles matching pattern
var totalRight:uint;
// Number of tile varieties available in the level
var tileVariety:uint;
// Create game timer
// Game timer
var gameClock:Timer;
public function GameBoard()
{
_init();
}
// Initialize game
private function _init():void
{
// Create the tile grid
for (var i:uint = 0; i < 16; i++)
{
var newTile:Tile = new Tile();
newTile.cacheAsBitmap = true;
newTile.x = (i % 4) * 110 + 10;
newTile.y = Math.floor(i/4) * 110 + 10;
newTile.idNo = i;
newTile.addEventListener(MouseEvent.CLICK, _clickTile);
newTile.addEventListener(TouchEvent.TOUCH_TAP, _clickTile);
addChild(newTile);
tileArray.push(newTile);
tileValueArray.push(0);
}
// General score display updater
addEventListener(Event.ENTER_FRAME, _updateDisplay);
// Configure the game clock and listeners
gameClock = new Timer(1000,120);
gameClock.addEventListener(TimerEvent.TIMER, _timerSecond);
gameClock.addEventListener(TimerEvent.TIMER_COMPLETE, _gameOver);
}
// Event callback for timer interval
private function _timerSecond(e:TimerEvent):void
{
gameTimer.setTimer(gameClock.currentCount);
}
// Event callback for timer completion, or game over
private function _gameOver(e:TimerEvent):void
{
// Dispatch a custom event
this.dispatchEvent(new Event("gameOver"));
}
// Event callback for clicking on a tile
private function _clickTile(e:Event):void
{
// Access the event object's target's parent,
// or the MovieClip they clicked on
var clickedTile:uint = e.target.parent.idNo;
// Increment the tile value
tileValueArray[clickedTile]++;
// Determine if tile value is outside of the variety
// range and adjust if so
if (tileValueArray[clickedTile] >= tileVariety)
{
tileValueArray[clickedTile] = 0;
}
// Update the display of the clicked tile
tileArray[clickedTile].displayTile (tileValueArray[clickedTile]);
// Rescore the game
_scoreGame();
}
// Public function called from the main application to start
// the game
public function newGame():void
{
currentLevel = 1;
_setupGame();
}
// Private function that sets up a game from within the
// class, reused when reaching new levels
private function _setupGame():void
{
// Reset the amount correct
totalRight = 0;
// Clear the puzzle solution array
tilePuzzleArray = [];
// Determine the tile variety count based on the current
// level
if (currentLevel < 2)
{
tileVariety = 2;
}
else if (currentLevel < 4 )
{
tileVariety = 3;
}
else if (currentLevel < 6 )
{
tileVariety = 4;
}
else if (currentLevel < 8 )
{
tileVariety = 5;
}
else
{
tileVariety = 6;
}
// Create the puzzle solution
for (var i:uint = 0; i < 16; i++)
{
var newValue:uint = Math.random() * tileVariety;
tilePuzzleArray.push(newValue);
}
// Create the initial random set of tiles
for (var i:uint = 0; i < 16; i++)
{
var setRandomTile:uint = Math.random() * tileVariety;
tileArray[i].displayTile(setRandomTile);
tileValueArray[i] = setRandomTile;
}
// Score the game with the initial set of values
_scoreGame();
// Start the game clock
gameClock.start();
}
// Score the game based on the puzzle solution and currently
// displayed tiles
private function _scoreGame():void
{
// Reset the score
totalRight = 0;
// Process through all the tiles and score
for (var i:uint = 0; i < 16; i++)
{
if (tilePuzzleArray[i] == tileValueArray[i])
{
totalRight++;
}
}
// Test if game is won, if so, increase level and
// restart
if (totalRight == 16)
{
gameClock.stop();
currentLevel++;
_setupGame();
}
}
// Event callback for entering frame to update the score and
// timer indicators
private function _updateDisplay(e:Event):void
{
perComplete.setComplete(totalRight);
gameLevelText.text = String(currentLevel);
}
}
}
This code is the meat of the project. The Document class is moving the user around the various screens. Let’s start with variables that are in the class:
// Create arrays for tile objects, values of tiles, and the puzzle
// solution
// Array of Tile MovieClips
var tileArray:Array = new Array();
// Array of the current color shown
var tileValueArray:Array = new Array();
// Array of the solution pattern
var tilePuzzleArray:Array = new Array();
// General game variables
// Current level of the game
var currentLevel:uint;
// Total correct tiles matching pattern
var totalRight:uint;
// Number of tile varieties available in the level
var tileVariety:uint;
// Create game timer
// Game timer
var gameClock:Timer;
Because there is so much going on here, heavy use of comments is recommended; otherwise, you will easily forget what is going on.
First there are two arrays that are created. The first is an array of MovieClips that will be created to build the game board. The second are the values of the tiles that are displayed on the game board so you know what colors are currently being displayed. The last array will hold the secret puzzle code that the user needs to match to advance to the next level.
The next set of variables holds the current level of the game, the total number of tiles that match the secret pattern, and a variable that will tell the game engine how many color variations there are in the current level that will be calculated based on the rules of the technical specification.
Finally, a timer for the game clock is created.
The constructor executes an initialization method that starts to set up the user interface of the game, the game board, the rules of the game, and the interface controls:
// Initialize game
private function _init():void
{
// Create the tile grid
for (var i:uint = 0; i < 16; i++)
{
var newTile:Tile = new Tile();
newTile.cacheAsBitmap = true;
newTile.x = (i % 4) * 110 + 10;
newTile.y = Math.floor(i/4) * 110 + 10;
newTile.idNo = i;
newTile.addEventListener(MouseEvent.CLICK, _clickTile);
newTile.addEventListener(TouchEvent.TOUCH_TAP, _clickTile);
addChild(newTile);
tileArray.push(newTile);
tileValueArray.push(0);
}
// General score display updater
addEventListener(Event.ENTER_FRAME, _updateDisplay);
// Configure the game clock and listeners
gameClock = new Timer(1000,120);
gameClock.addEventListener(TimerEvent.TIMER, _timerSecond);
gameClock.addEventListener(TimerEvent.TIMER_COMPLETE, _gameOver);
}
I like to create layout mathematically if possible. I find that it leads to more adaptable layouts that can be adjusted easily with a couple tweaks rather than forcing me to go into the FLA and adjust the layout visually. The tile grid is created using a for
loop that creates a new instance of the Tile
class, sets the cacheAsBitmap
property to true
, and positions it on the screen.
The math for this is a little tricky. The x
property is calculated based on a modulo operation. Since the grid is four by four, you can use that to your advantage to build the game board. For reference, look at Figure P3.11.
Figure P3.11. The game board showing index numbers and modulo calculations
If the index number for each item was applied with a modulo operation using 4 (the number of items in a row), you can get a number you can then multiply with the width of the tile, offset based on the inset margin of the container.
For example, tile number 12, when using modulo with the value 4, returns 0 since 12 is evenly divisible by 4. 0 multiplied by the width of the tile (my design used a width of 110 pixels) becomes 0, and then you add a padding of 10 to center in the grid. Another example is tile number 10. In this case the module operation returns 2, which when multiplied by the width gives 220, plus the padding, gives an x coordinate of 230.
Pretty cool, huh? Let’s look at y
next.
For the y
property, you need to do something a bit different. By using simple division, you can determine if a number, which divided by 4 and removing the decimal, sits on a specific row in the grid (Figure P3.12).
Figure P3.12. The game board showing index numbers and results to calculate y
Let’s take tile 1 as an example first. If you take the index number, divide by 4, and drop the decimal, you end up with 0. Which, you guessed it, can be multiplied with the height of the object with a padding offset. Another example is tile 14—when divided by 4 and the decimal dropped results in 3, which can then be positioned using the same calculation.
This example is obviously putting your math and layout skills to the test. An alternative here could be to position the objects on the screen and then add them to an array, which is similar to the method used in the DiceOut project, but this method offers a lot more flexibility—what if you needed to have a five by five grid instead? With just the modification of a few values, that can easily be arranged.
With the positioning finished, the tile is given a custom property called idNo
. This is used to store which tile will need to be accessed when you click or tap it and which tile it is in the sequence.
User events are then listened to in the next line: one for click and the other for tap.
The tile is added to the display stack and then pushed to the array so you can loop through them easily later in the project. An initial value of 0 is assigned to the value array, just for good measure.
Next, you create an event listener that will fire with each frame. This is used to update the display of the game timer and percentage complete. Immediately following that, the game clock is configured to run for 120 seconds with a 1 second interval. Matching functions for the TIMER
and TIMER_COMPLETE
events are created. The first will execute _timerSecond:
private function _timerSecond(e:TimerEvent):void
{
gameTimer.setTimer(gameClock.currentCount);
}
Here, the gameTimer
MovieClip is then accessed, running a public setTimer
method, sending the current interval count of the timer to the object. The game timer is another class called GameTimer:
package
{
import flash.display.MovieClip;
public class GameTimer extends MovieClip
{
public function GameTimer()
{
stop();
resetTimer();
}
public function resetTimer():void
{
gotoAndStop(1);
}
public function setTimer(newTime:uint):void
{
var timeLeft:uint = 120 - newTime;
timeLeftText.text = timeLeft + "s";
gotoAndStop(newTime);
}
}
}
This class has a resetTimer
method that sets the playhead to the first frame. setTimer
takes the interval count provided and calculates the frame that the playhead should be positioned at, and then updates the text field with the total number of seconds left. Since the interval count goes up with each interval and you are displaying the timer as counting down, the number needs to be modified to create the countdown effect. For example, when the timer starts, it has an interval count of 0, but the display shows 120 seconds remain.
private function _gameOver(e:TimerEvent):void
{
// Dispatch a custom event
this.dispatchEvent(new Event("gameOver"));
}
When the timer is complete, the _gameOver
method runs, which dispatches the custom event “gameOver” that the main Document class captures and sends the user to the game over screen.
Whew! Now everything is staged, but the game itself needs to be created. If you remember, this is done in the public newGame
method that is called from the Document class. You are going to skip the _clickTile
method for now—but you’ll get to it soon.
// Public function called from the main application to start the
// game
public function newGame():void
{
currentLevel = 1;
_setupGame();
}
// Private function that sets up a game from within the class,
// reused when reaching new levels
private function _setupGame():void
{
// Reset the amount correct
totalRight = 0;
// Clear the puzzle solution array
tilePuzzleArray = [];
// Determine the tile variety count based on the current level
if (currentLevel < 2)
{
tileVariety = 2;
}
else if (currentLevel < 4 )
{
tileVariety = 3;
}
else if (currentLevel < 6 )
{
tileVariety = 4;
}
else if (currentLevel < 8 )
{
tileVariety = 5;
}
else
{
tileVariety = 6;
}
// Create the puzzle solution
for (var i:uint = 0; i < 16; i++)
{
var newValue:uint = Math.random() * tileVariety;
tilePuzzleArray.push(newValue);
}
// Create the initial random set of tiles
for (var i:uint = 0; i < 16; i++)
{
var setRandomTile:uint = Math.random() * tileVariety;
tileArray[i].displayTile(setRandomTile);
tileValueArray[i] = setRandomTile;
}
// Score the game with the initial set of values
_scoreGame();
// Start the game clock
gameClock.start();
}
The public newGame
method sets the currentLevel
variable to 1, and then runs the private _setupGame
method. Inside of _setupGame
, two variables are reset: totalRight
and tilePizzleArray
, which contains the puzzle solution.
The rules of the specification are used to calculate the number of the possible tile varieties for the tileVariety
variable.
A for
loop is used to create the puzzle solution. The value of the tile is determined using a random number multiplied by the tileVariety
variable, providing additional variety options as the game level increases. This value is then pushed to the tilePuzzleArray.
To randomize the board, the same calculation is used to set the display of the tiles on the board and to then provide the tileValueArray
with the matching number that can be used to determine the puzzle is complete.
Each tile is an instance of the Tile
class that contains a little bit of functionality:
package
{
import flash.display.MovieClip;
public class Tile extends MovieClip
{
public var idNo:uint;
public function Tile()
{
_init();
}
private function _init():void
{
stop();
}
public function displayTile(tileNo:uint):void
{
gotoAndStop(tileNo+1);
}
}
}
The Tile
MovieClip displays a different colored tile on each frame. The displayTile
method is used to take the value number created to show which tile color is displayed, which starts with 0, and convert that to an appropriate value for the frame numbers, which start at 1.
With the puzzle solution set and the initial tiles configured, the game is initially scored based on the random starting configuration using the _scoreGame
method:
private function _scoreGame():void
{
// Reset the score
totalRight = 0;
// Process through all the tiles and score
for (var i:uint = 0; i < 16; i++)
{
if (tilePuzzleArray[i] == tileValueArray[i])
{
totalRight++;
}
}
// Test if game is won, if so, increase level and restart
if (totalRight == 16)
{
gameClock.stop();
currentLevel++;
_setupGame();
}
}
Here the totalRight
variable is reset, and you loop through the various arrays. First, you ask if the element of the puzzle solution is equal to that of the tile’s displayed value. If it is, the totalRight
variable is incremented by 1.
After the array loop finishes you ask if the total right is equal to 16, meaning that all the tiles match the secret pattern. If so, gameClock
is stopped, the current level is incremented 1, and you start all over again with _setupGame
.
What happens when you click or tap a tile? This is the method you skipped earlier—it is named _clickTile:
private function _clickTile(e:Event):void
{
var clickedTile:uint = e.target.parent.idNo;
// Access the event object's target's parent,
// or the MovieClip they clicked on
// Increment the tile value
tileValueArray[clickedTile]++;
// Determine if tile value is outside of the variety range and
// adjust if so
if (tileValueArray[clickedTile] >= tileVariety)
{
tileValueArray[clickedTile] = 0;
}
// Update the display of the clicked tile
tileArray[clickedTile].displayTile(tileValueArray[clickedTile]);
// Rescore the game
_scoreGame();
}
You create a variable within the method that stores the value of the idNo
of the item that was clicked. When using the event callback object, the target in this example is not the tile itself, but the sprite that displays the various colored variations. By using the parent
property, you can access the right object and get the idNo
value.
When you tap the tile, you want to bump up the tile’s color to the next color value, which is done by incrementing the tile value array using the clickedTile
property to select the right element. To make sure that the color of the tile doesn’t fall outside the variety of colors determined for the current level, an if
statement is used to test the current value against the tile variety count. If it exceeds it, it is reset back to 0.
Finally, the tile itself is adjusted to display the right color, and the game is rescored (Figure P3.13).
Figure P3.13. The fruits of your labor
Amazing to think that this game is using only the ActionScript that you have learned in this book. While there are some intermediate concepts or approaches used in this example, it illustrates how far you can take ActionScript just based on the skills you have learned so far.
But this is just the start of your adventure with ActionScript. I encourage you to dive deeper and create amazing web sites, desktop applications, or mobile apps using what you have learned in this book.
Oh, and if you ever meet me in person, say hello, or contact me on Twitter under the handle @sfdesigner and show me what you have been able to create with ActionScript. I’m always amazed by the creative projects that people build using ActionScript.
Congratulations! You have a lot to be proud of.
18.188.96.232