TeekoTeacher V1.0 by Mihailo Despotovic, June 2003

Advise:

TeekoTeacher, Version by Mihailo Despotovic 2003.

Developed for the CAPS4 Conference paper “TeekoTeacher: A Tool for Learning Good Teeko Strategies”, The Fourth International Conference on Human-System Learning, Glasgow, Scotland, UK, 2003.

TeekoTeacher Instructions

1. Rules of Teeko

The game is played on a five by five chess-like board. Each player has four equal pieces.

The first four moves define the opening phase. During this phase, each player alternatively puts one piece on an unoccupied field on the board. Then, the pieces are treated as kings in Chess — they can be moved only to unoccupied adjacent fields.

The goal of the game is to make a four in a row, diagonal or square formation. The game is considered to be drawn if the same pair of moves (move = ply + reply) gets repeated three times in a row.

2. How to play

During the opening phase, just click on the field you want to play. During the post-opening phase, two clicks are required to move a piece. First, click on the field occupied by the piece you want to move. Then click on the field you want to move that piece to.

3. How to learn

TeekoTeacher allows you to play on behalf both sides and to learn from advices provided by the program. You can choose to receive advices for the first player, for the second player and for both players.

During the game, depending on the setup and the availability of the current position in the database, TeekoTeacher might provide an advice for the next best move (where "the best" is "the best according to the database.") This advice will be represented as a flashing field (in the opening phase) and as a flashing pair of board fields ('from' and 'to' fields) during the post-opening phase. You can follow those advices and try to learn from them or, alternatively, you can choose to ignore them.

You can also use the log area to copy and then save game logs so you can study them later at your convenience.

The original Java code, 8 files
tt/FieldFlasherThread.java
package tt;

import java.awt.*;

public class FieldFlasherThread extends Thread
{
  private boolean flash = true;
  private Button oldButton = null;
  private Button newButton = null;

  private boolean adviseX = true;
  private boolean adviseO = true;
  int moveCounter = 0;

  /**
   * Flashes one (in opening) two fields ("from" and "to") (in the game)
   */
  public FieldFlasherThread(Button oldButton, Button newButton, int moveCounter,
                            boolean adviseX, boolean adviseO)
  {
    this.newButton = newButton;
    this.oldButton = oldButton;
    this.moveCounter = moveCounter;
    this.adviseX = adviseX;
    this.adviseO = adviseO;
  }

  public void run()
  {
    while(flash)
    {
      if(xIsOnTheMove() && adviseX ||
         !xIsOnTheMove() && adviseO)
      {
        setAdvisingX(adviseX);
        setAdvisingO(adviseO);

        if(newButton != null)
          newButton.setBackground(Color.white);
        if(oldButton != null)
          oldButton.setBackground(Color.yellow);

        try { Thread.sleep(TTStarter.FLASH_DELAY); }
        catch(InterruptedException ie) { ; }

        if(!flash)
        {
          reset();
          return;
        }

        setAdvisingX(adviseX);
        setAdvisingO(adviseO);

        if(xIsOnTheMove() && adviseX ||
         !xIsOnTheMove() && adviseO)
        {
          if(newButton != null)
            newButton.setBackground(Color.yellow);
          if(oldButton != null)
            oldButton.setBackground(Color.white);

          try { Thread.sleep(TTStarter.FLASH_DELAY); }
          catch(InterruptedException ie){ ; }

          setAdvisingX(adviseX);
          setAdvisingO(adviseO);
        }
      }
    }

    if(newButton != null)
      newButton.setBackground(Color.white);
    if(oldButton != null)
      oldButton.setBackground(Color.white);
  }

  private void reset()
  {
    if(newButton != null)
      newButton.setBackground(Color.white);
    if(oldButton != null)
      oldButton.setBackground(Color.white);
  }

  public void stopFlashing()
  {
    flash = false;
    try
    {
      interrupt();
    }
    catch(Throwable t)
    {
      System.out.println("You can ignore this:");
      t.printStackTrace();
    }
  }

  public void setAdvisingX(boolean b)
  {
    if(!xIsOnTheMove())
      reset();
    adviseX = b;
  }

  public void setAdvisingO(boolean b)
  {
    if(xIsOnTheMove())
      reset();
    adviseO = b;
  }

  private boolean xIsOnTheMove()
  {
    return moveCounter % 2 != 0;
  }
}
tt/MessageBox.java
package tt;

import java.awt.*;
import java.awt.event.*;

/**
 * Encapsulates a trivial message box.
 */
public class MessageBox extends Frame implements ActionListener
{
  private Button b = new Button("  OK  ");
  private TTFrame tf = null;
  private boolean restart = false;

  public MessageBox(String title, String message, TTFrame tf, boolean restart, boolean textAreaDisplay)
  {
    super(title);
    this.tf = tf;
    this.restart = restart;

    this.setLayout(new BorderLayout());
    Panel p = new Panel(new BorderLayout());
    if(!textAreaDisplay)
    {
      p.add(new Label(message, Label.CENTER), BorderLayout.CENTER);
      this.setResizable(false);
    }
    else
    {
      TextArea ta = new TextArea(message, 20, 60, TextArea.SCROLLBARS_VERTICAL_ONLY);
      ta.setEditable(false);
      ta.setBackground(Color.white);
      p.add(ta);
      this.setResizable(true);
    }

    Panel bWrap = new Panel(new FlowLayout(FlowLayout.CENTER));
    bWrap.add(b);
    b.addActionListener(this);

    add(p, BorderLayout.CENTER);
    add(bWrap, BorderLayout.SOUTH);

    // closing of the frame
    this.addWindowListener(new WindowAdapter()
    {
      public void windowClosing(WindowEvent we)
      {
        MessageBox.this.dispose();
      }
    });

    pack();

    int w = Toolkit.getDefaultToolkit().getScreenSize().width;
    int h = Toolkit.getDefaultToolkit().getScreenSize().height;
    int x = (int)(w - getPreferredSize().width) / 2;
    int y = (int)(h - getPreferredSize().height) / 2;
    this.setBounds(x, y, (int)getPreferredSize().width, (int)getPreferredSize().height);

    show();
  }

  public void actionPerformed(ActionEvent ae)
  {
    dispose();
    if(restart && tf != null)
      tf.restart();
  }
}
tt/TTBoard.java
package tt;

import java.util.*;

public class TTBoard
{
  public static TTFrame f = null;

  public static final int SIZE = 5;
  public int[][] board = new int[SIZE][SIZE]; // the board

  public TTBoard nextBestPosition = null;
  public int oldX = -1;
  public int oldY = -1;
  public int newX = -1;
  public int newY = -1;

  public int wonFirst = -1;
  public int wonSecond = -1;

  /**
   * Default constructor
   */
  public TTBoard() { ; }

  /**
   * Constructor that takes a board
   */
  public TTBoard(TTBoard currBoard)
  {
    if(currBoard == null)
      return;

    for (int i = 0; i < SIZE; i++)
      for (int j = 0; j < SIZE; j++)
        board[i][j] = currBoard.board[i][j];

    oldX = currBoard.oldX;
    oldY = currBoard.oldY;
    newX = currBoard.newX;
    newY = currBoard.newY;
    nextBestPosition = currBoard.nextBestPosition;
    wonFirst = currBoard.wonFirst;
    wonSecond = currBoard.wonSecond;
  }

  /**
   * Dumps the board to log (used for debug)
   */
  public static void dumpBoard(TTBoard b)
  {
    if(b == null)
    {
      System.out.println("NULL!");
      return;
    }
    for(int i=0; i<SIZE; i++)
      System.out.println("" + b.board[i][0] +
                         "" + b.board[i][1] +
                         "" + b.board[i][2] +
                         "" + b.board[i][3] +
                         "" + b.board[i][4]);
    System.out.println("oldX: " + b.oldX + " oldY: " + b.oldY);
    System.out.println("newX: " + b.newX + " newY: " + b.newY);
  }

