Skip to content

Commit 90248f6

Browse files
committed
Add Minimax tic-tac-toe algorithm
1 parent 9b923e9 commit 90248f6

File tree

4 files changed

+253
-4
lines changed

4 files changed

+253
-4
lines changed

src/main/java/org/sean/array/TicTacToe.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public void update(int r, int c) {
108108
printBoard();
109109
}
110110

111-
private static int[] retrieveInputRowCol(Scanner scanner) {
111+
public static int[] retrieveInputRowCol(Scanner scanner) {
112112
String row = scanner.next();
113113
String col = scanner.next();
114114
int r;
@@ -172,7 +172,7 @@ public static void main(String[] args) throws IOException {
172172
}
173173
}
174174

175-
private static Map<String, String> applyTranslations(String[] args) throws FileNotFoundException {
175+
public static Map<String, String> applyTranslations(String[] args) throws FileNotFoundException {
176176
String fileName = "zh_cn.json";
177177
if (args.length > 0) {
178178
String language = args[0];
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package org.sean.array;
2+
3+
import java.io.FileNotFoundException;
4+
import java.util.Arrays;
5+
import java.util.Map;
6+
import java.util.Scanner;
7+
8+
import static java.lang.System.exit;
9+
10+
public class TicTacToeAuto {
11+
public static final int COMP_LOSS = -1;
12+
public static final int DRAW = 0;
13+
public static final int COMP_WIN = 1;
14+
15+
public static final int COMP = 0x1;
16+
public static final int HUMAN = 0x2;
17+
private final char[] board;
18+
private static final char chessEmpty = '-';
19+
private static final char chessComp = 'O';
20+
private static final char chessHuman = 'X';
21+
22+
// all the possible winning lines
23+
private static final int[][] lineIdxTable =
24+
new int[][] {
25+
{0, 1, 2}, // horizontal lines
26+
{3, 4, 5},
27+
{6, 7, 8},
28+
{0, 3, 6}, // vertical lines
29+
{1, 4, 7},
30+
{2, 5, 8},
31+
{0, 4, 8}, // cross
32+
{2, 4, 6},
33+
};
34+
35+
public static class MoveInfo {
36+
public int move;
37+
public int value;
38+
39+
public MoveInfo(int m, int v) {
40+
move = m;
41+
value = v;
42+
}
43+
44+
@Override
45+
public String toString() {
46+
return "MoveInfo{" + "move=" + move + ", value=" + value + '}';
47+
}
48+
}
49+
50+
public TicTacToeAuto() {
51+
board = new char[9];
52+
Arrays.fill(board, chessEmpty);
53+
}
54+
55+
private void place(int pos, int player) {
56+
update(pos, player == HUMAN ? chessHuman : chessComp);
57+
}
58+
59+
void printBoard() {
60+
for (int i = 1; i <= board.length; i++) {
61+
System.out.printf("%c ", board[i - 1]);
62+
if (i % 3 == 0) System.out.println();
63+
}
64+
}
65+
66+
private void unplace(int pos) {
67+
update(pos, chessEmpty);
68+
}
69+
70+
private void update(int pos, char chess) {
71+
board[pos] = chess;
72+
}
73+
74+
private boolean hasWon(char chess) {
75+
int oCnt, xCnt;
76+
for (int[] line : lineIdxTable) {
77+
xCnt = oCnt = 0;
78+
79+
for (int coord : line) {
80+
if (chessComp == board[coord]) {
81+
oCnt++;
82+
} else if (chessHuman == board[coord]) {
83+
xCnt++;
84+
}
85+
}
86+
if (oCnt == 3 && chess == chessComp || xCnt == 3 && chess == chessHuman) {
87+
return true;
88+
}
89+
}
90+
return false;
91+
}
92+
93+
private MoveInfo immediateWin(char chess) {
94+
int oCnt, xCnt;
95+
int nextPos;
96+
for (int[] line : lineIdxTable) {
97+
nextPos = xCnt = oCnt = 0;
98+
99+
for (int coord : line) {
100+
if (chessComp == board[coord]) {
101+
oCnt++;
102+
} else if (chessHuman == board[coord]) {
103+
xCnt++;
104+
} else {
105+
nextPos = coord;
106+
}
107+
}
108+
109+
// search for the empty terminal position for current player
110+
if (chess == chessComp) {
111+
if (oCnt == 2 && xCnt == 0) return new MoveInfo(nextPos, COMP_WIN);
112+
} else {
113+
if (xCnt == 2 && oCnt == 0) return new MoveInfo(nextPos, COMP_LOSS);
114+
}
115+
}
116+
117+
return null;
118+
}
119+
120+
private boolean fullBoard() {
121+
for (int i = 0; i < 3; i++) {
122+
for (int j = 0; j < 3; j++) {
123+
if (board[i * 3 + j] == chessEmpty) return false;
124+
}
125+
}
126+
return true;
127+
}
128+
129+
private boolean isEmpty(int pos) {
130+
return board[pos] == chessEmpty;
131+
}
132+
133+
/***
134+
* Recursive method to find best move for computer.
135+
* MoveInfo.move returns a number from 1-9 indicating square.
136+
* Possible evaluations satisfy COMP_LOSS < DRAW < COMP_WIN.
137+
*
138+
* @return The next {@link MoveInfo} for computer
139+
*/
140+
public MoveInfo findCompMove() {
141+
int i, responseValue;
142+
int value, bestMove = 1;
143+
MoveInfo quickWinInfo;
144+
145+
if (fullBoard()) value = DRAW;
146+
else if ((quickWinInfo = immediateWin(chessComp)) != null) return quickWinInfo;
147+
else {
148+
value = COMP_LOSS;
149+
for (i = 0; i < 9; i++) {
150+
if (isEmpty(i)) {
151+
place(i, COMP);
152+
responseValue = findHumanMove().value;
153+
unplace(i); // Restore board
154+
155+
if (responseValue > value) {
156+
value = responseValue;
157+
bestMove = i;
158+
}
159+
}
160+
}
161+
}
162+
163+
return new MoveInfo(bestMove, value);
164+
}
165+
166+
private MoveInfo findHumanMove() {
167+
int i, responseValue;
168+
int value, bestMove = 1;
169+
MoveInfo quickWinInfo;
170+
171+
if (fullBoard()) value = DRAW;
172+
else if ((quickWinInfo = immediateWin(chessHuman)) != null) return quickWinInfo;
173+
else {
174+
value = COMP_WIN;
175+
for (i = 0; i < 9; i++) {
176+
if (isEmpty(i)) {
177+
place(i, HUMAN);
178+
responseValue = findCompMove().value;
179+
unplace(i);
180+
181+
if (responseValue < value) {
182+
// Update best move
183+
value = responseValue;
184+
bestMove = i;
185+
}
186+
}
187+
}
188+
}
189+
return new MoveInfo(bestMove, value);
190+
}
191+
192+
public static void main(String[] args) throws FileNotFoundException {
193+
Map<String, String> trans = TicTacToe.applyTranslations(args);
194+
195+
Scanner scanner = new Scanner(System.in);
196+
System.out.println(trans.get("welcome_challenge"));
197+
int order = scanner.nextInt();
198+
boolean isCompTurn = order == 2;
199+
200+
TicTacToeAuto auto = new TicTacToeAuto();
201+
auto.printBoard();
202+
203+
for (int i = 0; !(auto.fullBoard()); i++) {
204+
if (isCompTurn) {
205+
MoveInfo compMove = auto.findCompMove();
206+
int cord = compMove.move;
207+
System.out.printf(
208+
trans.get("computer_placed") + "[%d, %d]%n", cord / 3 + 1, cord % 3 + 1);
209+
210+
auto.place(compMove.move, COMP);
211+
auto.printBoard();
212+
if (auto.hasWon(chessComp)) {
213+
System.out.println(trans.get("computer_win"));
214+
exit(0);
215+
}
216+
} else {
217+
System.out.println(trans.get("input_now"));
218+
int[] loc;
219+
do {
220+
loc = TicTacToe.retrieveInputRowCol(scanner);
221+
} while (loc[0] > 3
222+
|| loc[0] < 1
223+
|| loc[1] > 3
224+
|| loc[1] < 1
225+
|| !auto.isEmpty(3 * (loc[0] - 1) + loc[1] - 1));
226+
227+
System.out.printf(trans.get("your_input"), loc[0], loc[1]);
228+
auto.place(3 * (loc[0] - 1) + loc[1] - 1, HUMAN);
229+
auto.printBoard();
230+
if (auto.hasWon(chessHuman)) {
231+
System.out.println(trans.get("you_win"));
232+
exit(0);
233+
}
234+
}
235+
isCompTurn = !isCompTurn;
236+
}
237+
System.out.println(trans.get("result_draw"));
238+
}
239+
}

src/main/resources/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@
55
"cell_occupied": "The entered position is occupied",
66
"your_input": "The location you entered is : (Row %d, Column %d)\n",
77
"result_winner": "\uD83C\uDFAE Game over,✌️ The winner is player %c",
8-
"result_draw": "\uD83C\uDFAE Game over,both sides played to a draw"
8+
"result_draw": "\uD83C\uDFAE Game over,both sides played to a draw",
9+
"welcome_challenge": "\uD83D\uDC4F Welcome to Tactic Challenge Games \uD83C\uDFAE,Please enter the player who started first (1 for Human, 2 for Computer) :",
10+
"computer_placed": "Computer placed : ",
11+
"input_now": "It's your turn, please input the row (1-3) and column (1-3) :",
12+
"you_win": "\uD83C\uDFAE You win!",
13+
"computer_win": "\uD83C\uDFAE Computer wins!"
914
}

src/main/resources/zh_cn.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@
55
"cell_occupied": "输入的位置已被占用,不能再放置其它棋子",
66
"your_input": "您输入的是 : (%d 排, %d 列)\n",
77
"result_winner": "\uD83C\uDFAE 游戏结束,✌️ 获胜方是️ %c",
8-
"result_draw": "\uD83C\uDFAE 游戏结束,双方打成平局"
8+
"result_draw": "\uD83C\uDFAE 游戏结束,双方打成平局",
9+
"welcome_challenge": "\uD83D\uDC4F 欢迎来到 Tactic 挑战游戏 \uD83C\uDFAE,请输入先开始的玩家 (1 人类选手/ 2 电脑) :",
10+
"computer_placed": "电脑已经下了棋子: ",
11+
"input_now": "轮到您了,请输入行(1-3)和列(1-3):",
12+
"you_win": "\uD83C\uDFAE 您赢了!",
13+
"computer_win": "\uD83C\uDFAE 电脑赢了!"
914
}

0 commit comments

Comments
 (0)