This is the Chapter 11 from our book "The Flash 8 Game Developing Handbook", ISBN 1-931769-47-8. CD-ROM included. You can buy this book from us for $30 only.
var // the depth of the think clip thinkdepth=1, // the depth of the gameover clip gameoverdepth=1, // the depths of the youlose and youwin clips resultdepth=gameoverdepth+1, // the initial depth of the square clips squaredepth=resultdepth+1, // the number of the squares (and pieces) on the board maxpieces=12, // the coordinates of the upper left square clip x0=50, y0=125, // the size of a square in pixels sqx=70, // a variable indicating who makes the move // (0 - the gamer, 1 - the computer) mymove, // the current number of pieces numieces, // a variable setting a time delay waittime, // the board (3 groups containing 3, 4, and 5 pieces) board=new Array([0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]), // an array to store marks marked=new Array([0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]);I believe you understand the purpose of the variables from the comments. I'll describe them in more detail later.
//*** The main program *** // draw the board var sdepth=squaredepth; for (var i=0; i < 3; i++) for (var j=0; j < 5; j++) { if (j < i+3) { attachMovie('square','square'+j+i,sdepth); eval('square'+j+i)._x=x0+sqx*j; eval('square'+j+i)._y=y0+sqx*i; } ++sdepth; } newGame(); stop();This code needs some explanation. First, it draws 12 squares that will remain untouched during the game. There can be pieces on the squares, so you'll attach a piece to a square using the attachMovie function. In addition, there can be markers put by the gamer on pieces. So you have three clips two of which being attached to the master clip. The board and marked arrays have five elements in each line, but only the first three elements are used in the first line, and four used in the second. (Generally, ActionScript allows you to declare different dimensions of an array). To make the program simpler, let's assign depths to the squares as if there are five elements in each line. That is, the first square in the upper line has a depth of sdepth=squaredepth, the depth of its neighbor to the right is greater by one, the depth of the first square in the second line is sdepth+5, and that of the first square in the third line is sdepth+10. This is why the ++sdepth statement is located outside the conditional statement. You should name the squares so that it is easy to attach pieces to squares and markers to pieces in a loop. So the square in the ith line and jth column should be named 'square'+j+i. For example, the first square will have the square00 name, and the last will have the square42 name. This is done in the following statement:
attachMovie('square','square'+j+i,sdepth);The if (j < i+3) condition "cuts off" unused elements in the first two lines.
eval('square'+j+i)._x=x0+sqx*j; eval('square'+j+i)._y=y0+sqx*i;statements assign each newly created square coordinates in pixels computed from its logical coordinates j and i. The eval('square'+j+i) function returns a reference to the square object, given its name.
// displays "Thinking..." when arg != 0 // and hides when arg == 0 function showThinking(arg) { if (arg) { attachMovie('think','think',thinkdepth); think._x=185; think._y=12; } else think.removeMovieClip(); }Listing 11.4. The showGameOver function
// displays "Game over" when arg != 0 // and hides when arg == 0 function showGameOver(arg) { if (arg) { attachMovie('gameover','gameover',gameoverdepth); gameover._x=185; gameover._y=12; } else gameover.removeMovieClip(); }Listing 11.5. The showResult function
// when arg == 0, hides the "You lose" or "You win" text // when arg == 1, displays "You lose" // when arg == 2, displays "You win" function showResult(arg) { var names=new Array('','youlose','youwin'); if (!arg) { youlose.removeMovieClip(); youwin.removeMovieClip(); } else { attachMovie(names[arg],names[arg],resultdepth); var name=eval(names[arg]); name._x=12; name._y=12; } }Listing 11.6. The thinking function
// displays "Thinking..." and calls the computerMove function // after a delay of 1.500 ms function thinking() { if (getTimer() < waittime) return; showThinking(1); waittime=getTimer()+1500; gameact.onEnterFrame=computerMove; }This function is called before the computer starts thinking over its move. Delays before and after the appearance of the Thinking: text are necessary to avoid displaying the text and making a move almost simultaneously.
// this is called after the gamer releases the mouse button // on the "Comp begins" button function youBegin() { if (numpieces == maxpieces) // If all pieces are on the board, makes a random move anyMove(); }If the computer makes the first move, the function checks whether all pieces are on the board. When this is the case, it calls the anyMove function (Listing 11.8) to make a random move. If the program computed all variants before the first move, it would always win (at least in this position and with these rules), so the gamer would see the You lose message instantly. Our program is friendly, and it warns the gamer who will inevitably lose that he or she can start a new game.
// making a random move function anyMove() { var i,j,k,rows=new Array(0,0,0); // assigning the rows array the number of pieces in three rows for (i=0; i < 3; i++) for (j=0; j < i+3; j++) rows[i]+=board[i][j]; // finding the row with the maximum number of pieces i=2; if (rows[1] > rows[2]) i=1; if (rows[0] > rows[1]) i=0; // removing k pieces from the ith row k=2; if (rows[i] < 3 || Math.random() < 0.67) k=1; for (j=0; j < i+3; j++) if (board[i][j]) { showPiece(0,j,i); if (!--k) break; } // the gamer's move mymove=0; // hide "Thinking..." showThinking(0); }You should be careful when making a random move. It is desirable to avoid positions which the gamer can easily analyze. Therefore, the computer should delete few pieces, for example, one or two, and shouldn't leave empty rows after its move. Later, when you learn how to skim through variants, you'll know another method for making a "good" random move. In this function, you declare an array of three elements and assign them the numbers of pieces in each row. Then you assign the i variable the number of the row with the maximum number of pieces. Then you call the Math.random method of the Math object to decide whether one or two pieces should be removed. The computer removes one piece when the ith row contains less than three pieces or when the Math.random method returns a number less than 0.67 (the probability of which is 2/3). Then a loop iterates through all the pieces in the ith row:
for (j=0; j < i+3; j++) if (board[i][j]) { showPiece(0,j,i); if (!--k) break; }The loop finds a square with a piece (board[i][j]) and removes the piece by calling the showPiece function (Listing 11.9). Then it subtracts one from the k variable and terminates when k becomes equal to zero.
// shows the piece with the (x, y) coordinates when arg != 0 // and hides the pieces when arg == 0 function showPiece(arg,x,y) { var // the reference to the square containing the piece name=eval('square'+x+y); if (arg) { // attaching the piece to the square (the piece depth is 1) name.attachMovie('piece','piece',1); // computing a reference to the piece clip from the piece name name=eval('name.piece'); // placing the piece in the center of the square name._x=0; name._y=0; // store the coordinates of the piece in its properties j and i // to compute the coordinates when the gamer clicks on the piece name.j=x; name.i=y; // each piece will pass its coordinates from the j and i // properties to the rel function name.onRelease=function() { rel(this.j,this.i); } // creating a piece logically removing a piece from the board board[y][x]=1; } else { // the piece gradually disappears --numpieces; board[y][x]=marked[y][x]=0; // The piece disappears gradually eval('name.piece').play(); } }The following fragment of the showPiece function needs some explanation:
name.j=x; name.i=y; // each piece will pass its coordinates from the j and i // properties to the rel function name.onRelease=function() { rel(this.j,this.i); }When you create an instance of the piece clip, you add the j and i properties to it. Thanks to this, each instance of the piece clip stores its board coordinates (j is the column number, and i is the row number, numbering begins from zero). In addition, a handler of the mouse release event is created for each piece instance. As a result, when a gamer releases the mouse button on a piece, it passes the rel function its coordinates to inform the main program which piece is clicked. You could implement computation of coordinates in another way by finding the pixel coordinates of the mouse pointer, when the user releases the mouse button, and converting these to the logical coordinates of a square of the board. However, you would additionally have to check whether there is a piece on the square. The solution where the piece tells its coordinates on its own is more convenient.
// preparing for a new game function newGame() { var i,j; for (var i=0; i < 3; i++) for (var j=0; j < 5; j++) if (j < i+3) showPiece(1,j,i); // setting the current number of pieces to the maximum number numpieces=maxpieces; // the gamer can make a move mymove=0; // hiding the clip with the result showResult(0); // hiding the gameover clip showGameOver(0); }The function in Listing 11.10 puts the pieces by calling the showPiece function, hides any messages, and allows the gamer to make the first move.
// This function is called when the gamer releases the mouse button // on any piece. It takes the coordinates of the piece: x and y. function rel(x,y) { // If this is the computer's move, return if (mymove) return; // Is the piece in the x column and the y row marked? if (marked[y][x]) { // Removing the mark clip from the piece eval('square'+x+y+'.piece.mark').removeMovieClip(); // Removing the mark logically marked[y][x]=0; return; } // The gamer clicked on an unmarked piece. Checking for marked // pieces in the other rows. for (var i=0; i < 3; i++) { // Skipping the current row if (i == y) continue; for (var j=0; j < i+3; j++) // A mark piece is found in another row. The attempt // of an illegal move is ignored. if (marked[i][j]) return; } // marking the piece and // finding a reference to the required clip from its name var name=eval('square'+x+y+'.piece'); // attaching the mark clip to the piece (whose depth is 1) name.attachMovie('mark','mark',1); // getting a reference to the mark clip instance name=eval('name.mark'); // putting it in the center of the piece name._x=0; name._y=0; // marking the piece logically marked[y][x]=1; }