  /**
   * Determine if there are four in a row, column, diagonal or square.
   */
  public boolean hasFour(int piece)
  {
    return fourInARow(piece) ||
           fourInAColumn(piece) ||
           fourInADiagonal(piece) ||
           fourInASquare(piece);
  }

  private boolean fourInARow(int piece)
  {
    for(int i=0; i<SIZE; i++)
    {
      for(int offset = 0; offset<2; offset++)
      {
        boolean found = true;
        for(int j=0 + offset; j<4 + offset; j++)
        {
          if(board[i][j] != piece)
          {
            found = false;
            break;
          }
        }
        if(found) return true;
      }
    }
    return false;
  }

  private boolean fourInAColumn(int piece)
  {
    for(int j=0; j<SIZE; j++)
    {
      for(int offset = 0; offset<2; offset++)
      {
        boolean found = true;
        for(int i=0 + offset; i<4 + offset; i++)
        {
          if(board[i][j] != piece)
          {
            found = false;
            break;
          }
        }
        if(found) return true;
      }
    }
    return false;
  }

  private boolean fourInADiagonal(int piece)
  {
    return
      // diagonals on main diagonals
      board[0][0] == piece && board[1][1] == piece && board[2][2] == piece && board[3][3] == piece ||
      board[1][1] == piece && board[2][2] == piece && board[3][3] == piece && board[4][4] == piece ||
      board[0][4] == piece && board[1][3] == piece && board[2][2] == piece && board[3][1] == piece ||
      board[1][3] == piece && board[2][2] == piece && board[3][1] == piece && board[4][0] == piece ||
      // "small" diagonals
      board[0][1] == piece && board[1][2] == piece && board[2][3] == piece && board[3][4] == piece ||
      board[1][0] == piece && board[2][1] == piece && board[3][2] == piece && board[4][3] == piece ||
      board[0][3] == piece && board[1][2] == piece && board[2][1] == piece && board[3][0] == piece ||
      board[1][4] == piece && board[2][3] == piece && board[3][2] == piece && board[4][1] == piece;
  }

  private boolean fourInASquare(int piece)
  {
    for(int i=0; i<SIZE-1; i++)
    {
      for(int j=0; j<SIZE-1; j++)
      {
        if(fourInASpecificSquare(piece, i, j))
           return true;
      }
    }
    return false;
  }

  private boolean fourInASpecificSquare(int piece, int x, int y)
  {
    return board[x][y] == piece && board[x+1][y] == piece &&
           board[x][y+1] == piece && board[x+1][y+1] == piece;
  }

  /**
   * Checks if the whole board is empty
   */
  public boolean isEmpty()
  {
    for(int i=0; i<SIZE; i++)
      for(int j=0; j<SIZE; j++)
        if(board[i][j] != TTEngine.EMPTY)
          return false;
    return true;

  }

  /**
   * Calculate all successors of a given board for a given player
   * @return vector of all successors
   */
  public static Vector getAllSuccessors(TTBoard b, int piece)
  {
    Vector all = new Vector();
    for(int i=0; i<SIZE; i++)
    {
      for(int j=0; j<SIZE; j++)
      {
        if(b.board[i][j] == piece)
        {
          // find all successors that emerge when you move this piece
          Vector allForThisPiece = getAllSuccessorsForOnePiece(b, piece, i, j);
          // add them to the result
          for(int k=0; k<allForThisPiece.size(); k++)
            all.addElement(allForThisPiece.elementAt(k));
        }
      }
    }
    return all;
  }

  /**
   * Try to make all possible moves with one piece
   */
  private static Vector getAllSuccessorsForOnePiece(TTBoard b, int piece, int x, int y)
  {
    Vector v = new Vector();

    // there are maximum 8 possible successors from x,y:
    addSingleSuccessor(v, b, piece, x, y, x-1, y-1); // up left
    addSingleSuccessor(v, b, piece, x, y, x,   y-1); // up
    addSingleSuccessor(v, b, piece, x, y, x+1, y-1); // up right

    addSingleSuccessor(v, b, piece, x, y, x-1, y  ); // left
    addSingleSuccessor(v, b, piece, x, y, x+1, y  ); // right

    addSingleSuccessor(v, b, piece, x, y, x-1, y+1); // down left
    addSingleSuccessor(v, b, piece, x, y, x,   y+1); // down
    addSingleSuccessor(v, b, piece, x, y, x+1, y+1); // up right

    return v;
  }

  /**
   * Creates a successor (if possible)
   */
  private static void addSingleSuccessor(Vector v, TTBoard b, int piece, int oldX, int oldY, int newX, int newY)
  {
    if(newX >= 0 && newX < SIZE && newY >= 0 && newY < SIZE && // new position must be valid...
       b.board[newX][newY] == TTEngine.EMPTY) // ...and empty
    {
      TTBoard temp = new TTBoard(b);
      temp.board[oldX][oldY] = TTEngine.EMPTY; // remove the piece from the old position
      temp.board[newX][newY] = piece; // put the piece to the new position
      temp.newX = newX;
      temp.newY = newY;
      temp.oldX = oldX;
      temp.oldY = oldY;
      v.addElement(temp);
    }
  }

  /**
   * Checks if two boards are the same
   */
  public static boolean sameBoards(TTBoard b1, TTBoard b2)
  {
    if(b1 == null || b2 == null) // have to be non-null boards
      return false;
    for(int i=0; i<SIZE; i++)
      for(int j=0; j<SIZE; j++)
        if(b1.board[i][j] != b2.board[i][j])
          return false;
    return true;
  }

  /**
   * Makes a symmetric board (in the terms of the player's pieces)
   */
  private void makeSymmetric()
  {
    for(int i=0; i<SIZE; i++)
      for(int j=0; j<SIZE; j++)
      {
        if(board[i][j] == TTEngine.O)
          board[i][j] = TTEngine.X;
        else if(board[i][j] == TTEngine.X)
          board[i][j] = TTEngine.O;
      }
  }

  /**
   * Calculates the best position succeeding the current one.
   * 1. Finds all successors of the current position.
   * 2. Tries to see which ones match with the database.
   * 3. Uses the highest ranked position among the matched positions.
   */
  public void calculateNextBestPosition(int piece, boolean opening, int moveCounter)
  {
    if(opening) // opening, use heuristic
    {
      nextBestPosition = TTOpening.getMove(piece, this, moveCounter);
      f.log("Heuristic is suggesting this opening move: " +
            (char)('A' + nextBestPosition.newY) + "" + (nextBestPosition.newX+1));
    }
    else // post-opening, use the database
    {
      // find the succesors that are present in the database
      Vector plainSuccessors = getAllSuccessors(this, piece);
      Vector present = new Vector();
      int plainCount = 0;
      int symmetricCount = 0;
      for(int i=0; i<TTDatabase.boards.size(); i++)
      {
        TTBoard dbBoard = (TTBoard)TTDatabase.boards.elementAt(i);

        // check how many plain successors we have in the database
        for(int j=0; j<plainSuccessors.size(); j++)
        {
          TTBoard temp = (TTBoard)plainSuccessors.elementAt(j);
          if(TTBoard.sameBoards(dbBoard, temp))
          {
            addBoard(present, temp, dbBoard, false);
            plainCount++;
          }
          else // maybe the symmetric one is there?
          {
            temp.makeSymmetric();
            if(TTBoard.sameBoards(dbBoard, temp))
            {
              addBoard(present, temp, dbBoard, true);
              symmetricCount++;
            }
          }
        }
      }

      f.log(plainSuccessors.size() + " possible moves for '" +
            TTEngine.getPieceGUI(piece) + "' found from this position");
      f.log(plainCount + " plain and " + symmetricCount + " symmetric move(s) found in the database");

      nextBestPosition = null;

      // calculate the best plain position
      int maxScore = Integer.MIN_VALUE;
      int gamesPlayed = 0;
      int gamesWon = 0;
      for(int i=0; i<present.size(); i++)
      {
        TTBoard temp = (TTBoard)present.elementAt(i);
        int score = piece == TTEngine.X ? temp.wonFirst - temp.wonSecond :
                                          temp.wonSecond - temp.wonFirst;
        if(score > maxScore)
        {
          gamesPlayed = temp.wonFirst + temp.wonSecond;
          gamesWon = piece == TTEngine.X ? temp.wonFirst : temp.wonSecond;
          maxScore = score;
          nextBestPosition = new TTBoard(temp);
        }
      }

      if(nextBestPosition != null)
      {
        float winningPercentage = (100 * gamesWon) / gamesPlayed;
        f.log("Recommended move: " + (char)('A' + nextBestPosition.oldY) + "" + (nextBestPosition.oldX+1) +
              "-" + (char)('A' + nextBestPosition.newY) + "" + (nextBestPosition.newX+1));
        f.log("The advice was part of a winning strategy in " + gamesWon + " of " + gamesPlayed +
              " games (" + winningPercentage + "%)");
      }
    }
  }

  private void addBoard(Vector present, TTBoard temp, TTBoard dbBoard, boolean symmetric)
  {
    TTBoard toAdd = new TTBoard(temp);
    // propagate the values!
    toAdd.wonFirst = symmetric ? dbBoard.wonSecond : dbBoard.wonFirst;
    toAdd.wonSecond = symmetric ?  dbBoard.wonFirst : dbBoard.wonSecond;
    present.addElement(toAdd);
  }
}
tt/TTDatabase.java
package tt;

import java.io.*;
import java.util.*;

public class TTDatabase
{
  public static Vector boards = new Vector(); // all positions will be here
  public static int total = 0;

  /**
   * The class that populates and encapsulates the database of positions
   */
  public TTDatabase(String fileName)
  {
    System.out.println("Reading the database...");
    readRawData(fileName);
    total = boards.size();
    System.out.println("Parsed " + total + " positions.");
    System.out.println("Cleaning the database...");
    postprocessData();
  }

  /**
   * Reads the raw data file and populates the database
   */
  private void readRawData(String fileName)
  {
    char[] buffer = new char[4];

    try
    {
      InputStreamReader fr = getReader(fileName);
      while(true)
      {
        TTBoard b = new TTBoard();

        // read the board fields
        for(int i=0; i<25; i++)
        {
          int x = i / TTBoard.SIZE;
          int y = i - x * TTBoard.SIZE;

          int field = fr.read();
          switch(field)
          {
            case  -1 : return; // EOF!
            case '1' : { b.board[x][y] = TTEngine.X; break; }
            case '2' : { b.board[x][y] = TTEngine.O; break; }
            default  : { b.board[x][y] = TTEngine.EMPTY; }
          }
        }

        // read wins and loses of the first player (here, it's 'X') and calculate the score
        fr.read(buffer);
        b.wonFirst = convertToInt(buffer);
        fr.read(buffer);
        b.wonSecond = convertToInt(buffer);

        // add the board
        boards.addElement(b);
      }
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }

  /**
   * Goes trough all positions, finds duplicates and accumulates them
   */
  private void postprocessData()
  {
    for(int i=0; i<boards.size()-1; i++)
    {
      if(i > 0 && i % 500 == 0)
        System.out.println("Added " + i + " unique positions...");

      TTBoard first = (TTBoard)boards.elementAt(i);
      for(int j=i+1; j<boards.size(); j++)
      {
        TTBoard second = (TTBoard)boards.elementAt(j);
        if(TTBoard.sameBoards(first, second))
        {
          first.wonFirst += second.wonFirst;
          first.wonSecond +=second.wonSecond;
          boards.removeElementAt(j);
        }
      }
    }
  }

  // utility methods

  /**
   * Convert an array of chars to integer
   */
  private static final int convertToInt(char[] chars)
  {
    // put leading zeros instead of spaces
    for(int i=0; i<chars.length; i++)
      if(chars[i] == ' ')
        chars[i] = '0';
    // calculate the number
    int res = 0;
    for(int i=0; i<chars.length; i++)
      res += powTen(i) * (chars[chars.length - i - 1] - '0');
    return res;
  }

  /**
   * Quick power of ten (faster than Java's Math.pow which deals with doubles)
   */
  private static final int powTen(int pow)
  {
    int res = 1;
    for(int i=0; i<pow; i++)
      res *= 10;
    return res;
  }

  /**
   * Gets a reader for the database file depending on the file location (filesystem or applet's JAR)
   */
  private static final InputStreamReader getReader(String name)
  {
    InputStreamReader isr = null;
    try
    {
      isr = new FileReader(name); // file system
    }
    catch(Exception e)
    {
      InputStream in = TTStarter.class.getResourceAsStream("/" + name); // applet's JAR
      isr = new InputStreamReader(in);
    }
    return isr;
  }
}
tt/TTEngine.java
package tt;

import java.awt.*;
import java.util.*;

public class TTEngine
{
  // internal statics
  public static final int EMPTY = 0; // empty field
  public static final int X = 1; // "X" piece
  public static final int O = 2; // "O" piece
  private TTFrame f;

  // game states
  public static final int COMPUTER_WON = 0;
  public static final int USER_WON = 1;
  public static final int DRAW = 2;
  private static final int KEEP_PLAYING = -1;

  // other internal variables
  public TTBoard board = null; // the board
  public boolean computerFirst = false; // whether computer played first or not
  public int moveCounter = 1; // the current move
  public int gameState = KEEP_PLAYING; // the state of the game
  private TTBoard[] history = new TTBoard[13]; // if 1 == 5 == 9 == 13  =>  draw

  FieldFlasherThread t = null;

  /**
   * The constructor
   */
  public TTEngine(TTFrame f)
  {
    board = new TTBoard();
    this.f = f;
  }

  /**
   * Resets the board
   */
  public void resetBoard()
  {
    board = new TTBoard();
    moveCounter = 1;
    this.gameState = KEEP_PLAYING;
    if(t != null)
      t.stopFlashing();
  }

  /**
   * User's move implementation
   * Updates the game state
   * @return true if the move was valid
   */
  public boolean play(int field, int prevField)
  {
    int i = field / TTBoard.SIZE;
    int j = field - TTBoard.SIZE * i;

    int iPrev = prevField / TTBoard.SIZE;
    int jPrev = prevField - TTBoard.SIZE * iPrev;

    if(isOpening())
    {
      if(board.board[i][j] == EMPTY)
      {
        board.board[i][j] = getUserPiece();
        f.log("'" + getPieceGUI(getUserPiece()) + "' played on " + (char)('A' + i) + "" + (j+1));
        moveCounter++;
        updateGameState();
        return true;
      }
      else
        return false;
    }
    else
    {
      if(board.board[iPrev][jPrev] == getUserPiece() && // can move it's own pieces only...
         board.board[i][j] == EMPTY && // ...to unoccupied...
         canMoveHere(iPrev, jPrev, i, j)) // ...AND adjacent fileds
      {
        board.board[iPrev][jPrev] = EMPTY;
        board.board[i][j] = getUserPiece();
        f.log("'" + getPieceGUI(getUserPiece()) + "' played " +
              (char)('A' + jPrev) + "" + (iPrev+1) + "-" +
              (char)('A' + j) + "" + (i+1));
        moveCounter++;
        updateGameState();
        return true;
      }
      else
        return false;
    }
  }

