Six-Trix
Aqualines
CycleMan
Domino Dilemma
Rhumb Line
All Games...
Six-Trix on flash!
CycleMan
PacHoney Bear
CycleMan
Cram
SuperNim
Abracadabra
Javanoid
Basketball
Well known and Unique Games & Puzzles

 
Home Downloadable Games Free Online Flash Games Free Online Java Games Flash Lessons Portfolio Partners  
 
 
 
 
 
 

How to make a logical game on Flash. Part 4

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.

11.5. Limiting the Depth of Skimming through Moves

The program works good. I would say, it works better than it should. I suggest that you make the isWin function less intelligent.
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 there
Insert 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"
Note: if you wish to check for the correctness of skimming through moves, set maxdepth to 100 and edit the code of the orbtLevel.onRelease event handler appropriately. When the maxdepth value is greater than 12, the program should consider moves up to the final position and win if possible.
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.

11.6. Adding Some Heuristic

Let's add the heuristic rule formulated at the end of the previous section to the isWin function and see whether it can speed up playing for the computer's part (Listing 11.17).
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 == r2
is equivalent to
if (r0 == 0 && r1 > 0 && r1 == r2
In 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.

11.7. Continuation of the Theme

The program works quickly, and you can make it even quicker by replacing the arrays with scalar variables in the isWin function. Why don't you try to make it more interesting and add a fourth row to the game board? In addition, you can implement other game rules because there are many versions of Nim. For example, one version allows the gamers to remove one or two neighboring pieces from one row. It is called mathematical bowling.
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!

<<Prev.


Write to us Site Map
Copyright © 2003-2017 GameIntellect.com. All Rights Reserved.