Game Logic


In this section we will discuss the logic involved in all aspects of the game, from choosing a word-list category to generating the grid and selecting words. In certain cases we will specifically look at code, but in most cases I will explain what each function does without taking you through it line by line.

Choosing a Category

Before you can choose a category (which is just any one of the word lists in the wordlists.xml file), the file must be loaded and the information extracted. Let's look at the ActionScript on frame 1.

 1   doc = new XML();  2   doc.ignoreWhite = true; 3   doc.load("wordlists.xml"); 

First we create an XML object called doc in which we want to load the file. Then we load it using the load() method of the XML object. Since we can't do anything in the game until this file is loaded, we add a condition to the pre-loader for the game. You can see this on frame 2.

 1   factor=(_root.getBytesLoaded()+doc.getBytesLoaded())/      (_root.getBytesTotal()+doc.getBytesTotal()); 2   percent.text=Math.floor(factor*100)+"%"; 3   if (factor>=1 && doc.loaded) { 4      _root.gotoAndStop("Splash"); 5   } 

graphics/14fig04.gif

Imagine that the XML file is very large, say 50 KB. Then adding this XML file to your pre-loader would be a necessity. If you did not, then the game file would display the main menu, and the user could attempt to start playing a game before the words were even loaded! In line 1 above, we create a variable called factor. It is the total number of bytes loaded, divided by the total number of bytes in the file. When factor is multiplied by 100, the result is the percentage that has been loaded. So, for instance, if wordsearch.swf is 80 KB and wordlist.xml is 20 KB, then the denominator of that ratio is 100. If at one point wordsearch.swf has 60 KB loaded and wordlist.xml has 10 KB, then the numerator is 70. So then the percentage loaded at that time is (70/100)*100 = 70%. In line 3 we check to see if factor is greater than or equal to 1 and if doc is fully loaded. When doc is fully loaded, its loaded property is true. If both of these conditions are met, then everything is finished loading and it is OK to proceed to the Splash frame. Otherwise, the playhead moves to the next frame and then back to this frame again for another check.

From the Splash frame the user can click Start Game and get taken to the Select frame. Here we interpret the XML in the doc object and build and display the categories in a ListBox component. Here are the first few lines of ActionScript:

 1   words = {};  2   words.lists = []; 3   playButton._alpha=50; 

First we create an object called words. This object will be used to store the word lists and eventually will be used to store all of the information about the game. In line 2 we create an array in the words object called lists. This array will contain one object for each category. The object will contain the list of words in that category as well as the category's name. Next we set the _alpha property of the playButton to 50. This is to give a visual indication that you cannot proceed until a category is chosen. When a category is chosen, playButton's _alpha is set back to 100.

Next, the following function is created and then executed:

 1   function init() { 2      var temp = []; 3      var temp = doc.firstChild.childNodes; 4      scrollingList = []; 5      for (var i = 0; i<temp.length; ++i) { 6         var tempList = temp[i].childNodes; 7         var category = temp[i].attributes.category; 8         var wordArray = []; 9         for (var j = 0; j<tempList.length; ++j) { 10           var word = tempList[j].firstChild.nodeValue; 11           wordArray.push(word); 12        } 13        words.lists.push({wordList:wordArray,           category:category}); 14        scrollingList.push({label:category, data:i}); 15     } 16     scrollList.setDataProvider(scrollingList); 17     scrollList.setChangeHandler("myHandler"); 18  } 19  init(); 

This function steps through the XML in the doc XML object and extracts all of the information we need. An array of words is created for each category (line 11) and then stored in an object that describes that category (line 13). Also stored in that object is a property called category that stores the category's name. This object is pushed into the lists array. For example, the object for the first category, Types of Fruit, is stored as the first element in the lists array, lists[0]. This object, lists[0], contains the property category whose value is "Types of Fruit", and an array called wordList, whose values are the words of the category. By the time the function gets to line 16, all of the information from the XML has been extracted and stored properly. The ListBox component on the stage has an instance name of scrollList. In line 16 we set a data provider for this list by pointing it to the scrollingList array, which contains the name of each category. The ListBox component then takes this information and automatically populates the list. Then, in line 17 we change the function that is called when an item is selected. We do this with the setChangeHandler() method and pass in the name of the function we would like to have called. So when a category is clicked, the myHandler() function is called.