  /**
   * Return all immediate successors from a given position
   */
  private Vector getAllSuccessors(TTBoard b, int player)
  {
    // todo...
    return new Vector();
  }

  /**
   * Returns the internal representation of computer's piece
   */
  public int getComputerPiece()
  {
    //return computerFirst ? X : O;
    return moveCounter % 2 == 0 ? X : O;
  }

  /**
   * Returns the internal representation of user's piece
   */
  public int getUserPiece()
  {
    //return computerFirst ? O : X;
    return moveCounter % 2 == 0 ? O : X;
  }

  /**
   * Returns the representaion of the piece on the board
   */
  public static String getPieceGUI(int i)
  {
    switch(i)
    {
      case X : return "X";
      case O : return "O";
      default : return "";
    }
  }

  /**
   * Getter for the gameState
   */
  public int getGameState()
  {
    return gameState;
  }

  /**
   * Updates the game state
   */
  public void updateGameState()
  {
    pushPosition();

    if(board.hasFour(getComputerPiece()))
    {
      gameState = COMPUTER_WON;
      return;
    }

    if(board.hasFour(getUserPiece()))
    {
      gameState = USER_WON;
      return;
    }

    if(isDraw())
    {
      gameState = DRAW;
      return;
    }

    // calculate the next best position (covers both opening and the real game)
    board.calculateNextBestPosition(getUserPiece(), isOpening(), moveCounter);

    // stop the previous thread (if any)
    if(t != null)
    {
      t.stopFlashing();
      t = null;
    }
    if(board.nextBestPosition != null)
    {
      // create and start a new one (old button is present only if not in opening phase)
      Button newB = f.buttons[board.nextBestPosition.newX][board.nextBestPosition.newY];
      Button oldB = isOpening() ? null : f.buttons[board.nextBestPosition.oldX][board.nextBestPosition.oldY];
      boolean adviseX = f.adviseBoth.getState() || f.adviseX.getState();
      boolean adviseO = f.adviseBoth.getState() || f.adviseO.getState();
      t = new FieldFlasherThread(oldB, newB, moveCounter, adviseX, adviseO);
      t.start();
    }
    else
      f.log("Sorry, no advice for this position");
    f.log("'" + getPieceGUI(getUserPiece()) + "' is on the move..");
  }

  /**
   * Checks if the game is in it's opening phase (first 8 moves) or not
   */
  private boolean isOpening()
  {
    return moveCounter < 9;
  }

  /**
   * Checks if a move is valid
   */
  private boolean canMoveHere(int fromX, int fromY, int toX, int toY)
  {
    int xDiff = Math.abs(fromX - toX);
    int yDiff = Math.abs(fromY - toY);

    if(xDiff == 0 && yDiff == 0) // can't move on the same spot
      return false;

    if(xDiff > 1 || yDiff > 1) // can't move more than 1 field away
      return false;

    return true;
  }

  /**
   * Pushes the current position into the history
   */
  private void pushPosition()
  {
    for(int i=1; i<history.length; i++)
    {
      if(history[i] != null)
      {
        TTBoard temp = new TTBoard(history[i]);
        history[i-1] = temp;
      }
    }
    history[history.length-1] = new TTBoard(board);
  }

  /**
   * Checks if the last 3 moves were repeated ones.  This is a draw.
   */
  private boolean isDraw()
  {
    for(int i=0; i<5; i++)
    {
      if(!TTBoard.sameBoards(history[i], history[i+4]) ||
         !TTBoard.sameBoards(history[i+4], history[i+8]))
        return false;
    }
    return true;
  }

  /*private void dumpHistory()
  {
    for(int i=0; i<history.length; i++)
    {
      System.out.println(i);
      TTBoard.dumpBoard(history[i]);
      System.out.println("===");
    }
  }*/
}
tt/TTFrame.java
package tt;

import java.awt.*;
import java.awt.event.*;
import java.io.*;

/**
 * Frame to encapsulate the GUI for the game
 */
public class TTFrame extends Frame implements ActionListener, ItemListener
{
  // GUI appearance
  private static final String BOARD_FONT = "monospaced";
  private static final int BOARD_FONT_SIZE = 60;

  // GUI variables
  private TextArea status = null;
  private Button restartButton = new Button("Restart Game");
  private Button clearLogButton = new Button("Clear Log");
  private Checkbox playsFirstCB = new Checkbox("Computer Plays First");
  private Checkbox logCB = new Checkbox("Show Log", true);
  private Panel statusPanelWrap = null;
  public Button buttons[][] = null;
  private MenuItem instructionsMenuItem = new MenuItem("Instructions");
  private MenuItem aboutMenuItem = new MenuItem("About TeekoTeacher");
  private MenuItem databaseMenuItem = new MenuItem("Database Info");
  private CheckboxGroup cbg = new CheckboxGroup();
  public Checkbox adviseO = new Checkbox("Player 'O'", cbg, false);
  public Checkbox adviseX = new Checkbox("Player 'X'", cbg, false);
  public Checkbox adviseBoth = new Checkbox("Both", cbg, true);

  // internal variables
  private TTEngine engine = null;
  private boolean firstTime = true;
  private int field = -1; // the current clicked field
  private int prevField = -1; // the precvious clicked field

  /**
   * The constructor
   */
  public TTFrame()
  {
    super();
    TTBoard.f = this;
    engine = new TTEngine(this);
    initGUI();
    log("Welcome to TeekoTeacher");
    restart();
  }

  /**
   * Inits the GUI
   */
  private void initGUI()
  {
    setTitle("TeekoTeacher");
    setLayout(new BorderLayout());

    // set up the menu bar and menus
    MenuBar mb = new MenuBar();
    this.setMenuBar(mb);

    Menu fm = new Menu("File");
    fm.add(databaseMenuItem);
    mb.add(fm);

    Menu hm = new Menu("Help");
    hm.add(instructionsMenuItem);
    hm.addSeparator();
    hm.add(aboutMenuItem);
    mb.setHelpMenu(hm);

    databaseMenuItem.addActionListener(this);
    instructionsMenuItem.addActionListener(this);
    aboutMenuItem.addActionListener(this);

    Panel controlPanel = new Panel();
    controlPanel.setLayout(new BorderLayout());

    Panel radios = new Panel(new GridLayout(4, 1));
    radios.add(new Label("Advise:"));
    radios.add(adviseX);
    radios.add(adviseO);
    radios.add(adviseBoth);
    adviseX.addItemListener(this);
    adviseO.addItemListener(this);
    adviseBoth.addItemListener(this);
    Panel radiosWrap = new Panel(new BorderLayout());
    radiosWrap.add(new Panel(), BorderLayout.NORTH);
    radiosWrap.add(new Panel(), BorderLayout.CENTER);
    radiosWrap.add(radios, BorderLayout.SOUTH);

    Panel bp = new Panel(new BorderLayout());
    bp.add(clearLogButton, BorderLayout.NORTH);
    bp.add(new Panel(), BorderLayout.CENTER);
    bp.add(restartButton, BorderLayout.SOUTH);

    controlPanel.add(radiosWrap, BorderLayout.NORTH);
    controlPanel.add(logCB, BorderLayout.CENTER);
    controlPanel.add(bp, BorderLayout.SOUTH);

    Panel controlPanelWrap = new Panel(new BorderLayout());
    controlPanelWrap.add(controlPanel, BorderLayout.CENTER);
    controlPanelWrap.add(new Panel(), BorderLayout.EAST);

    Panel boardPanel = new Panel(new GridLayout(TTBoard.SIZE+1, TTBoard.SIZE+1))
    {
      public Dimension getPreferredSize() { return new Dimension(350, 350); }
    };

    boardPanel.add(new Label());
    for(int i=0; i<TTBoard.SIZE; i++)
    {
      Label l = new Label("" + (i+1));
      boardPanel.add(new Label("" + (char)('A' + i), Label.CENTER));
    }
    buttons = new Button[TTBoard.SIZE][TTBoard.SIZE];
    for(int i=0; i<TTBoard.SIZE; i++)
    {
      boardPanel.add(new Label("" + (i+1), Label.CENTER));
      for(int j=0; j<TTBoard.SIZE; j++)
      {
        Button b = new Button() { public boolean isFocusTraversable() { return false; } };
        int buttonId = i * TTBoard.SIZE + j;
        b.addActionListener(this);
        b.setActionCommand("" + buttonId);
        b.setBackground(Color.white);
        b.setFont(new Font(BOARD_FONT, Font.BOLD, BOARD_FONT_SIZE));
        boardPanel.add(b);
        buttons[i][j] = b;
      }
    }
    Panel boardPanelWrap = new Panel(new BorderLayout());
    boardPanelWrap.add(boardPanel, BorderLayout.CENTER);
    boardPanelWrap.add(new Panel(), BorderLayout.SOUTH);
    boardPanelWrap.add(new Panel(), BorderLayout.WEST);
    boardPanelWrap.add(new Panel(), BorderLayout.NORTH);
    boardPanelWrap.add(new Panel(), BorderLayout.EAST);

    Panel statusPanel = new Panel();
    statusPanel.setLayout(new BorderLayout());
    status = new TextArea(8, 60);
    status.setEditable(false);
    status.setBackground(Color.white);
    statusPanel.add(status, BorderLayout.CENTER);
    statusPanelWrap = new Panel(new BorderLayout());
    statusPanelWrap.add(statusPanel, BorderLayout.CENTER);
    statusPanelWrap.add(new Panel(), BorderLayout.NORTH);
    statusPanelWrap.add(new Panel(), BorderLayout.SOUTH);
    statusPanelWrap.add(new Panel(), BorderLayout.WEST);
    statusPanelWrap.add(new Panel(), BorderLayout.EAST);

    Panel infoPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
    infoPanel.add(new Label("TeekoTeacher"));

    //add(infoPanel, BorderLayout.NORTH);
    add(controlPanelWrap, BorderLayout.EAST);
    add(boardPanelWrap, BorderLayout.CENTER);
    add(statusPanelWrap, BorderLayout.SOUTH);
    refreshLogging();

    // closing of the frame
    this.addWindowListener(new WindowAdapter()
     {
       public void windowClosing(WindowEvent we)
       {
         TTFrame.this.dispose();
       }
     });

    // add listeners
    restartButton.addActionListener(this);
    clearLogButton.addActionListener(this);
    playsFirstCB.addItemListener(this);
    logCB.addItemListener(this);

    pack();

    int w = Toolkit.getDefaultToolkit().getScreenSize().width;
    int h = Toolkit.getDefaultToolkit().getScreenSize().height;
    int x = (w - (int)getPreferredSize().width) / 2;
    int y = (h - (int)getPreferredSize().height) / 2;
    setBounds(x, y, (int)getPreferredSize().width, (int)getPreferredSize().height );

    show();

    if(TTDatabase.boards.size() == 0)
      new MessageBox("No Database!", "The database file (" +
                     TTStarter.DATABASE_FILE_NAME + ") not found or empty.", this, false, false);
  }

  /**
   * Deals with the buttons
   */
  public void actionPerformed(ActionEvent ae)
  {
    Object src = ae.getSource();
    String cmd = ae.getActionCommand();

    if(src == restartButton)
    {
      try { restart(); }
      catch(Throwable t) { new MessageBox("Exception!", t.toString(), this, false, false); }
    }
    else if(src == aboutMenuItem)
      about();
    else if(src == instructionsMenuItem)
      instructions();
    else if(src == databaseMenuItem)
      showDatabaseInfo();
    else if(src == clearLogButton)
      status.setText("");
    else // board fields
    {
      prevField = field;
      field = Integer.parseInt(cmd);
      if(engine.play(field, prevField)) // if the move was legal, the board was updated...
      {
        updateBoard(field);
        if(gameFinished())
        {
          engine.resetBoard();
          return;
        }
      }
    }
  }

  /**
   * Deals with the checkboxes
   */
  public void itemStateChanged(ItemEvent ie)
  {
    Object src = ie.getSource();
    if(src == logCB)
      refreshLogging();
    else if(src == adviseX || src == adviseO || src == adviseBoth)
    {
      boolean ax = adviseBoth.getState() || adviseX.getState();
      boolean ao = adviseBoth.getState() || adviseO.getState();
      engine.t.setAdvisingX(ax);
      engine.t.setAdvisingO(ao);
    }
  }

  /**
   * Restarts the game if one is in progress
   */
  protected void restart()
  {
    if(!engine.board.isEmpty())
    {
      if(!firstTime)
        log("Game has been restarted");
    }
    updateBoard(-1);
    firstTime = false;
  }

  /**
   * Updates the board GUI
   */
  private void updateBoard(int field)
  {
    if(field == -1) // restart
    {
      engine.resetBoard();
      for(int i=0; i<TTBoard.SIZE; i++)
        for(int j=0; j<TTBoard.SIZE; j++)
        {
          buttons[i][j].setLabel("");
          buttons[i][j].invalidate();
          engine.board = new TTBoard();
        }
      engine.updateGameState();
    }
    else // user played
    {
      for(int i=0; i<TTBoard.SIZE; i++)
      {
        for(int j=0; j<TTBoard.SIZE; j++)
        {
          String piece = engine.getPieceGUI(engine.board.board[i][j]);
          buttons[i][j].setForeground(piece.equals("X") ? Color.blue : Color.red);
          buttons[i][j].setLabel(piece);
          buttons[i][j].invalidate();
        }
      }
    }
    repaint();
  }

  /**
   * Check if the game is finished.
   * Might restart the game as a side effect.
   */
  private boolean gameFinished()
  {
    switch(engine.getGameState())
    {
      case TTEngine.COMPUTER_WON :
      {
        displayWin("COMP");
        return true;
      }
      case TTEngine.USER_WON :
      {
        displayWin("USER");
        return true;
      }
      case TTEngine.DRAW :
      {
        displayDraw();
        return true;
      }
    }
    return false;
  }

  /**
   * Displays won message
   */
  private void displayWin(String who)
  {
    String msg = who.equals("USER") ?
      "Player '" + engine.getPieceGUI(engine.getUserPiece())  + "'" :
      "Player '" + engine.getPieceGUI(engine.getComputerPiece()) + "'";
    msg += " won the game";
    log(msg);
    new MessageBox("The End", msg, this, true, false);
  }

  /**
   * Displays draw message
   */
  private void displayDraw()
  {
    String msg = "The game has been drawn.";
    log(msg);
    new MessageBox("The End", msg, this, true, false);
  }

  /**
   * Hide/display the logging area
   */
  private void refreshLogging()
  {
    statusPanelWrap.setVisible(logCB.getState());
    pack();
  }

  /**
   * Appends a string to the status
   */
  public void log(String s)
  {
    status.append(s + ".\n");
    status.setCaretPosition(status.getText().length());
  }

  /**
   * Display the about box
   */
  private void about()
  {
    new MessageBox("About TeekoTeacher", "TeekoTeacher, Version " +
                   TTStarter.VERSION +
                   " by Mihailo Despotovic 2003.", this, false, false);
  }

  /**
   * Display the instructions
   */
  private void instructions()
  {
    String s = "";
    BufferedReader br = null;
    try // try loading from the local fule system
    {
      br = new BufferedReader(new FileReader(TTStarter.INSTRUCTIONS_FILE_NAME));
    }
    catch(Exception e)
    {
      try // try loading from applet's JAR as a resource
      {
        InputStream in = TTStarter.class.getResourceAsStream("/" + TTStarter.INSTRUCTIONS_FILE_NAME);
        br = new BufferedReader(new InputStreamReader(in));
      }
      catch(Exception e1)
      {
        e1.printStackTrace();
        log(e1.toString());
        s = "Sorry, could not open the instructions file (" + TTStarter.INSTRUCTIONS_FILE_NAME + ")";
      }
    }
    if(br != null)
    {
      StringBuffer sb = new StringBuffer("");
      String line = null;
      try
      {
        while((line = br.readLine()) != null)
          sb.append(line + "\n");
        s = sb.toString();
        s = s.substring(0, s.length() -1); // chop off the trailing '\n'
      }
      catch(Exception e)
      {
        e.printStackTrace();
        s = "Sorry, could not read the instructions file (" + TTStarter.INSTRUCTIONS_FILE_NAME + ")";
      }
    }
    new MessageBox("TeekoTeacher Instructions", s, this, false, true);
  }

  /**
   * Displays some basic informations about the database used
   */
  private void showDatabaseInfo()
  {
    String s = "The database contains " + TTDatabase.total + " (" + TTDatabase.boards.size() +
               " unique) Teeko positions.";
    new MessageBox("TeekoTeacher Database", s, this, false, false);
  }
}
tt/TTOpening.java
package tt;

import java.util.*;

public class TTOpening
{
  private static Random r = new Random(System.currentTimeMillis());

  public static TTBoard getMove(int piece, TTBoard b, int moveCounter)
  {
    if(piece == TTEngine.O)
      piece = TTEngine.X;
    else
      piece = TTEngine.O;

    TTBoard temp = new TTBoard(b);
    switch(moveCounter)
    {
      case 0 : { move1(temp); break; }
      case 1 : { move2(temp); break; }
      case 2 : { move3(temp, piece); break; }
      case 3 : { move4(temp, piece); break; }
      case 4 : { move5(temp, piece); break; }
      case 5 : { move6(temp, piece); break; }
      case 6 : { move7(temp, piece); break; }
      case 7 : { move7(temp, piece); break; }
      case 8 : { move7(temp, piece); break; }
    }
    return temp;
  }

  private static void move1(TTBoard b)
  {
    playInTheMiddle(b);
  }

  private static void move2(TTBoard b)
  {
    if(b.board[2][2] == TTEngine.EMPTY)
      playInTheMiddle(b);
    else
      playInACorner(b);
  }

  private static void move3(TTBoard b, int piece)
  {
    if(b.board[2][2] == TTEngine.EMPTY)
      playInTheMiddle(b);
    else
      makeDangerous2(b, piece);
  }

  private static void move4(TTBoard b, int piece)
  {
    if(!avoidDangerous3(b, piece))
      move3(b, piece);
  }

  private static void move5(TTBoard b, int piece)
  {
    if(!make3(b, piece))
      move4(b, piece);
  }

  private static void move6(TTBoard b, int piece)
  {
    if(!avoid4(b, piece))
      move5(b, piece);
  }

  private static void move7(TTBoard b, int piece)
  {
    if(!make4(b, piece))
      move6(b, piece);
  }

  private static void playInTheMiddle(TTBoard b)
  {
    b.newX = 2;
    b.newY = 2;
  }

  private static void makeDangerous2(TTBoard b, int piece)
  {
    if(b.board[2][2] == piece) // middle field is mine, OK to play "corner"
    {
      playInACorner(b);
    }
    else
    {
      if( ((b.board[1][1] == piece || b.board[3][3] == piece) &&
          (b.board[1][3] == TTEngine.EMPTY || b.board[3][1] == TTEngine.EMPTY))
          ||
          ((b.board[1][3] == piece || b.board[3][1] == piece) &&
          (b.board[1][1] == TTEngine.EMPTY || b.board[3][3] == TTEngine.EMPTY)) )
        playInACross(b);
      else
        playInACorner(b);
    }
  }

  private static boolean avoidDangerous3(TTBoard b, int piece)
  {
    for(int i=0; i<=4; i++) // horizontal
    {
      if(b.board[i][1]==piece && b.board[i][2]==piece && b.board[i][3]==0) { tryToPlay(b, i, 3); return true; }
      if(b.board[i][2]==piece && b.board[i][3]==piece && b.board[i][1]==0) { tryToPlay(b, i, 1); return true; }
      if(b.board[i][1]==piece && b.board[i][3]==piece && b.board[i][2]==0) { tryToPlay(b, i, 2); return true; }
    }

    for(int j=0; j<=4; j++) // vertical
    {
      if(b.board[1][j]==piece && b.board[2][j]==piece && b.board[3][j]==0) { tryToPlay(b, 3, j); return true; }
      if(b.board[2][j]==piece && b.board[3][j]==piece && b.board[1][j]==0) { tryToPlay(b, 1, j); return true; }
      if(b.board[1][j]==piece && b.board[3][j]==piece && b.board[2][j]==0) { tryToPlay(b, 2, j); return true; }
    }

    // diagonal
    if(b.board[1][1]==piece && b.board[2][2]==piece && b.board[3][3]==0) { tryToPlay(b, 3, 3); return true; }
    if(b.board[2][2]==piece && b.board[3][3]==piece && b.board[1][1]==0) { tryToPlay(b, 1, 1); return true; }
    if(b.board[1][3]==piece && b.board[2][2]==piece && b.board[3][1]==0) { tryToPlay(b, 3, 1); return true; }
    if(b.board[3][1]==piece && b.board[2][2]==piece && b.board[1][3]==0) { tryToPlay(b, 1, 3); return true; }

    return false;
  }

  private static boolean make3(TTBoard b, int piece)
  {
    // horizontal
    if(b.board[1][1]==piece && b.board[1][2]==piece && b.board[1][3]==0) {tryToPlay(b, 1, 3); return true;}
    if(b.board[1][2]==piece && b.board[1][3]==piece && b.board[1][1]==0) {tryToPlay(b, 1, 1); return true;}
    if(b.board[1][1]==piece && b.board[1][3]==piece && b.board[1][2]==0) {tryToPlay(b, 1, 2); return true;}

    if(b.board[2][1]==piece && b.board[2][2]==piece && b.board[2][3]==0) {tryToPlay(b, 2, 3); return true;}
    if(b.board[2][2]==piece && b.board[2][3]==piece && b.board[2][1]==0) {tryToPlay(b, 2, 1); return true;}
    if(b.board[2][3]==piece && b.board[2][1]==piece && b.board[2][2]==0) {tryToPlay(b, 1, 2); return true;}

    if(b.board[3][1]==piece && b.board[3][2]==piece && b.board[3][3]==0) {tryToPlay(b, 3, 3); return true;}
    if(b.board[3][2]==piece && b.board[3][3]==piece && b.board[3][1]==0) {tryToPlay(b, 3, 1); return true;}
    if(b.board[3][1]==piece && b.board[3][3]==piece && b.board[3][2]==0) {tryToPlay(b, 3, 2); return true;}

    // vertical
    if(b.board[1][1]==piece && b.board[2][1]==piece && b.board[3][1]==0) {tryToPlay(b, 3, 1); return true;}
    if(b.board[2][1]==piece && b.board[3][1]==piece && b.board[1][1]==0) {tryToPlay(b, 1, 1); return true;}
    if(b.board[1][1]==piece && b.board[3][1]==piece && b.board[2][1]==0) {tryToPlay(b, 2, 1); return true;}

    if(b.board[1][2]==piece && b.board[2][2]==piece && b.board[3][2]==0) {tryToPlay(b, 3, 2); return true;}
    if(b.board[1][2]==piece && b.board[3][2]==piece && b.board[2][2]==0) {tryToPlay(b, 2, 2); return true;}
    if(b.board[2][2]==piece && b.board[3][2]==piece && b.board[1][2]==0) {tryToPlay(b, 1, 2); return true;}

    if(b.board[1][3]==piece && b.board[2][3]==piece && b.board[3][3]==0) {tryToPlay(b, 3, 3); return true;}
    if(b.board[1][3]==piece && b.board[3][3]==piece && b.board[2][3]==0) {tryToPlay(b, 2, 3); return true;}
    if(b.board[2][3]==piece && b.board[3][3]==piece && b.board[1][3]==0) {tryToPlay(b, 1, 3); return true;}

    // diagonal
    if(b.board[1][1]==piece && b.board[2][2]==piece && b.board[3][3]==0) {tryToPlay(b, 3, 3); return true;}
    if(b.board[2][2]==piece && b.board[3][3]==piece && b.board[1][1]==0) {tryToPlay(b, 1, 1); return true;}
    if(b.board[1][3]==piece && b.board[2][2]==piece && b.board[3][1]==0) {tryToPlay(b, 3, 1); return true;}
    if(b.board[3][1]==piece && b.board[2][2]==piece && b.board[1][3]==0) {tryToPlay(b, 1, 3); return true;}

    // square ?!

    return false;
  }

  private static boolean avoid4(TTBoard b, int e)
  {
    // horizontal
    for(int i=0; i<=4; i++)
    {
      if(b.board[i][0]==e && b.board[i][1]==e && b.board[i][2]==e && b.board[i][3]==0)
      { tryToPlay(b, i, 3); return true; }

      if(b.board[i][2]==e && b.board[i][3]==e && b.board[i][4]==e && b.board[i][1]==0)
      { tryToPlay(b, i, 1); return true; }
    }

    // vertical
    for(int j=0; j<=4; j++)
    {
      if(b.board[0][j]==e && b.board[1][j]==e && b.board[2][j]==e && b.board[3][j]==0)
      { tryToPlay(b, 3, j); return true; }

      if(b.board[2][j]==e && b.board[3][j]==e && b.board[4][j]==e && b.board[1][j]==0)
      { tryToPlay(b, 1, j); return true; }
    }

    // diagonal
    if(b.board[0][1]==e&&b.board[1][2]==e&&b.board[2][3]==e&&b.board[3][4]==0){tryToPlay(b, 3, 4); return true;}
    if(b.board[3][4]==e&&b.board[1][2]==e&&b.board[2][3]==e&&b.board[0][1]==0){tryToPlay(b, 0, 1); return true;}

    if(b.board[0][0]==e&&b.board[1][1]==e&&b.board[2][2]==e&&b.board[3][3]==0){tryToPlay(b, 3, 3); return true;}
    if(b.board[2][2]==e&&b.board[3][3]==e&&b.board[4][4]==e&&b.board[1][1]==0){tryToPlay(b, 1, 1); return true;}

    if(b.board[1][0]==e&&b.board[2][1]==e&&b.board[3][2]==e&&b.board[4][3]==0){tryToPlay(b, 4, 3); return true;}
    if(b.board[2][1]==e&&b.board[3][2]==e&&b.board[4][3]==e&&b.board[1][0]==0){tryToPlay(b, 1, 0); return true;}


    if(b.board[0][3]==e&&b.board[1][2]==e&&b.board[2][1]==e&&b.board[3][0]==0){tryToPlay(b, 3, 0); return true;}
    if(b.board[3][0]==e&&b.board[1][2]==e&&b.board[2][1]==e&&b.board[0][3]==0){tryToPlay(b, 0, 3); return true;}

    if(b.board[0][4]==e&&b.board[1][3]==e&&b.board[2][2]==e&&b.board[3][1]==0){tryToPlay(b, 3, 1); return true;}
    if(b.board[2][2]==e&&b.board[3][1]==e&&b.board[4][0]==e&&b.board[1][3]==0){tryToPlay(b, 1, 3); return true;}

    if(b.board[1][4]==e&&b.board[2][3]==e&&b.board[3][2]==e&&b.board[4][1]==0){tryToPlay(b, 4, 1); return true;}
    if(b.board[2][3]==e&&b.board[3][2]==e&&b.board[4][1]==e&&b.board[1][4]==0){tryToPlay(b, 1, 4); return true;}

    // square
    for(int i=0; i<=3; i++)
    {
      for(int j=0; j<=3; j++)
      {
        if(b.board[i][j]==e && b.board[i+1][j]==e && b.board[i][j+1]==e && b.board[i+1][j+1]==0)
        { tryToPlay(b, i+1, j+1); return true; }
        if(b.board[i][j]==e && b.board[i+1][j]==e && b.board[i+1][j+1]==e && b.board[i][j+1]==0)
        { tryToPlay(b, i, j+1); return true; }
        if(b.board[i+1][j+1]==e && b.board[i+1][j]==e && b.board[i][j+1]==e && b.board[i][j]==0)
        { tryToPlay(b, i, j); return true; }
        if(b.board[i][j]==e && b.board[i+1][j+1]==e && b.board[i][j+1]==e && b.board[i+1][j]==0)
        { tryToPlay(b, i+1, j); return true; }
      }
    }

    return false;
  }

  private static boolean make4(TTBoard b, int piece)
  {
    // horizontal
    for(int i=0; i<=4; i++)
     {
      if(b.board[i][0]==piece && b.board[i][1]==piece && b.board[i][2]==piece && b.board[i][3]==0)
       { tryToPlay(b, i, 3); return true; }

      if(b.board[i][2]==piece && b.board[i][3]==piece && b.board[i][4]==piece && b.board[i][1]==0)
       { tryToPlay(b, i, 1); return true; }
     }

    // vertical
    for(int j=0; j<=4; j++)
     {
      if(b.board[0][j]==piece && b.board[1][j]==piece && b.board[2][j]==piece && b.board[3][j]==0)
       { tryToPlay(b, 3, j); return true; }

      if(b.board[2][j]==piece && b.board[3][j]==piece && b.board[4][j]==piece && b.board[1][j]==0)
       { tryToPlay(b, 1, j); return true; }
     }

    // diagonal
    if(b.board[0][1]==piece&&b.board[1][2]==piece&&b.board[2][3]==piece&&b.board[3][4]==0)
     {tryToPlay(b, 3, 4); return true;}
    if(b.board[3][4]==piece&&b.board[1][2]==piece&&b.board[2][3]==piece&&b.board[0][1]==0)
     {tryToPlay(b, 0, 1); return true;}

    if(b.board[0][0]==piece&&b.board[1][1]==piece&&b.board[2][2]==piece&&b.board[3][3]==0)
     {tryToPlay(b, 3, 3); return true;}
    if(b.board[2][2]==piece&&b.board[3][3]==piece&&b.board[4][4]==piece&&b.board[1][1]==0)
     {tryToPlay(b, 1, 1); return true;}
    if(b.board[1][1]==piece&&b.board[2][2]==piece&&b.board[3][3]==piece)
     {
      if(b.board[0][0]==0) { tryToPlay(b, 0, 0); return true; }
      if(b.board[4][4]==0) { tryToPlay(b, 4, 4); return true; }
     }

    if(b.board[1][0]==piece&&b.board[2][1]==piece&&b.board[3][2]==piece&&b.board[4][3]==0)
     {tryToPlay(b, 4, 3); return true;}
    if(b.board[2][1]==piece&&b.board[3][2]==piece&&b.board[4][3]==piece&&b.board[1][0]==0)
     {tryToPlay(b, 1, 0); return true;}


    if(b.board[0][3]==piece&&b.board[1][2]==piece&&b.board[2][1]==piece&&b.board[3][0]==0)
     {tryToPlay(b, 3, 0); return true;}
    if(b.board[3][0]==piece&&b.board[1][2]==piece&&b.board[2][1]==piece&&b.board[0][3]==0)
     {tryToPlay(b, 0, 3); return true;}

    if(b.board[0][4]==piece&&b.board[1][3]==piece&&b.board[2][2]==piece&&b.board[3][1]==0)
     {tryToPlay(b, 3, 1); return true;}
    if(b.board[2][2]==piece&&b.board[3][1]==piece&&b.board[4][0]==piece&&b.board[1][3]==0)
     {tryToPlay(b, 1, 3); return true;}
    if(b.board[1][3]==piece&&b.board[2][2]==piece&&b.board[3][1]==piece)
     {
      if(b.board[0][4]==0) { tryToPlay(b, 0, 4); return true; }
      if(b.board[4][0]==0) { tryToPlay(b, 4, 0); return true; }
     }

    if(b.board[1][4]==piece&&b.board[2][3]==piece&&b.board[3][2]==piece&&b.board[4][1]==0)
     {tryToPlay(b, 4, 1); return true;}
    if(b.board[2][3]==piece&&b.board[3][2]==piece&&b.board[4][1]==piece&&b.board[1][4]==0)
     {tryToPlay(b, 1, 4); return true;}

    // square
    for(int i=0; i<=3; i++)
    {
     for(int j=0; j<=3; j++)
      {
       if(b.board[i][j]==piece&&b.board[i+1][j]==piece&&b.board[i][j+1]==piece&&b.board[i+1][j+1]==0)
        { tryToPlay(b, i+1, j+1); return true; }
       if(b.board[i][j]==piece&&b.board[i+1][j]==piece&&b.board[i+1][j+1]==piece&&b.board[i][j+1]==0)
        { tryToPlay(b, i, j+1); return true; }
       if(b.board[i+1][j+1]==piece&&b.board[i+1][j]==piece&&b.board[i][j+1]==piece&&b.board[i][j]==0)
        { tryToPlay(b, i, j); return true; }
       if(b.board[i][j]==piece&&b.board[i+1][j+1]==piece&&b.board[i][j+1]==piece&&b.board[i+1][j]==0)
        { tryToPlay(b, i+1, j); return true; }
      }
    }

    return false;
  }

  private static void playInACorner(TTBoard b)
  {
    if(b.board[1][1] != TTEngine.EMPTY &&
       b.board[1][3] != TTEngine.EMPTY &&
       b.board[3][1] != TTEngine.EMPTY &&
       b.board[3][3] != TTEngine.EMPTY)
      playInACross(b);

    while(true)
    {
      if(b.board[1][1] != TTEngine.EMPTY &&
         b.board[1][3] != TTEngine.EMPTY &&
         b.board[3][1] != TTEngine.EMPTY &&
         b.board[3][3] != TTEngine.EMPTY)
      {
        return;
      }

      Thread.yield();
      int i = getNextRandomInt(4);
      switch(i)
      {
        case 0 : { if(tryToPlay(b, 1, 1)) return; }
        case 1 : { if(tryToPlay(b, 1, 3)) return; }
        case 2 : { if(tryToPlay(b, 3, 1)) return; }
        case 3 : { if(tryToPlay(b, 3, 3)) return; }
      }
    }
  }

  private static void playInACross(TTBoard b)
  {
    if(b.board[1][2] != TTEngine.EMPTY &&
       b.board[2][3] != TTEngine.EMPTY &&
       b.board[3][2] != TTEngine.EMPTY &&
       b.board[2][1] != TTEngine.EMPTY)
      playInACorner(b);

    while(true)
    {
      if(b.board[1][2] != TTEngine.EMPTY &&
         b.board[2][3] != TTEngine.EMPTY &&
         b.board[3][2] != TTEngine.EMPTY &&
         b.board[2][1] != TTEngine.EMPTY)
      {
        return;
      }

      Thread.yield();
      int i = getNextRandomInt(4);
      switch(i)
      {
        case 0 : { if(tryToPlay(b, 1, 2)) return; }
        case 1 : { if(tryToPlay(b, 2, 3)) return; }
        case 2 : { if(tryToPlay(b, 3, 2)) return; }
        case 3 : { if(tryToPlay(b, 2, 1)) return; }
      }
    }
  }

  private static boolean tryToPlay(TTBoard b, int x, int y)
  {
    if(b.board[x][y] == TTEngine.EMPTY)
    {
       b.newX = x;
       b.newY = y;
       return true;
    }
    else
      return false;
  }

  private static final int getNextRandomInt(int bound)
  {
    return r.nextInt() % bound;
  }

  /*
  private static void playRandom(TTBoard b)
  {
    while(true)
    {
      int x = r.nextInt(4);
      int y = r.nextInt(4);
      if(tryToPlay(b, x, y))
         return;
    }
  }
  */
}
tt/TTStarter.java
package tt;

import java.applet.*;
import java.awt.*;

/**
 * TeekoTeacher
 *
 * Developed for the CAPS4 Conference paper:
 *
 * "TeekoTeacher: A Tool for Learning Good Teeko Strategies"
 * The Fourth International Conference on Human-System Learning
 * Glasgow, Scotland, UK, 2003.
 *
 * Copyright (c) 2003. Mihailo Despotovic, Redwood City, CA, USA [ mihailod@hotmail.com ]
 *
 * -------------------------------------------------------------------------------------------
 *
 * Developer's diary:
 *
 * Development started on Saturday, March 15. 2003.
 *
 * BETA 1 released on March 19. 2003.
 * - program is able to parse the database and advise both players
 * - Java 1.1 compliant version released on March 21. 2003.
 *
 * BETA 2 released on March 23. 2003. (TODO)
 * - fixed a nasty bug (position properties were not propagated, the first one was always suggested)
 * - selective advising
 * - draw detection
 * - work with player symmetric positions
 * - cumulative populating of same positions during the database parsing
 * - a little bit more detailed logging info
 *
 * Version 1.0 Released on June 8th 2003.
 * - the version to be shown on the CAPS4 confernece, Glasgow, UK, 2003.
 * - same as BETA 2 but with the security exception issue (while restarting the game) fixed
 */
public class TTStarter extends Applet
{
  public static final String VERSION = "1.0 (June 8th 2003)"; // tehnically, the build date should not go here...

  public static final String INSTRUCTIONS_FILE_NAME = "ttinstr.txt";
  public static final String DATABASE_FILE_NAME = "ttdb.txt";
  public static final int FLASH_DELAY = 400;

  /**
   * This class can be used as the main clas of an application
   */
  public static void main(String[] a)
  {
    initApplication();
  }

  /**
   * Also, this class can be used as the entry point to an applet
   */
  public void init()
  {
    initApplication();
  }

  /**
   * The code that actually starts the application
   */
  private static void initApplication()
  {
    new TTDatabase(DATABASE_FILE_NAME);
    new TTFrame();
  }
}