Last active
November 6, 2015 21:23
-
-
Save rhulha/969fa63f511c69622d39 to your computer and use it in GitHub Desktop.
Original starting code base from www3.ntu.edu.sg/home/ehchua/programming/java/javagame_tictactoe_ai.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.raysforge.visual.demo; | |
import java.awt.Color; | |
import java.awt.Container; | |
import java.awt.Font; | |
import java.awt.GridLayout; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.util.ArrayList; | |
import java.util.List; | |
import javax.swing.JButton; | |
import javax.swing.JFrame; | |
public class TicTacToeOld extends JButton implements ActionListener { | |
final static TicTacToeOld[][] cells = new TicTacToeOld[3][3]; | |
final static Font font = new Font("Comic Sans MS", Font.BOLD, 100); | |
final static int EMPTY = 0; | |
final static int computer = 1; | |
final static int human = -1; | |
private int content; | |
public TicTacToeOld() { | |
addActionListener(this); | |
setFocusPainted(false); | |
setFont(font); | |
} | |
public static void main(String[] args) { | |
JFrame f = new JFrame("TicTacToe"); | |
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); | |
f.setSize(640, 640); | |
Container cp = f.getContentPane(); | |
cp.setLayout(new GridLayout(3, 3)); | |
for (int x = 0; x < 3; x++) { | |
for (int y = 0; y < 3; y++) { | |
cp.add(cells[x][y] = new TicTacToeOld()); | |
} | |
} | |
f.setLocationRelativeTo(null); | |
f.setVisible(true); | |
} | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
if (getText().length() > 0) | |
return; | |
setText("X"); | |
setForeground(Color.GREEN); | |
content = human; | |
int[] result = minimax(4, computer); | |
if (result[1] == -1) | |
return; | |
cells[result[1]][result[2]].setText("O"); | |
cells[result[1]][result[2]].content = computer; | |
cells[result[1]][result[2]].setForeground(Color.RED); | |
} | |
/** Recursive minimax at level of depth for either maximizing or minimizing player. | |
Return int[3] of {score, row, col} */ | |
private int[] minimax(int depth, int player) { | |
// Generate possible next moves in a List of int[2] of {row, col}. | |
List<int[]> nextMoves = generateMoves(); | |
// mySeed is maximizing; while oppSeed is minimizing | |
int bestScore = (player == computer) ? Integer.MIN_VALUE : Integer.MAX_VALUE; | |
int currentScore; | |
int bestRow = -1; | |
int bestCol = -1; | |
if (nextMoves.isEmpty() || depth == 0) { | |
// Gameover or depth reached, evaluate score | |
bestScore = evaluate(); | |
} else { | |
for (int[] move : nextMoves) { | |
// Try this move for the current "player" | |
cells[move[0]][move[1]].content = player; | |
if (player == computer) { // mySeed (computer) is maximizing player | |
currentScore = minimax(depth - 1, human)[0]; | |
if (currentScore > bestScore) { | |
bestScore = currentScore; | |
bestRow = move[0]; | |
bestCol = move[1]; | |
} | |
} else { // oppSeed is minimizing player | |
currentScore = minimax(depth - 1, computer)[0]; | |
if (currentScore < bestScore) { | |
bestScore = currentScore; | |
bestRow = move[0]; | |
bestCol = move[1]; | |
} | |
} | |
// Undo move | |
cells[move[0]][move[1]].content = EMPTY; | |
} | |
} | |
return new int[] { bestScore, bestRow, bestCol }; | |
} | |
/** Find all valid next moves. | |
Return List of moves in int[2] of {row, col} or empty list if gameover */ | |
private List<int[]> generateMoves() { | |
List<int[]> nextMoves = new ArrayList<int[]>(); // allocate List | |
// If gameover, i.e., no next move | |
if (hasWon(computer) || hasWon(human)) { | |
return nextMoves; // return empty list | |
} | |
// Search for empty cells and add to the List | |
for (int row = 0; row < 3; ++row) { | |
for (int col = 0; col < 3; ++col) { | |
if (cells[row][col].content == EMPTY) { | |
nextMoves.add(new int[] { row, col }); | |
} | |
} | |
} | |
return nextMoves; | |
} | |
/** The heuristic evaluation function for the current board | |
@Return +100, +10, +1 for EACH 3-, 2-, 1-in-a-line for computer. | |
-100, -10, -1 for EACH 3-, 2-, 1-in-a-line for opponent. | |
0 otherwise */ | |
private int evaluate() { | |
int score = 0; | |
// Evaluate score for each of the 8 lines (3 rows, 3 columns, 2 diagonals) | |
score += evaluateLine(0, 0, 0, 1, 0, 2); // row 0 | |
score += evaluateLine(1, 0, 1, 1, 1, 2); // row 1 | |
score += evaluateLine(2, 0, 2, 1, 2, 2); // row 2 | |
score += evaluateLine(0, 0, 1, 0, 2, 0); // col 0 | |
score += evaluateLine(0, 1, 1, 1, 2, 1); // col 1 | |
score += evaluateLine(0, 2, 1, 2, 2, 2); // col 2 | |
score += evaluateLine(0, 0, 1, 1, 2, 2); // diagonal | |
score += evaluateLine(0, 2, 1, 1, 2, 0); // alternate diagonal | |
return score; | |
} | |
/** The heuristic evaluation function for the given line of 3 cells | |
@Return +100, +10, +1 for 3-, 2-, 1-in-a-line for computer. | |
-100, -10, -1 for 3-, 2-, 1-in-a-line for opponent. | |
0 otherwise */ | |
private int evaluateLine(int row1, int col1, int row2, int col2, int row3, int col3) { | |
int score = 0; | |
// First cell | |
if (cells[row1][col1].content == computer) { | |
score = 1; | |
} else if (cells[row1][col1].content == human) { | |
score = -1; | |
} | |
// Second cell | |
if (cells[row2][col2].content == computer) { | |
if (score == 1) { // cell1 is mySeed | |
score = 10; | |
} else if (score == -1) { // cell1 is oppSeed | |
return 0; | |
} else { // cell1 is empty | |
score = 1; | |
} | |
} else if (cells[row2][col2].content == human) { | |
if (score == -1) { // cell1 is oppSeed | |
score = -10; | |
} else if (score == 1) { // cell1 is mySeed | |
return 0; | |
} else { // cell1 is empty | |
score = -1; | |
} | |
} | |
// Third cell | |
if (cells[row3][col3].content == computer) { | |
if (score > 0) { // cell1 and/or cell2 is mySeed | |
score *= 10; | |
} else if (score < 0) { // cell1 and/or cell2 is oppSeed | |
return 0; | |
} else { // cell1 and cell2 are empty | |
score = 1; | |
} | |
} else if (cells[row3][col3].content == human) { | |
if (score < 0) { // cell1 and/or cell2 is oppSeed | |
score *= 10; | |
} else if (score > 1) { // cell1 and/or cell2 is mySeed | |
return 0; | |
} else { // cell1 and cell2 are empty | |
score = -1; | |
} | |
} | |
return score; | |
} | |
private int[] winningPatterns = { 0b111000000, 0b000111000, 0b000000111, // rows | |
0b100100100, 0b010010010, 0b001001001, // cols | |
0b100010001, 0b001010100 // diagonals | |
}; | |
/** Returns true if thePlayer wins */ | |
private boolean hasWon(int thePlayer) { | |
int pattern = 0b000000000; // 9-bit pattern for the 9 cells | |
for (int row = 0; row < 3; ++row) { | |
for (int col = 0; col < 3; ++col) { | |
if (cells[row][col].content == thePlayer) { | |
pattern |= (1 << (row * 3 + col)); | |
} | |
} | |
} | |
for (int winningPattern : winningPatterns) { | |
if ((pattern & winningPattern) == winningPattern) | |
return true; | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment