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.

First, create some additional graphics. This will be the btLevel button to select the game level (1 or 2) and the Level text to the left of the button. Begin with the text. Enter the third frame of the main timeline and create a text field using the Text tool. The text properties should be as follows: Static Text, Tahoma, 30 pts, bold, #0066FF, left-justified. The coordinates of the upper left corner should be X=258 and Y=327. Type in the Level word.

As for the button, create a new clip, name it btLevel, and check the Export for ActionScript checkbox. Create a layer above Layer 1 and name it Layer 2. In the first frame in Layer 1, draw a rectangle with rounded corners (the corner radius should be 5 pixels). Select the #3399FF color and a thickness of 2 pixels for the outline and the #FFFFC4 color for the fill. Align the rectangle to the center of the work area. The timeline of this button clip will have four frames: levels one and two, each with highlight and without it. Enter the fourth frame of Layer 1 and press <F5> to create a frame and make the object live up to that frame.

In the first frame of Layer 2, create a text field with the same properties as the field you have created earlier (except that this text should be centered). Type in 1. Align this to the center of the work area, copy everything to the second frame and change the digit color to #FF6600. Create a blue two in the third frame and a red two in the fourth frame in a similar manner. Attach the following action to all these frames:

stop();Let's proceed with programming. In the data section of the main program, replace the semicolon to a comma:

// an array to store marks marked=new Array([0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]),and add a few variables:

// the game level (1 or 2) level=1, // the maximum depth of skimming through the moves (depends on the level) maxdepth=6, // a copy of maxdepth for the isWin function workdepth=0;Here the level variable contains what the btLevel button reads: one or two. The maxdepth variable is the maximum depth of skimming through the moves that is computed from the maxdepth=level+5 formula. I found this formula through experimentation. Zero will be assigned to the workdepth variable before a call to the isWin function so that the function can determine to which depth it has dug.

Besides zero and one, the isWin function will return a third value, two. It will indicate that isWin didn't compute till the final position and cannot estimate the given position. At the beginning of the isWin function, you'll increase the workdepth variable by one and compare the result with the maxdepth variable. If workdepth is greater than maxdepth, the function will exit and return two. Before the function exits, it should restore the value of the workdepth variable passed to it, that is, it should subtract one from the variable. As a result, workdepth will store the current depth or the number of the moves made (after the rows1[i]=j statement is executed) in every virtual instance of the isWin function. Outside the function, the workdepth variable is always equal to zero.

Before proceeding to more complicated things, insert the code for the btLevel button (Listing 11.15) immediately after the following line:

Listing 11.15. The code for the btLevel button

//*** The main program *** // placing the btLevel button clip attachMovie('btLevel','btLevel',leveldepth); btLevel._x=360; btLevel._y=348; // defining the onRollOver event handler btLevel.onRollOver=function() { // going to the frame with a highlighted digit btLevel.gotoAndStop(btLevel._currentframe+1); } // defining the onRollOut event handler btLevel.onRollOut=function() { // returning to the frame where the digit isn't highlighted btLevel.gotoAndStop(btLevel._currentframe-1); } // defining the onRelease event handler btLevel.onRelease=function() { // the level can be changed only at the beginning of the game if (numpieces == maxpieces) { // toggling the level between 1 and 2 level=3-level; // adjusting the maximum depth maxdepth=level+5; // going to the frame where the digit isn't highlighted btLevel.gotoAndStop(level*2); } } // defining the onReleaseOutside event handler btLevel.onReleaseOutside=function() { // returning to the frame where the digit isn't highlighted btLevel.gotoAndStop(btLevel._currentframe-1); }At the beginning of the program, insert the following lines immediately after the var line:

// The depth of the btLevel clip leveldepth=0,The code for the btLevel clip is simple: you wrote similar code earlier. Your task now is to display the game level and highlight an appropriate digit depending on the gamer's actions. In addition, you should toggle the value of the level variable between one and two.

In the computerMove function, locate the following lines:

// Skimming through the computer moves in the current position // starting from the lowest row because there can be more pieces thereInsert these lines before the lines you located:

// setting the depth of skimming through moves workdepth=0;You don't need to reset the workdepth variable to zero every time you call isWin. As you already know, this variable is always equal to zero outside the isWin function.

Listing 11.16 presents an updated version of the isWin function where the depth of skimming through variants is limited.

Listing 11.16. An updated version of the isWin function

// returns an estimate of the move that led to the current position // 0 - the move was losing // 1 - the move was winning // 2 - unknown function isWin(rows) { if (++workdepth > maxdepth) { --workdepth; return 2; } var i,j,res,res2, rows1=new Array(rows[0],rows[1],rows[2]); for (i=2; i >= 0; i--) { // skimming over empty rows if (!rows1[i]) continue; // Making the opponent's moves in the ith row for (j=0; j < rows[i]; j++) { // A move: leaving j pieces in the ith row rows1[i]=j; // estimating the move res=isWin(rows1); // If the opponent's move is winning, the move that led to the // current position, was losing if (res == 1) { --workdepth; return 0; } if (res == 2) res2=2; } // restoring the number of pieces in the ith row rows1[i]=rows[i]; } --workdepth; if (res2 == 2) return 2; // If the opponent's all moves are losing, // the move that led to the current position, was winning return 1; }This version of the isWin function differs from the previous in the conditional statement discussed earlier and in the

--workdepth;statement placed before the return. In addition, the "2" estimate indicating that the result is unknown is implemented. This project is located in the nim1.fla and nim1.as files. Make sure to update the code attached to the third frame of the movie:

#include "nim1.as"

You might be eager to start the program and play with it. Forward! Surprised? ActionScript is too slow, and 15 seconds aren't enough for it to think over a move. What a pity! Is Flash too silly to play intellectual games?

You, a programmer, should help it. If you analyze the game closely, you'll notice that it is very easy to tell whether a position is winning when only two rows of pieces are left. If there are equal numbers of pieces in the rows, the next move is losing. When the numbers of pieces in the rows are different, the next move is winning if it makes the numbers equal. This rule is used in many games and is called a symmetric winning strategy. In other words, a move that destroys symmetry is losing.

Listing 11.17. A version of the isWin function with heuristic

// returns an estimate of the move that led to the current position // 0 - the move was losing // 1 - the move was winning // 2 - unknown function isWin(rows) { if (++workdepth > maxdepth) { --workdepth; return 2; } var i,j,res,res2,r0,r1,r2, rows1=new Array(rows[0],rows[1],rows[2]); // assigning the numbers of pieces to scalar variables to speed up // the program's work r0=rows[0]; r1=rows[1]; r2=rows[2]; // Checking whether exactly one row is empty. // In this case, if the other rows contain equal numbers of pieces // the previous move was winning if (!r0 && r1 && r1 == r2 || !r1 && r0 && r0 == r2 || !r2 && r0 && r0 == r1) { --workdepth; return 1; } for (i=2; i >= 0; i--) { // skimming over empty rows if (!rows1[i]) continue; // Making the opponent's moves in the ith row for (j=0; j < rows[i]; j++) { // A move: leaving j pieces in the ith row rows1[i]=j; // estimating the move res=isWin(rows1); // If the opponent's move is winning, the move that led to the // current position, was losing if (res == 1) { --workdepth; return 0; } if (res == 2) res2=2; } // restoring the number of pieces in the ith row rows1[i]=rows[i]; } --workdepth; if (res2 == 2) return 2; // If the opponent's all moves are losing, // the move that led to the current position, was winning return 1; }Before skimming through the moves, this isWin version checks whether there is exactly one empty row. If there is, it checks whether the other rows contain equal numbers of pieces. If the second condition is also true, the function decreases workdepth by one and returns one.

{ --workdepth; return 1; }This indicates that the move which led to the position was winning.

If you failed to understand the conditional statement that combines these conditions, I'll explain it using an example.

if (!r0 && r1 && r1 == r2is equivalent to

if (r0 == 0 && r1 > 0 && r1 == r2In other words, if the top row is empty, and the middle row isn't, and the numbers of pieces in the middle and bottom rows are equal, the function should return one immediately.

Start the game. Eureka! (Perhaps, this is what Archimedos cried running naked through Syracuse with the golden crown in his hands). The program now plays very quickly and the isWin function always wins even at the first game level, as if there is no limit to the skimming depth. Well, you should adjust the program's mental ability by decreasing the maxdepth value. I found a good value (two) through experimentation. Change

maxdepth=6,to

maxdepth=2,in the data section.

In the onRelease event handler of the btLevel clip, change

maxdepth=level+5;to

maxdepth=level+1;Now the program doesn't think too deep. The heuristic rule is quite nice.

You should now adjust the anyMove function so that it doesn't leave positions that can be won easily using the heuristic rule.

To make moves more varied, implement the following idea: Select a random row and a random number of pieces to remove. If at least one piece will be left in this row after this move, make this move. Otherwise try another random move. To avoid an infinite search, make no more than 50 attempts. If a desired move isn't found after 50 attempts, break the loop and remove one piece from the row with the maximum number of pieces.

Listing 11.18 contains a new version of the anyMove function. The comments explain it completely.

Listing 11.18. The new version of the anyMove function

// Making a random move function anyMove() { var i,j,k, n=50, 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]; // making n attempts to remove k pieces from the ith row do { // assigning i a random number, 0, 1, or 2 i=Math.floor(Math.random()*3); // if there are less than two pieces in the row, try another if (rows[i] < 2) continue; // assigning k a random number, 1, 2, or 3 k=Math.floor(Math.random()*3)+1; // if there are 3 pieces in the ith row, and k is equal to 3: if (rows[i] == 3 && k == 3) // : assign k a random number, 1 or 2 k=Math.floor(Math.random()*2)+1; // if there are 2 pieces in the ith row, assign k one if (rows[i] == 2) k=1; // removing k pieces from the ith row rows[i]-=k; // if there are rows with equal to numbers of pieces: if (rows[0] == rows[1] || rows[0] == rows[2] || rows[1] == rows[2]) // : undo the move { rows[i]+=k; k=0; } // after the move is made, break the loop if (k) break; } while (--n); // if no move is made, make a simple move if (!k) { // 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 one piece from the ith row rows[i]-=1; k=1; } // removing k pieces from the ith row 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..." text showThinking(0); }With this version of the anyMove function, the program makes varied moves, and it is interesting to play with it. However, the game is still for children. This version of the project is in the nim2.fla and nim2.as files.

The projects that implement Nim also have a blue copyright panel and a button with a link to www.gameintellect.com. They are necessary if an owner of the book tries to use this game, for example, on his or her site. It is prohibited to sell, rent, or exchange the game, to distribute its source code, or to derive a profit from the game in any other way! The source code is intended only for training purposes.

If you fail to write a new isWin function, ask me for help. In any case, a Delphi (or C++) shell will allow you to write a powerful game because processor code is quicker than ActionScript. This completes my book. I hope it was useful for you. Game over!

Copyright © 2003-2021 GameIntellect.com. All Rights Reserved.