Saturday, May 11, 2013

An Implementation of the Tic-Tac-Toe Artificial Intelligence in Java


This is my Java implementation of the Tic-Tac-Toe artificial intelligence. The first player, O, is the user, and the second player, X, is the computer.

The algorithm is simple and effective but may not be entirely perfect. The heuristic rules are simply grounded on those basic strategies I put together for playing Tic-Tac-Toe games and presented in the private int computeHeuristicScoreAt(int position) method in ArtificialIntelligence.java below.

You can download the Java source codes in http://sites.google.com/site/moderntone/TicTacToe.zip and the executable jar file along with the two images in http://sites.google.com/site/moderntone/TicTacToeExecutableJar.zip









ArtificialIntelligence.java

package com.tictactoe;

import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class ArtificialIntelligence extends MouseAdapter{
 
 /** Indexes of the nine positions:
  * 0 1 2
  * 3 4 5
  * 6 7 8 
  */
 private int[] situation; // length = 9; 0: empty; 1: O; 2: X private JTable table;
 private Integer[] heuristicScoresAndPositions;
 
 public ArtificialIntelligence(JTable table){
  this.table = table;
  situation = new int[9];
  
 }
 
 private static final HashMap<Integer, List<Integer>> linesMap;
 
 static {
  linesMap = new HashMap<Integer, List<Integer>>();
  
  List<Integer> lines = new ArrayList<Integer>();
  lines.add((1 << 4) + 2); lines.add((3 << 4) + 6);
  lines.add((4 << 4) + 8); 
linesMap.put(0, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((0 << 4) + 2); lines.add((4 << 4) + 7);
  linesMap.put(1, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((0 << 4) + 1); lines.add((4 << 4) + 6);
  lines.add((5 << 4) + 8);
  linesMap.put(2, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((0 << 4) + 6); lines.add((4 << 4) + 5);
  linesMap.put(3, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((0 << 4) + 8); lines.add((2 << 4) + 6);
  lines.add((1 << 4) + 7); lines.add((3 << 4) + 5);
  linesMap.put(4, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((2 << 4) + 8); lines.add((3 << 4) + 4);
  linesMap.put(5, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((0 << 4) + 3); lines.add((2 << 4) + 4);
  lines.add((7 << 4) + 8);
  linesMap.put(6, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((1 << 4) + 4); lines.add((6 << 4) + 8);
  linesMap.put(7, lines);
  
  lines = new ArrayList<Integer>();
  lines.add((0 << 4) + 4); lines.add((2 << 4) + 5);
  lines.add((6 << 4) + 7);
  linesMap.put(8, lines);
 }
 
 private boolean checkWhetherTheCurrentPlayerWins(int position, boolean byUser){
  List<Integer> possibleLines = linesMap.get(position);
  for (Integer anotherTwoPositions : possibleLines){
   int p1 = anotherTwoPositions >> 4, p2 = anotherTwoPositions & 0xf;
   if (byUser){
    if (situation[p1] * situation[p2] == 1) 
     return true;
   }
   else 
    if (situation[p1] + situation[p2] == 4) 
    return true;
  }
  return false;
 }
 
 @Override
 public void mousePressed(MouseEvent e) {
  boolean notEnded = playByUser(e);
  try {
   Thread.sleep(100);
  } catch (InterruptedException e1) {
   e1.printStackTrace();
  }
  if (notEnded)
   playByComputer();
 }

 private void computeHeuristicScores(){
  heuristicScoresAndPositions = new Integer[9]; //score << 8 + row << 4 + column 
  for (int i = 0 ; i < 9 ; i++){
   heuristicScoresAndPositions[i] = (situation[i] > 0) ? 
     i : computeHeuristicScoreAt(i) + i; 
  }
 }
 
 private int computeHeuristicScoreAt(int position){
  List<Integer> possibleLines = linesMap.get(position);
  
  int h = 0;
  for (Integer line : possibleLines){
   int p1 = line >> 4, p2 = line & 0xf;
   int zeroCount = 0, oneCount = 0, twoCount = 0;
   
   switch (situation[p1]) {
   case 0:  zeroCount++; break;
   case 1:  oneCount++; break;
   default: twoCount++; break;
   }
   switch (situation[p2]) {
   case 0:  zeroCount++; break;
   case 1:  oneCount++; break;
   default: twoCount++; break;
   }
   
   if (twoCount == 2)
    return 1 << 20;
   else if (oneCount == 2)
    h += 1 << 16;
   else {
    if (zeroCount == 1 && twoCount == 1)
     h += 1 << 12;
    else if ( zeroCount * oneCount == 1)
     h += 1 << 10;
    else if (zeroCount == 2)
     h += 1 << 9;
    else {
     h += 1 << 6;
    }
   }
  }
  return h;
 }
 
 private void playByComputer(){
  computeHeuristicScores();
  Arrays.sort(heuristicScoresAndPositions);
  int thePosition = heuristicScoresAndPositions[8] & 0xf;
  if (situation[thePosition] > 0){
   JOptionPane.showMessageDialog(null, "This game is ended in a draw.");
   restart();
   return;
  }
  situation[thePosition] = 2;
  ImageIcon OorXIcon = new ImageIcon("images/x.png");
  Image img = OorXIcon.getImage();
  Image newImg = img.getScaledInstance(90, 90,
    java.awt.Image.SCALE_SMOOTH);
  DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
  tableModel.setValueAt(new ImageIcon(newImg), thePosition / 3, thePosition % 3);
  if (checkWhetherTheCurrentPlayerWins(thePosition, false)){
   JOptionPane.showMessageDialog(null, "Congratuations! Player X Wins.");
   restart();
   return;
  }
 }
 
 private boolean playByUser(MouseEvent e){
  int column = table.columnAtPoint(e.getPoint());
  int row = table.rowAtPoint(e.getPoint());
  if (table.getValueAt(row, column) != null)
   return false;
  int position = row * 3 + column;
  situation[position] = 1;
  ImageIcon OorXIcon = new ImageIcon("images/o.png");
  Image img = OorXIcon.getImage();
  Image newImg = img.getScaledInstance(90, 90,
    java.awt.Image.SCALE_SMOOTH);
  DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
  tableModel.setValueAt(new ImageIcon(newImg), row, column);
  if (checkWhetherTheCurrentPlayerWins(position, true)){
   JOptionPane.showMessageDialog(null, "Congratuations! Player O Wins.");
   restart();
   return false;
  }
  return true;
 }
 
 public void restart(){
  for (int i = 0; i < 3; i++) {
   for (int j = 0; j < 3; j++)
    table.setValueAt(null, i, j);
  }
  situation = new int[9];
 }
}



ImageRenderer.java


package com.tictactoe;

import java.awt.Component;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

public class ImageRenderer extends DefaultTableCellRenderer {
 
 private static final long serialVersionUID = 1L;
 JLabel lbl = new JLabel();
 public Component getTableCellRendererComponent(JTable table,
   Object value, boolean isSelected, boolean hasFocus, int row,
   int column) {
  lbl.setIcon((ImageIcon) value);
  return lbl;
 }
}

TictacToe.java

package com.tictactoe;

import javax.swing.*;
import java.awt.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.table.DefaultTableModel;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class TicTacToe extends JFrame {

 private static final long serialVersionUID = 1L;
 private JPanel contentPane;
 private static JTable table;
 private ArtificialIntelligence ai;
 
 public static void main(String[] args) {
  EventQueue.invokeLater(new Runnable() {
   public void run() {
    try {
     TicTacToe frame = new TicTacToe();
     frame.setVisible(true);
    } catch (Exception e) {
     e.printStackTrace();
    }
   }
  });
 }

 private void setTable() {
  final Object[][] tableItems = new Object[][] { { null, null, null },
    { null, null, null }, { null, null, null }, };
  table = new JTable();
  table.setGridColor(new Color(255, 0, 0));

  ai = new ArtificialIntelligence(table);
  table.addMouseListener(ai);
  table.setModel(new DefaultTableModel(tableItems, new String[] { "0",
    "1", "2" }) {
   private static final long serialVersionUID = 1L;
   @Override
   public boolean isCellEditable(int row, int column) {
    return false;
   }
  });

  table.setBackground(new Color(153, 255, 255));
  table.setBorder(new EtchedBorder(EtchedBorder.RAISED, new Color(107,
    142, 35), null));
  table.setBounds(89, 70, 270, 270);
  for (int i = 0; i < 3; i++) {
   table.setRowHeight(i, 90);
   table.getColumnModel().getColumn(i)
     .setCellRenderer(new ImageRenderer());
  }
 }

 public TicTacToe() {
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  setBounds(100, 100, 450, 470);
  setTitle("Tic-Tac-Toe");
  setResizable(false);
  contentPane = new JPanel();
  contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
  contentPane.setLayout(null);
  setContentPane(contentPane);

  setTable();
  contentPane.add(table);

  JButton btnNewButton = new JButton("Restart");
  btnNewButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
    ai.restart();
   }
  });
  btnNewButton.setFont(new Font("SansSerif", Font.BOLD, 16));
  btnNewButton.setBounds(180, 380, 90, 25);
  contentPane.add(btnNewButton);
 }
}

1 comment: