A Java Framework for Search and Game Playing
program short and easy to understand. The method getM ove returns an object of a class derived from the class M ove e.g., T icT acT oeM ove or ChessM ove.
The GameSearch class implements the following methods to perform game search: protected Vector alphaBetaint depth, Position p,
boolean player protected Vector alphaBetaHelperint depth,
Position p, boolean player,
float alpha, float beta
public void playGamePosition startingPosition, boolean humanPlayFirst
The method alphaBeta is simple; it calls the helper method alphaBetaHelper with initial search conditions; the method alphaBetaHelper then calls itself recur-
sively. The code for alphaBeta is:
protected Vector alphaBetaint depth, Position p,
boolean player {
Vector v = alphaBetaHelperdepth, p, player, 1000000.0f,
-1000000.0f; return v;
} It is important to understand what is in the vector returned by the methods alphaBeta
and alphaBetaHelper. The first element is a floating point position evaluation for the point of view of the player whose turn it is to move; the remaining values are the
“best move” for each side to the last search depth. As an example, if I let the tic-tac- toe program play first, it places a marker at square index 0, then I place my marker
in the center of the board an index 4. At this point, to calculate the next computer move, alphaBeta is called and returns the following elements in a vector:
next element: 0.0 next element: [-1,0,0,0,1,0,0,0,0,]
next element: [-1,1,0,0,1,0,0,0,0,] next element: [-1,1,0,0,1,0,0,-1,0,]
next element: [-1,1,0,1,1,0,0,-1,0,] next element: [-1,1,0,1,1,-1,0,-1,0,]
next element: [-1,1,1,1,1,-1,0,-1,0,]
25
next element: [-1,1,1,1,1,-1,-1,-1,0,] next element: [-1,1,1,1,1,-1,-1,-1,1,]
Here, the alpha-beta enhanced min-max search looked all the way to the end of the game and these board positions represent what the search procedure calculated as
the best moves for each side. Note that the class T icT acT oeP osition derived from the abstract class P osition has a toString method to print the board values to
a string.
The same printout of the returned vector from alphaBeta for the chess program is:
next element: 5.4 next element:
[4,2,3,5,9,3,2,4,7,7,1,1,1,0,1,1,1,1,7,7, 0,0,0,0,0,0,0,0,7,7,0,0,0,1,0,0,0,0,7,7,
0,0,0,0,0,0,0,0,7,7,0,0,0,0,-1,0,0,0,7,7, -1,-1,-1,-1,0,-1,-1,-1,7,7,-4,-2,-3,-5,-9,
-3,-2,-4,]
next element: [4,2,3,0,9,3,2,4,7,7,1,1,1,5,1,1,1,1,7,7,
0,0,0,0,0,0,0,0,7,7,0,0,0,1,0,0,0,0,7,7, 0,0,0,0,0,0,0,0,7,7,0,0,0,0,-1,0,0,0,7,7,
-1,-1,-1,-1,0,-1,-1,-1,7,7,-4,-2,-3,-5,-9, -3,-2,-4,]
next element: [4,2,3,0,9,3,2,4,7,7,1,1,1,5,1,1,1,1,7,7,
0,0,0,0,0,0,0,0,7,7,0,0,0,1,0,0,0,0,7,7, 0,0,0,0,0,0,0,0,7,7,0,0,0,0,-1,-5,0,0,7,7,
-1,-1,-1,-1,0,-1,-1,-1,7,7,-4,-2,-3,0,-9, -3,-2,-4,]
next element: [4,2,3,0,9,3,0,4,7,7,1,1,1,5,1,1,1,1,7,7,
0,0,0,0,0,2,0,0,7,7,0,0,0,1,0,0,0,0,7,7, 0,0,0,,0,0,0,0,0,7,7,0,0,0,0,-1,-5,0,0,7,7,
-1,-1,-1,-1,0,-1,-1,-1,7,7,-4,-2,-3,0,-9, -3,-2,-4,]
next element: [4,2,3,0,9,3,0,4,7,7,1,1,1,5,1,1,1,1,7,7,
0,0,0,0,0,2,0,0,7,7,0,0,0,1,0,0,0,0,7,7, -1,0,0,0,0,0,0,0,7,7,0,0,0,0,-1,-5,0,0,7,7,
0,-1,-1,-1,0,-1,-1,-1,7,7,-4,-2,-3,0,-9, -3,-2,-4,]
26
Here, the search procedure assigned the side to move the computer a position evaluation score of 5.4; this is an artifact of searching to a fixed depth. Notice that
the board representation is different for chess, but because the GameSearch class manipulates objects derived from the classes P osition and M ove, the GameSearch
class does not need to have any knowledge of the rules for a specific game. We will discuss the format of the chess position class ChessP osition in more detail when
we develop the chess program.
The classes Move and Position contain no data and methods at all. The classes Move and Position are used as placeholders for derived classes for specific games.
The search methods in the abstract GameSearch class manipulate objects derived from the classes Move and Position.
Now that we have seen the debug printout of the contents of the vector returned from the methods alphaBeta and alphaBetaHelper, it will be easier to understand how
the method alphaBetaHelper works. The following text shows code fragments from the alphaBetaHelper method interspersed with book text:
protected Vector alphaBetaHelperint depth, Position p,
boolean player, float alpha,
float beta {
Here, we notice that the method signature is the same as for alphaBeta, except that we pass floating point alpha and beta values. The important point in understanding
min-max search is that most of the evaluation work is done while “backing up” the search tree; that is, the search proceeds to a leaf node a node is a leaf if the
method reachedM axDepth return a Boolean true value, and then a return vector for the leaf node is created by making a new vector and setting its first element to the
position evaluation of the position at the leaf node and setting the second element of the return vector to the board position at the leaf node:
if reachedMaxDepthp, depth { Vector v = new Vector2;
float value = positionEvaluationp, player; v.addElementnew Floatvalue;
v.addElementp; return v;
} If we have not reached the maximum search depth i.e., we are not yet at a leaf node
in the search tree, then we enumerate all possible moves from the current position using the method possibleM oves and recursively call alphaBetaHelper for each
27
new generated board position. In terms of Figure 2.8, at this point we are moving down to another search level e.g., from level 1 to level 2; the level in Figure 2.8
corresponds to depth argument in alphaBetaHelper:
Vector best = new Vector; Position [] moves = possibleMovesp, player;
for int i=0; imoves.length; i++ { Vector v2 = alphaBetaHelperdepth + 1, moves[i],
player, -beta, -alpha;
float value = -Floatv2.elementAt0.floatValue; if value beta {
ifGameSearch.DEBUG System.out.println value=+
value+ ,beta=+beta;
beta = value; best = new Vector;
best.addElementmoves[i]; Enumeration enum = v2.elements;
enum.nextElement; skip previous value while enum.hasMoreElements {
Object o = enum.nextElement; if o = null best.addElemento;
} }
Use the alpha-beta cutoff test to abort search if we found a move that proves that
the previous move in the move chain was dubious
if beta = alpha { break;
} }
Notice that when we recursively call alphaBetaHelper, we are “flipping” the player argument to the opposite Boolean value. After calculating the best move
at this depth or level, we add it to the end of the return vector:
Vector v3 = new Vector; v3.addElementnew Floatbeta;
Enumeration enum = best.elements;
28
while enum.hasMoreElements { v3.addElementenum.nextElement;
} return v3;
When the recursive calls back up and the first call to alphaBetaHelper returns a vector to the method alphaBeta, all of the “best” moves for each side are stored
in the return vector, along with the evaluation of the board position for the side to move.
The class GameSearch method playGame is fairly simple; the following code fragment is a partial listing of playGame showing how to call alphaBeta, getM ove,
and makeM ove:
public void playGamePosition startingPosition, boolean humanPlayFirst {
System.out.printlnYour move:; Move move = getMove;
startingPosition = makeMovestartingPosition, HUMAN, move;
printPositionstartingPosition; Vector v = alphaBeta0, startingPosition, PROGRAM;
startingPosition = Positionv.elementAt1; }
} The debug printout of the vector returned from the method alphaBeta seen earlier
in this section was printed using the following code immediately after the call to the method alphaBeta:
Enumeration enum = v.elements; while enum.hasMoreElements {
System.out.println next element: + enum.nextElement;
} In the next few sections, we will implement a tic-tac-toe program and a chess-
playing program using this Java class framework.