Skip to content

Instantly share code, notes, and snippets.

@xtpor
Last active February 6, 2017 08:31
Show Gist options
  • Save xtpor/10af6f62eb86cc20bcf27c3eb8976f3a to your computer and use it in GitHub Desktop.
Save xtpor/10af6f62eb86cc20bcf27c3eb8976f3a to your computer and use it in GitHub Desktop.
0
x..
.o.
...
-1
...
xo.
...
0
...
.o.
x..
-1
.x.
.o.
...
-1
...
.o.
.x.
0
..x
.o.
...
-1
...
.ox
...
0
...
.o.
..x
import java.util.List;
import java.util.ArrayList;
public class State {
// This class represent a valid game state
private char[] _state;
private char _player;
private char _winner;
public static String debug (State state) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<3; i++) {
for (int j=0; j<3; j++) {
char c = state.at(i + j * 3);
c = c == ' ' ? '.' : c;
sb.append(c);
}
sb.append("\n");
}
return sb.toString();
}
public static void verifyState (char[] state) {
// verify the state, throw an error if the state is invalid
ensure(state != null);
ensure(state.length == 9);
int oCount = count(state, 'o');
int xCount = count(state, 'x');
int spaceCount = count(state, ' ');
ensure(oCount == xCount || oCount == xCount + 1);
// assert that the state only contains 'o', 'x' and ' '
ensure(oCount + xCount + spaceCount == 9);
}
public static char oppsite (char player) {
return player == 'x' ? 'o' : 'x';
}
private static int count (char[] state, char target) {
// count how many 'target' inside the state
int count = 0;
for (char c : state) {
if (c == target) {
count += 1;
}
}
return count;
}
private static void ensure (boolean cond) {
if (!cond) {
throw new IllegalArgumentException();
}
}
public State (char[] state) {
State.verifyState(state);
_state = state.clone();
_player = computeCurrentPlayer();
_winner = computeWinnerPlayer();
}
public char at (int index) {
return _state[index];
}
public char player () {
return _player;
}
public char winner () {
return _winner;
}
public int rate (char optimizingPlayer) {
if (winner() == optimizingPlayer) {
return 1;
} else if (winner() == oppsite(optimizingPlayer)) {
return -1;
} else {
return 0;
}
}
public int minimax (char optimizingPlayer) {
if (winner() != ' ') {
return rate(optimizingPlayer);
} else {
if (player() == optimizingPlayer) {
int maxValue = Integer.MIN_VALUE;
for (int index : derivations()) {
int value = derive(index).minimax(optimizingPlayer);
maxValue = Math.max(maxValue, value);
}
return maxValue;
} else {
int minValue = Integer.MAX_VALUE;
for (int index : derivations()) {
int value = derive(index).minimax(optimizingPlayer);
minValue = Math.min(minValue, value);
}
return minValue;
}
}
}
public State derive (int index) {
ensure(at(index) == ' ');
ensure(winner() == ' ');
char[] nextState = _state.clone();
nextState [index] = player();
return new State(nextState);
}
public List<Integer> derivations () {
List<Integer> derivationIndexes = new ArrayList<Integer>();
if (winner() == ' ') {
for (int i=0; i<9; i++) {
if (at(i) == ' ') {
derivationIndexes.add(i);
}
}
}
return derivationIndexes;
}
private char computeCurrentPlayer () {
// assume the 'o' always moves first
if (count(_state, 'x') == count(_state, 'o')) {
return 'o';
} else {
return 'x';
}
}
private char computeWinnerPlayer () {
int[][] winningPatterns = {
new int[] {0, 1, 2},
new int[] {3, 4, 5},
new int[] {6, 7, 8},
new int[] {0, 3, 6},
new int[] {1, 4, 7},
new int[] {2, 5, 8},
new int[] {0, 4, 8},
new int[] {2, 4, 6},
};
for (int[] pat : winningPatterns) {
if (_state[pat[0]] == _state[pat[1]] &&
_state[pat[1]] == _state[pat[2]] &&
_state[pat[0]] != ' ') {
return _state[pat[0]];
}
}
if (count(_state, ' ') == 0) {
return 'd';
} else {
return ' ';
}
}
}
public class Test {
public static void main(String[] args) {
State state = new State(new char[] {
' ', ' ', ' ',
' ', 'o', ' ',
' ', ' ', ' ',
});
for (int index : state.derivations()) {
State derived = state.derive(index);
System.out.println(derived.minimax('x'));
System.out.println(State.debug(derived));
}
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class TicTacToe extends JFrame {
private ArrayList<JButton> btns;
private State state;
private boolean AIMove;
public static void main(String[] args) {
new TicTacToe();
}
public TicTacToe () {
initializeUI();
resetState();
render();
}
private void initializeUI () {
this.setLayout(new GridLayout(3, 3));
this.btns = new ArrayList<JButton>();
for (int i=0; i<9; i++) {
JButton btn = new JButton();
this.btns.add(btn);
this.add(btn);
btn.addActionListener(new ClickHandler(i, this));
}
this.pack();
this.setVisible(true);
}
private void render () {
for (int i=0; i<9; i++) {
btns.get(i).setText(String.valueOf(state.at(i)));
}
}
public void onpress (int i) {
if (state.at(i) != ' ') return;
state = state.derive(i);
render();
if (state.winner() != ' ') {
message(state.winner() == 'x' ? "x win" :
state.winner() == 'o' ? "o win" :
"draw");
resetState();
render();
return;
}
// AI move
state = state.derive(computeOptimizeMove());
render();
if (state.winner() != ' ') {
message(state.winner() == 'x' ? "x win" :
state.winner() == 'o' ? "o win" :
"draw");
resetState();
render();
return;
}
}
public int computeOptimizeMove () {
int optimalIndex = 0;
int maximumValue = Integer.MIN_VALUE;
for (int index : state.derivations()) {
int value = state.derive(index).minimax(state.player());
if (value >= maximumValue) {
optimalIndex = index;
maximumValue = value;
}
}
return optimalIndex;
}
public void resetState () {
state = new State(new char[] {
' ', ' ', ' ',
' ', ' ', ' ',
' ', ' ', ' ',
});
}
public static void message (String content) {
JOptionPane.showMessageDialog(null, content, "gameover", JOptionPane.INFORMATION_MESSAGE);
}
}
class ClickHandler implements ActionListener {
private int i;
private TicTacToe game;
public ClickHandler (int i, TicTacToe game) {
this.i = i;
this.game = game;
}
public void actionPerformed(ActionEvent e) {
this.game.onpress(i);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment