Following is a possible implementation of Tic-Tac-Toe game class that
executes Tic-Tac-Toe games between instances of specified player
strategy classes.  When run from the command line, the first two
arguments specify the classes for the X and O players' strategies,
respectively, and the third argument the number of games to run.  When
doen with all the reptitions, the tally of wins for X, wins for O, and
draws are printed.

Three sample player strategy implementations are provided:
RandomPlayer simply chooses moves at random, PlayerWithClue makes
certain moves if obvious but can be beaten, and HumanPlayer provides a
command line interface for the user to play a side (useful for testing
strategies).  Following are some results playing these strategies
against each other:

[terrier] 757 % java Game RandomPlayer RandomPlayer 1000
After 1000 games,
player X (RandomPlayer) won 587 times,
player O (RandomPlayer) won 293 times,
and there were 120 draws.
[terrier] 758 % java Game PlayerWithClue RandomPlayer 1000
After 1000 games,
player X (PlayerWithClue) won 891 times,
player O (RandomPlayer) won 13 times,
and there were 96 draws.
[terrier] 759 % java Game RandomPlayer PlayerWithClue 1000
After 1000 games,
player X (RandomPlayer) won 64 times,
player O (PlayerWithClue) won 687 times,
and there were 249 draws.
[terrier] 760 % java Game PlayerWithClue PlayerWithClue 1000
After 1000 games,
player X (PlayerWithClue) won 350 times,
player O (PlayerWithClue) won 155 times,
and there were 495 draws.
[terrier] 761 % 

Everything else being equal, X (moving first) clearly has an advantage,
but the PlayerWithClue's simple strategy is clearly far superior than
that of the RandomPlayer.



/**
 * Game manages a game of Tic-Tac-Toe between two supplied player objects.
 */
public class Game {

    // constant values for game state
    public static final int PLAYING = 0;
    public static final int X_WINS  = 1;
    public static final int O_WINS  = 2;
    public static final int DRAW    = 3;

    private Board board = new Board();
    private Player xPlayer;
    private Player oPlayer;

    /** Start new game between with given players for X and O */
    public Game(Player x, Player o) {
	this.xPlayer = x;
	this.oPlayer = o;
	xPlayer.newGame(Board.X);
	oPlayer.newGame(Board.O);
    }

    /** Execute this game and return the outcome */
    public int play() throws IllegalPlayerException {
	int move, state;
	while (true) {
	    // X's turn
	    move = xPlayer.getMove((Board)board.clone());
	    if (board.sq[move] != Board.EMPTY)
		throw new IllegalPlayerException("illegal move by X",
		    xPlayer, (Board)board.clone(), move);
	    board.sq[move] = Board.X;
	    state = board.checkState();
	    if (state != PLAYING)
		break;

	    // O's turn
	    move = oPlayer.getMove((Board)board.clone());
	    if (board.sq[move] != Board.EMPTY)
		throw new IllegalPlayerException("illegal move by O",
		    oPlayer, (Board)board.clone(), move);
	    board.sq[move] = Board.O;
	    state = board.checkState();
	    if (state != PLAYING)
		break;
	}
	xPlayer.gameOver(state, (Board)board.clone());
	oPlayer.gameOver(state, (Board)board.clone());
	return state;
    }

    /**
     * Play Tic-Tac-Toe between two players of the supplied class names
     * for the given number of repititions and print out the total results.
     */
    public static void main(String[] args) {
	if (args.length != 3)
	    error("usage: Game player_X_class player_O_class reps");

	try {
	    int reps = Integer.parseInt(args[2]);

	    // load player classes and create instances
	    PlayerLoader loader = new PlayerLoader();
	    Class XClass = loader.loadClass(args[0], true);
	    Player XPlayer = (Player)XClass.newInstance();
	    Class OClass = loader.loadClass(args[1], true);
	    Player OPlayer = (Player)OClass.newInstance();

	    int xWins = 0;
	    int oWins = 0;
	    int draws = 0;

	    // play the games
	    for (int i = 0; i < reps; i++) {
		Game game = new Game(XPlayer, OPlayer);
		int result = game.play();
		switch (result) {
		  case X_WINS:
		    xWins++;
		    break;

		  case O_WINS:
		    oWins++;
		    break;

		  case DRAW:
		    draws++;
		    break;

		  default:
		    throw new InternalError("Invalid game result: " + result);
		}
	    }

	    // print out the total results
	    System.out.println("After " +
		reps + " game" + (reps == 1 ? "" : "s") + ",");
	    System.out.println("player X (" + XClass.getName() + ") won " +
		xWins +	" time" + (xWins == 1 ? "" : "s") + ",");
	    System.out.println("player O (" + OClass.getName() + ") won " +
		oWins +	" time" + (oWins == 1 ? "" : "s") + ",");
	    System.out.println("and there were " +
		draws + " draw" + (draws == 1 ? "" : "s") + ".");

	} catch (Exception e) {
	    error("Exception: " + e);
	}
    }

    static void error(String err) {
	System.err.println("Game: " + err);
	System.exit(1); // non-zero argument means "not good"
    }
}



/**
 * Board contains a public array representing a Tic-Tac-Toe board and
 * constants defining the meaning of the values in the array.
 *
 * Board also provides a few utility methods for performing potentially
 * useful analyses of the contents of the board.
 */
public class Board implements Cloneable {

    // constant values for board square contents
    public static final int EMPTY = 0;
    public static final int X     = 1;
    public static final int O     = 2;

    public Object clone() {	// never throws CloneNotSupportedException
	try {
	    Board nObj = (Board)super.clone();
	    return nObj;
	} catch (CloneNotSupportedException e) {
	    // can't happen
	    throw new InternalError(e.toString());
	}
    }

    /** board's actual contents: an array of nine integers */
    public int[] sq = new int[9];

    /** Return the player identifier that isn't the one supplied */
    public static int notPlayer(int who) {
	return 3 - who;
    }

    // all combinations of three squares which comprise a three-in-a-row
    private static int[][] winningRows = {
	{0, 1, 2}, {3, 4, 5}, {6, 7, 8},
	{0, 3, 6}, {1, 4, 7}, {2, 5, 8},
	{0, 4, 8}, {2, 4, 6}
    };

    /** Return an array of RowStats structures for each
      * potentially winning row. */
    public RowStats[] analyzeRows() {
	RowStats[] stats = new RowStats[winningRows.length];
	for (int i = 0; i < stats.length; i++)
	    stats[i] = new RowStats(this, (int[])winningRows[i].clone());
	return stats;
    }

    /** Return the state of a Tic-Tac-Toe game implied by the contents
      * of this board, using the constants defined in the Game class */
    public int checkState() {
	boolean draw = true;
	RowStats[] stats = analyzeRows();
	for (int i = 0; i < stats.length; i++) {
	    if (stats[i].xCount == stats[i].row.length)
		return Game.X_WINS;
	    if (stats[i].oCount == stats[i].row.length)
		return Game.O_WINS;
	    if (stats[i].xCount == 0 || stats[i].oCount == 0)
		draw = false;
	}
	if (draw)
	    return Game.DRAW;
	return Game.PLAYING;
    }

    public int emptySquare(int[] row) {
	for (int i = 0; i < row.length; i++)
	    if (sq[row[i]] == EMPTY)
		return row[i];
	throw new InternalError("emptySquare: no emptySquare");
    }

    /** Return string representation of board content */
    public static String nameOf(int who, String def) {
	if (who == X)
	    return "X";
	if (who == O)
	    return "O";
	else
	    return def;
    }

    /** Print ASCII representation of board to given PrintStream */
    public void print(java.io.PrintStream out) {
	out.println(nameOf(sq[0], "1") +
	      "|" + nameOf(sq[1], "2") +
	      "|" + nameOf(sq[2], "3"));
	out.println("-+-+-");
	out.println(nameOf(sq[3], "4") +
	      "|" + nameOf(sq[4], "5") +
	      "|" + nameOf(sq[5], "6"));
	out.println("-+-+-");
	out.println(nameOf(sq[6], "7") +
	      "|" + nameOf(sq[7], "8") +
	      "|" + nameOf(sq[8], "9"));
    }
}



/**
 * RowStats hold information about a particular Tic-Tac-Toe row from a
 * particular board state.  A "row" is an integer array containing the
 * indexes of sqaures on the Tic-Tac-Toe board (usually a potentially
 * winning combination).  Other public fields contain statistics for
 * that row on the board.
 */
public class RowStats {

    public int[] row;		// the row in consideration
    public int xCount = 0;	// how many Xs were in the row
    public int oCount = 0;	// how many Os were in the row

    /** Calculate row statistics from a given board */
    public RowStats(Board board, int[] row) {
	this.row = row;
	for (int i = 0; i < row.length; i++) {
	    if (board.sq[row[i]] == Board.X)
		xCount++;
	    if (board.sq[row[i]] == Board.O)
		oCount++;
	}
    }

    public int getCount(int who) {	
	if (who == Board.X)
	    return xCount;
	if (who == Board.O)
	    return oCount;
	throw new InternalError("RowStats.getCount: who == " + who);
    }
}



/**
 * Player is the interface implemented by objects that can play Tic-Tac-Toe.
 */
public interface Player {

    /** Prepare to play new game playing given side (X or O) */
    void newGame(int who);

    /** Return chosen move in current game and given board */
    int getMove(Board board);

    /** Inform player outcome of game */
    void gameOver(int result, Board board);
}



/**
 * RandomPlayer implements a Tic-Tac-Toe strategy of choosing
 * randomly selected squares.
 */
public class RandomPlayer implements Player {

    private java.util.Random random = new java.util.Random();

    public void newGame(int who) {
	// no pregame preparation necessary for random player
    }

    public int getMove(Board board) {
	int move;
	// keep choosing squares at random until an empty on is found
	do {
	    move = (Math.abs(random.nextInt()) % 9);
	} while (board.sq[move] != Board.EMPTY);
	return move;
    }

    public void gameOver(int result, Board board) {
	// random player doesn't care who won
    }
}



/**
 * PlayerWithClue implements a Tic-Tac-Toe strategy with a little more
 * knowledge of how to win the game than RandomPlayer does.  When choosing
 * a given move, if it sees a move that will win the game, it will choose it.
 * Otherwise, if it sees a move that will prevent the opponent from winning
 * in the next turn, then it will choose that move.  Otherwise, a random
 * empty square is chosen.
 */
public class PlayerWithClue implements Player {

    private int me;
    private int notme;

    private java.util.Random random = new java.util.Random();

    public void newGame(int who) {
	me = who;
	notme = Board.notPlayer(who);
    }

    public int getMove(Board board) {
	RowStats[] stats = board.analyzeRows();

	// win, if possible
	for (int i = 0; i < stats.length; i++)
	    if (stats[i].getCount(me) == 2 && stats[i].getCount(notme) == 0)
		return board.emptySquare(stats[i].row);

	// prevent losing, if possible
	for (int i = 0; i < stats.length; i++)
	    if (stats[i].getCount(notme) == 2 && stats[i].getCount(me) == 0)
		return board.emptySquare(stats[i].row);

	int move;
	// else, keep choosing squares at random until an empty on is found
	do {
	    move = (Math.abs(random.nextInt()) % 9);
	} while (board.sq[move] != 0);
	return move;
    }

    public void gameOver(int result, Board board) {
    }
}



/**
 * HumanPlayer is a Tic-Tac-Toe player implementation that interacts with the
 * user on System.in/System.out to query for moves.
 */
import java.io.*;

public class HumanPlayer implements Player {

    private int me;

    public void newGame(int who) {
	me = who;
    }

    public int getMove(Board board) {
	board.print(System.out);
	DataInputStream in = new DataInputStream(System.in);
	do {
	    System.out.print(Board.nameOf(me, "?") + "'s move: ");
	    System.out.flush();
	    String line = "";
	    try {
		line = in.readLine().trim();
	    } catch (IOException e) {
		System.err.println("I/O Exception reading input: " + e);
		System.exit(1);
	    }
	    try {
		int move = Integer.parseInt(line);
		if (move < 1 || move > 9) {
		    System.out.println(
			"Invalid square number: must be from 1 to 9.");
		} else if (board.sq[move - 1] != 0) {
		    System.out.println("Square " + move +
			" is occupied: try again.");
		} else {
		    return move - 1;
		}
	    } catch (NumberFormatException e) {
		System.out.println("Invalid number: try again.");
	    }
	} while (true);
    }

    public void gameOver(int result, Board board) {
	board.print(System.out);
	System.out.print("Game over: ");
	if (result == Game.X_WINS)
	    System.out.println("X won");
	else if (result == Game.O_WINS)
	    System.out.println("O won");
	else
	    System.out.println("Draw game");
    }
}
