import DataStructures.*;
import Exceptions.*;
class TicTacToe
{
public static final int HUMAN = 0;
public static final int COMPUTER = 1;
public static final int EMPTY = 2;
public static final int HUMAN_WIN = 0;
public static final int DRAW = 1;
public static final int UNCLEAR = 2;
public static final int COMPUTER_WIN = 3;
// Constructor
public TicTacToe( )
{
clearBoard( );
}
public int [ ] [ ] getBoard( )
{
return board;
}
public Best chooseMove( int side )
{
return chooseMove( side, HUMAN_WIN, COMPUTER_WIN, 0 );
}
// Find optimal move
private Best chooseMove( int side, int alpha, int beta, int depth )
{
int opp; // The other side
Best reply; // Opponent's best reply
int dc; // Placeholder
int simpleEval; // Result of an immediate evaluation
Position thisPosition = new Position( board );
int tableDepth = 5; // Max depth placed in Trans. table
int bestRow = 0;
int bestColumn = 0;
int value;
if( ( simpleEval = positionValue( ) ) != UNCLEAR )
return new Best( simpleEval );
if( depth == 0 )
transpositions.makeEmpty( );
else if( depth >= 3 && depth <= tableDepth )
{
try
{
Position lookupVal = ( Position ) transpositions.find( thisPosition );
return new Best( lookupVal.value );
}
catch( ItemNotFound e ) { /* Not found; Must evaluate */ }
}
if( side == COMPUTER )
{
opp = HUMAN; value = alpha;
}
else
{
opp = COMPUTER; value = beta;
}
Outer:
for( int row = 0; row < 3; row++ )
for( int column = 0; column < 3; column++ )
if( squareIsEmpty( row, column ) )
{
place( row, column, side );
reply = chooseMove( opp, alpha, beta, depth + 1 );
place( row, column, EMPTY );
if( side == COMPUTER && reply.val > value ||
side == HUMAN && reply.val < value )
{
if( side == COMPUTER )
alpha = value = reply.val;
else
beta = value = reply.val;
bestRow = row; bestColumn = column;
if( alpha >= beta )
break Outer; // Refutation
}
}
thisPosition.value = value;
if( depth <= tableDepth )
transpositions.insert( thisPosition );
return new Best( value, bestRow, bestColumn );
}
// Play move, including checking legality
public boolean playMove( int side, int row, int column )
{
if( row < 0 || row >= 3 || column < 0 || column >= 3
|| board[ row ][ column ] != EMPTY )
return false;
board[ row ][ column ] = side;
return true;
}
// Simple supporting routines
public void clearBoard( )
{
for( int i = 0; i < 3; i++ )
for( int j = 0; j < 3; j++ )
board[ i ][ j ] = EMPTY;
}
public boolean boardIsFull( )
{
for( int row = 0; row < 3; row++ )
for( int column = 0; column < 3; column++ )
if( board[ row ][ column ] == EMPTY )
return false;
return true;
}
boolean isAWin( int side )
{
int row, column;
/* Look for all in a row */
for( row = 0; row < 3; row++ )
{
for( column = 0; column < 3; column++ )
if( board[ row ][ column ] != side )
break;
if( column >= 3 )
return true;
}
/* Look for all in a column */
for( column = 0; column < 3; column++ )
{
for( row = 0; row < 3; row++ )
if( board[ row ][ column ] != side )
break;
if( row >= 3 )
return true;
}
/* Look on diagonals */
if( board[ 1 ][ 1 ] == side && board[ 2 ][ 2 ] == side
&& board[ 0 ][ 0 ] == side )
return true;
if( board[ 0 ][ 2 ] == side && board[ 1 ][ 1 ] == side
&& board[ 2 ][ 0 ] == side )
return true;
return false;
}
private HashTable transpositions = new QuadraticProbingTable( );
private int [ ] [ ] board = new int[ 3 ][ 3 ];
// Play a move, possibly clearing a square
private void place( int row, int column, int piece )
{
board[ row ][ column ] = piece;
}
private boolean squareIsEmpty( int row, int column )
{
return board[ row ][ column ] == EMPTY;
}
// Compute static value of current position (win, draw, etc.)
private int positionValue( )
{
return isAWin( COMPUTER ) ? COMPUTER_WIN :
isAWin( HUMAN ) ? HUMAN_WIN :
boardIsFull( ) ? DRAW : UNCLEAR;
}
}