Last active
February 6, 2017 08:31
-
-
Save xtpor/10af6f62eb86cc20bcf27c3eb8976f3a to your computer and use it in GitHub Desktop.
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
0 | |
x.. | |
.o. | |
... | |
-1 | |
... | |
xo. | |
... | |
0 | |
... | |
.o. | |
x.. | |
-1 | |
.x. | |
.o. | |
... | |
-1 | |
... | |
.o. | |
.x. | |
0 | |
..x | |
.o. | |
... | |
-1 | |
... | |
.ox | |
... | |
0 | |
... | |
.o. | |
..x | |
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
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 ' '; | |
} | |
} | |
} |
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
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)); | |
} | |
} | |
} |
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
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