Up to this point you have seen the ActionScript needed to load the XML file, parse it, store the data logically, and display the categories in a list. Now let's look at what happens when a category is selected.

 1   function myHandler() { 2      playButton._alpha = 100; 3      playButton.enable = true; 4      var categoryIndex = scrollList.getSelectedItem().data; 5      words.words = words.lists[categoryIndex].wordList; 6      words.category = scrollList.getSelectedItem().label; 7   } 

The function above, myHandler(), is called whenever a category is selected. First the playButton is given an _alpha value of 100 so that it appears to be enabled. Then we set a variable in the playButton instance called enabled to true. An if statement on the button in the playButton instance checks to see if enable is true before it (the button) responds to being clicked. In line 4 we set a variable called categoryIndex. This variable stores a number the number of the category that was selected in the list. Then, in line 5 we create an array in the words object called words. The value of this array is set by using categoryIndex and pointing to the wordList array on the object that represents that category. In short, this line creates an array of the words that will be in the game from the category selected. In line 6 we store a property on the words object called category. The value of category is the string name of the category selected. So, if Types of Fruit were selected, then words.category would be "Types of Fruit".

graphics/14fig05.gif

After a category has been selected, we have (as seen above) an array of words called words and the name of the category both stored in the words object. We can now safely move the frames needed to build the board.

Generating the Grid

Creating the grid layout from the list of words is the toughest part of this game. Words can be written forward or backward; can be vertical, horizontal, or diagonal; can even cross through each other. The logic used to do all of this is not too complex but it's not that easy, either! What is complex is the tall function, approximately 130 lines, that handles the bulk of this logic. In this section we will pick up where we left off in the previous section and look at everything needed to create a unique word-search grid on the screen.

We left off in the previous section after a category was selected. Once this is done, it is OK for the user to click the Play button. When Play is clicked, the game is taken to a frame called Generate. This frame contains all of the movie clips needed for the game screen. Here are the two actions in this frame:

 1   generating._visible=true;  2   play(); 

There is a movie-clip instance on the stage called generating. We set its _visible property to true. The first time you reach the Generate frame, the generate movie clip is already visible. However, when the game is restarted and this frame is visited again, generate is not already visible, so we make it visible with this action. We want this movie clip visible while the game is computing the grid layout, so that the game player knows what is going on. Line 2 above just tells the timeline to keep playing. Two frames later we reach the Game label and stop there.

The Game frame is the location of all the ActionScript that handles creating the grid, detecting word selections, and restarting the game. We will look at four functions in this section: scrambleWords(), displayList(), createBoard(), and restart().

ScrambleWords() takes the word list in memory and randomly maps those words into the board layout. It is the large function mentioned above and handles all of the logic needed to create the grid in memory. This function does not perform any physical placement of movie clips or text fields on the stage.

CreateBoard() takes the results from scrambleWords() and actually builds the board layout with all of the movie clips, adding random letters to fill all the blank spaces.

DisplayList() creates the list of words that shows on the left side of the screen. This function is also called whenever a correct word has been selected; it handles marking off a word in the list after it's been found.

Restart() simply removes all circle movie clips that have been created, if any, and then sends the movie back to the Generate frame label.

But first, let's look at the initial actions that need to happen to get this game going. The following ten lines of ActionScript, at the bottom of the Game frame, are not contained within a function.

 1   scrambleWords();  2   if (scrambledOK) { 3      wordList.createEmptyMovieClip("lines", 1); 4      category.text = words.category; 5      generating._visible = false; 6      displayList(); 7      createBoard(); 8   } else { 9      restart(); 10  } 

When the Game frame is reached, these actions are performed. First, the scrambleWords() function is called. We will talk about that function in detail below, but for now just assume the following: If scrambleWords() does its job successfully, then the variable scrambledOK is set to true. If it does not succeed, then scrambledOK is set to false. If false, then the game is restarted, and the movie will keep looping between the Game frame and the Generate frame until scrambleWords() does its job successfully. (We will talk about what determines success below.) If scrambledOK is true, then lines 3 7 are executed. Line 3 creates an empty movie clip within the wordList instance. The wordList movie clip is the one that will contain the list of words that are in the grid. The empty movie clip, called lines, is where the lines will be drawn through any words in the list that have been selected in the grid. We will discuss this in the next section. In line 4 we simply display the name of the category in a text field. Next, we set the visibility of the generating movie clip to false, since the generating is finished. We execute the displayList() function so that the list of words is shown on the left, and then call the createBoard() function so that the grid is made visible on the screen. You may recall that before createBoard() is called, the board exists only in memory.

What you have just seen is the big picture. First we attempt to create the board in memory. If we can't, then we restart and try again. If we can, then the board is created on the stage.

scrambleWords()

Now let's look at what the scrambleWords() function does. Here is pseudo-code that represents what is done in the function:


1   set maxTime to 5000
2   set scrambledOK to true
3   create two-dimensional letters array
4   create listings array
5   set now to getTimer()
6   for each word in words.wordList
7           randomly choose alignment
8           randomly choose direction
9           set tempWord from the word
10         set wordLength to tempWord.length
11         push object which stores tempWord, direction, and alignment
            onto listings
12         if direction is backward
13               reverse tempWord
14         if alignment is horizontal
15               set notDone to true
16               while notDone
17                     row = random row
18                     startx = random(boardsize-wordlength)
19                     notDone = false
20                     for each letter in tempWord
21                            set tempLetter to current letter
22                            if grid spacing contains something and it's not
                               tempLetter
23                              notDone=true
24                     if notDone is false
25                           store the word letters in the letters array
26                     if getTimer() - now > maxTime
27                           set scrambledOK to false
28                           break
29         else if alignment is vertical
30                          set notDone to true
31                          while notDone
32                               column = random column
33                               startY = random(boardsize-wordlength)
34                               notDone = false
35                               for each letter in tempWord
36                                set tempLetter to current letter
37              if grid spacing contains something and it's not
                 tempLetter
38                 notDone=true
39           if notDone is false
40              store the word letters in the letters array
41           if getTimer() - now > maxTime
42              set scrambledOK to false
43              break
44     else if alignment is diagonal
45        set notDone to true
46        while notDone
47           startX = random(boardsize-wordlength
48           startY = random(boardsize-wordlength)
49           notDone = false
50           for each letter in tempWord
51              set tempLetter to current letter
52              if grid spacing contains something and it's not
                 tempLetter
53                 notDone=true
54           if notDone is false
55              store the word letters in the letters array
56           if getTimer() - now > maxTime
57              set scrambledOK to false
58              break

This function is pretty well commented in the actual ActionScript in the FLA file. With the explanation of the pseudo-code given here and the function itself, you should be able to understand what is going on in scrambleWords().

First we set a variable called maxTime to 5000. This number serves as a cutoff for the amount of time we will allow the function to run. In this function we place words randomly. But if you've ever tried to make your own word jumble or crossword puzzle on paper, you'll know that this procedure is not foolproof; sometimes the script places words in positions that make it impossible to place any more words! In the case of the code we've written for this particular game, the loops in scrambleWords() would continue indefinitely looking for available slots that do not exist. To prevent this happening, we set this maxTime variable. During every while loop, we check to see how long the function has been running. If it has been running for a longer time than maxTime, then we break out of the loop and the function. This is when we would consider the scrambleWords() function not to have been successful, and as a result, the restart() function will be called.

Next, we set scrambledOK to true. This variable is set to false if maxTime is ever reached. We then create a two-dimensional array called letters on the words object. Each element in this array corresponds to a letter in the grid. The letters are inserted into the array as we place the words. At the end of the scrambleWords() function the only letters in the array are the ones from the words. So there are many blank elements. It is not until the createBoard() function is called that we fill in the remaining empty spaces.

In line 4 we create an array called listings. This array will contain one element for each word that is placed in the grid. The element is an object that stores information about the word. Next, we set a variable called now to store the current time. We can use that later to determine how long the function has been running.

The rest of the code, lines 7 58, is performed for each word in the wordList array. We randomly choose an alignment for a word (horizontal, vertical, or diagonal). Then we randomly choose a direction for the word to be spelled (forward or backward). Then, in line 9, we create a variable called tempWord, which stores the current word. We also create a variable, called wordLength, to store the length of the word. In line 11 we create an object that stores tempWord, the alignment, and the direction of the word and pushes it onto the listings array. This array is used later to determine if a word has been selected. We will see more of this in the next section. If the direction randomly chosen is backward, then we reverse the order of the letters in tempWord (line 12).

What happens next depends on the alignment that's been randomly chosen. There is a giant conditional with one branch for each of the three possible alignments. Each of these three branches works quite similarly. I will explain what happens in the horizontal branch and then mention the minor differences in the other two branches.

If the script has chosen horizontal alignment, then the "horizontal branch" (lines 15 28) is executed. We set a variable called notDone to true. Then we execute a while loop that will keep looping until notDone is no longer true. In the loop we randomly choose a row for this word to appear in. We then randomly choose an x position for the word's starting point. This random starting position isn't completely random, though; we base it on the width of the grid minus the length of the word. So if the grid's width is 20 and the word's length is 7, then we can start the word on row 13 or earlier. At this point we have a row in which to place the word and a starting x position, which is the equivalent of choosing a random column. Next, we loop through each letter in tempWord and compare the letter with the grid spacing in which this letter can be placed. If that space contains nothing or if it already contains this same letter, then we continue on and check the next letter. However, if we find a letter in that grid spacing other than the one that we are currently using, then we abort this loop and start over with new random starting positions. The script runs through this loop until it finds an acceptable position in the grid. If the loop takes too long (as previously discussed), then the entire function is aborted and the restart() function is called.

The branches of the conditional that handle vertical and diagonal alignments are very similar. The vertical alignment randomly chooses a column and then randomly chooses a starting y position based on the grid size and the length of tempWord. The while loop is then performed in the same way as it was for the horizontal alignment. For the diagonal branch of the conditional statement, the only difference is in choosing the starting position; both the starting x position and the starting y position are chosen from random numbers, based on the grid size and the word length.

graphics/tip_icon.gif

The scrambleWords() function is well commented throughout the ActionScript, so don't worry about being able to follow along with it.

createBoard()

The createBoard() function takes the randomly placed words from the scrambleWords() function and places movie clips on the screen to represent them. Then, if a grid spacing is blank (as most of them are), createBoard() assigns a random letter to that position.

 1   function createBoard() { 2      path = this.board; 3      path.depth = 0; 4      path.circles = 0; 5      gridSpacing = 17; 6      for (var i = 0; i<boardSize; ++i) { 7         for (var j = 0; j<boardSize; ++j) { 8            var clipName = "letter"+i+"_"+j; 9            path.attachMovie("letter", clipName,              ++path.depth); 10           path[clipName]._x = i*gridSpacing; 11           path[clipName]._y = j*gridSpacing; 12           var tempLetter =              words.letters[i][j].toUpperCase(); 13           if (tempLetter == undefined || tempLetter == "") { 14              var tempLetter = chr((random(26)+65)); 15              words.letters[i][j] = tempLetter; 16              path[clipName].dummy = true; 17           } 18           path[clipName].letter.text = tempLetter; 19        } 20     } 21  } 

In line 2 we create a reference to the board movie clip called path. The board instance is where we will attach all of the movie clips to hold the letters. It will also contain the circles, when they get drawn. Next we set the variable depth to 0 in the board movie clip, using the path reference. This number will be incremented for every movie clip attached and used to assign each of those movie clips a unique depth. In line 4 we set circles to 0. When circles are drawn in the board instance to select a word, a new movie clip is created to hold that circle, and this variable is incremented. After the game is over, we can then easily remove all of the circles, because this variable tells us how many there are. In line 5 we create a variable called gridSpacing. This will represent the distance we need to have between the registration points of each letter. The registration point is the top-left corner of the movie clip called letter, which will be attached for each letter. It has a linkage-identifier name of letter.

Next, we perform a nested loop to make the grid. In line 8 we create a name for each movie clip we are about to attach. We'll use the same naming convention as we did in Chapter 7, "Tile-Based Worlds," but with a slight variation: We'll use "letter" instead of "cell" as the naming stem, and we'll start the counting from 0 instead of 1. (It is common, when accessing arrays, to count from 0, since array values start at index 0.) For instance, letter3_5 is found in column 4 and row 6. Next, we add the new instance of the letter movie clip to the grid and position it. In line 12 we set a local variable called tempLetter from the element sitting in the corresponding spot in the two-dimensional array called letters. If that spot was occupied by a letter from one of the randomly placed words, then it will contain a letter. Otherwise it will contain nothing.

In line 13 we check to see if a letter in column i and row j exists. If it doesn't, we generate one randomly. Every character even tabs and carriage returns can be represented by a numeric value. This number is called an ASCII value. The ASCII numbers 65 90 represent the letters A Z (A=65, B=66, and so on). The ASCII numbers 97 122 represent the letters a z. Flash contains a function called chr() that returns a letter from an ASCII value. So, chr(65) returns "A." Using this expression, chr((random(26)+65)), we can have a random letter from A to Z returned (line 14). That is how we populate the blank grid spacings. In line 15 we take this new letter and store it in the two-dimensional letters array. Then we store a variable called dummy on the new letter movie clip with a value of true. We use this variable later when determining if selected text belongs to a word.

graphics/14fig06.gif

displayList()

This function handles the creation of the list of words to be displayed on the left side of the game screen. If a word has been selected in the game, it is crossed out in this area.

 1   function displayList() { 2      _root.tempFormat = new TextFormat(); 3      _root.tempFormat.font = "arial"; 4      _root.tempFormat.size = 12; 5      var tempValue = ""; 6      for (var i = 0; i<words.listings.length; ++i) { 7         var tempWord = words.listings[i].word; 8         if (!words.listings[i].found) { 9            tempValue += "<font color=\"#000099\">"+              tempWord+"</font><br>"; 10        } else { 11           tempValue += "<font color=\"#009999\">"+              tempWord+"</font><br>"; 12           _root.wordList.list.htmlText = tempValue; 13           var width = _root.tempFormat.getTextExtent              (tempWord).width+15; 14           var y = _root.wordList.list.textHeight-5; 15           _root.wordList.lines.lineStyle(2, 0x990000, 100); 16           _root.wordList.lines.moveTo(0, y); 17           _root.wordList.lines.lineTo(width, y); 18        } 19     } 20     _root.wordList.list.htmlText = tempValue; 21  } 

It's not hard to understand the big picture of what this function does. It uses HTML-formatted text to display the word list in an HTML text field. If a word has not yet been selected, it appears as one color; if it has, it appears as another color with a line marked through it. In this function we loop through the list of words in the listings array. If you remember, the listings array contains objects that represent each word. If there is a property on a word object called found that has a value of true, then the word has been marked as found.

In the first few lines we create a new text format. This text format is not to be applied directly to a text field. We create it for the sole purpose of being able to use the getTextExtent() method of the textFormat object. With getTextExtent() we can find out how wide a certain phrase of text will be. We use that information when drawing a line through a word to cross it out (lines 13 17). If you're using a Macintosh and the cross-out line extends all the way across the stage, it's not your eyes going bad it's a bug we haven't traced yet.

After every word in the listings array has been inspected and formatted in the tempValue variable, we set this value in the text field (line 20).

restart()

This function was discussed briefly at the beginning of this section. It is very short and simple. It removes all of the circle movie clips and then sends the movie back to the Generate frame label.

 1   function restart() { 2      for (var i = 0; i<=board.circles; ++i) { 3         board["circle"+i].removeMovieClip(); 4      } 5      _root.gotoandPlay("generate"); 6   } 

graphics/14fig07.gif

The for loop uses the value of the circles variable to know how many circle movie clips need to be removed. In line 5 the movie is instructed to go back to the Generate frame label. The game has now been restarted.

Detecting a Choice

When you click anywhere on the grid, a blue circle appears. If you keep the mouse button pressed and move the mouse around, one end of the circle (or, more accurately, the oval) stays pinned in the original spot but can freely rotate. The other end of the oval stretches to match your mouse position. The result is that you appear to be circling a group of letters. I am not going to discuss the ActionScript needed to move this circling movie clip around; I think you'll be able to easily understand it by looking at the ActionScript on the circler movie clip. (There's an instance of that movie clip to the left of the stage in the main timeline on the Game frame.) What you should know, though, is that when you (as a user) attempt to select text, the circler movie clip calls the selected() function and passes in the mouse's initial and end positions. From these two positions, the selected() function can tell which letters were selected. It then checks these selected letters against the list of words, using a function called checkList(). If the word is found, then a permanent circle is created around the word and the displayList() function is called to update the list, now showing the found word crossed out.

graphics/14fig08.gif

Here is the selected function:

 1   function selected(downX, downY, upX, upY) { 2       var x1 = Math.floor(downX/gridspacing); 3       var y1 = Math.floor(downY/gridspacing); 4       var x2 = Math.floor(upX/gridspacing); 5       var y2 = Math.floor(upY/gridspacing); 6       var tempWord = undefined; 7       if (y1 == y2) { 8          if (x2>x1) { 9             for (var i = x1; i<=x2; ++i) { 10              tempWord += words.letters[i][y1]; 11           } 12        } else if (x1>x2) { 13           for (var i = x2; i<=x1; ++i) { 14              tempWord += words.letters[i][y1]; 15           } 16        } 17     } else if (x1 == x2) { 18        if (y2>y1) { 19           for (var i = y1; i<=y2; ++i) { 20              tempWord += words.letters[x1][i]; 21           } 22        } else if (y1>y2) { 23           for (var i = y2; i<=y1; ++i) { 24              tempWord += words.letters[x1][i]; 25           } 26        } 27     } else if (x1 != x2 && y1 != y2 && Math.abs(x1-x2) ==        Math.abs(y1-y2)) { 28        var xSign = (x2-x1)/Math.abs(x2-x1); 29        var ySign = (y2-y1)/Math.abs(y2-y1); 30        var steps = Math.abs(x2-x1); 31        for (var i = 0; i<=steps; ++i) { 32           tempWord += words.letters[x1+xSign*i][y1+ySign*i]; 33        } 34     } 35     if (tempWord != undefined) { 36        if (checkList(tempWord)) { 37           var x1 = x1*gridspacing+gridspacing/2; 38           var x2 = x2*gridspacing+gridspacing/2; 39           var y1 = y1*gridspacing+gridspacing/2; 40           var y2 = y2*gridspacing+gridspacing/2; 41           var rise = y2-y1; 42           var run = x2-x1; 43           var angle = Math.atan2(rise, run)*180/Math.PI; 44           var distance = Math.sqrt(rise*rise+run*run); 45           var name = "circle"+(++board.circles); 46           board.attachMovie("circler", name, ++board.depth); 47           var clip = board[name]; 48           clip._x = x1; 49           clip._y = y1; 50           clip._rotation = angle; 51           clip.right._x = distance; 52           clip.lineStyle(0, 0x000099, 100); 53           clip.moveTo(0, 9.3); 54           clip.lineTo(distance, 9.3); 55           clip.moveTo(0, -9.3); 56           clip.lineTo(distance, -9.3); 57           clip._alpha = 50; 58           displayList(); 59        } 60     } 61  } 

First, we determine from the mouse positions the grid spacings that the mouse was over when the mouse button was pressed and when it was released. (This is a simple math trick that was explained in Chapter 7, "Tile-Based Worlds"; "spacing" from that chapter works the same as "grid spacing" here.) We then check the coordinates of these spacings against three conditions. There are three valid relative positions of these coordinates: They can be in the same column (vertical); that is, x1 is the same as x2. They can be in the same row (horizontal); that is, y1 is the same as y2. Or they can be diagonal, in which case the absolute value of the difference in x1 and x2 is the same as the absolute value of the difference in y1 and y2. If none of these conditions are met, then the selected letters are not a valid choice. If one of these conditions is met, then the user has made a selection in an appropriate way, but we still need to check to see if the letters form a word in the word list.

In each of the three conditions above, we use for loops, moving from the starting position to the end position, to build the selected word from the individual letters. We store this built word as tempWord. In line 35 we check to see if tempWord has a string value. If it doesn't, then the function is over, and the user sees nothing happen (except that the circle that he or she attempted to draw disappears). If tempWord has a value, then we check it against the list of words using the function called checkList(). This function just loops through the available words and checks it in both forward and reverse directions. If the word matches, then a result of true is returned; otherwise false is returned. If true, lines 37 58 are executed. They add an instance of the circler movie clip and draw a circle around the selected text using Flash's drawing API. This leaves a permanent circle on the board to mark what the user has already found. In line 58 the displayList() function is called. This rebuilds the list on the left side of the screen so that it shows the newly found word crossed out.

You have seen the majority of what makes this game work. There are several little functions used to handle things like mouse-down and mouse-up events that we aren't discussing in this chapter but that should be easily understood.



Macromedia Flash MX Game Design Demystified(c) The Official Guide to Creating Games with Flash
Macromedia Flash MX Game Design Demystified: The Official Guide to Creating Games with Flash -- First 1st Printing -- CD Included
ISBN: B003HP4RW2
EAN: N/A
Year: 2005
Pages: 163
Authors: Jobe Makar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net