TicTacToeAI with Minimax Algorithm

huangapple go评论73阅读模式
英文:

TicTacToeAI with Minimax Algorithm

问题

我已经实现了井字游戏的Minimax算法,参考自GeeksForGeeks。我知道Minimax算法是如何工作的,但是我的代码似乎不按照它的方式工作。我已经反复检查可能出错的地方,也进行了调试。但似乎我找不到问题所在。

请查看算法,非常感谢额外的一双眼睛,以找到我找不到的错误部分。

我已经对代码的每个部分进行了注释,有助于理解。请帮助我解决问题。

谢谢。

import java.util.Arrays;
import java.util.Scanner;

// ... 省略了一些代码 ...

// 检查游戏结果,显示胜利者或平局。
public boolean displayResult() {
    int valR = checkRows();
    int valC = checkCols();
    int diag = checkDiag();
    if (diag == 1 || diag == 3 || valR == 3 || valC == 3) {
        System.out.println("X wins");
        return true;
    } else if (diag ==  2 || diag == 4 || valR == 2 || valC == 2) {
        System.out.println("O wins");
        return true;
    } else if ((diag == 0 || valC == 1 || valR == 1) && checkForSpace()) {
        System.out.println("Draw");
        return true;
    }
    return false;
}

// ... 其他部分的代码 ...

蓝色箭头指示发生错误的地方。
绿色标记说明应该是正确的方式。
TicTacToeAI with Minimax Algorithm

控制台输出:

Input command: start user ai
---------
|       |
|       |
|       |
---------
Enter the coordinates (According to Matrix, Space separated integers): 1 1
---------
|       |
|   X   |
|       |
---------
Making move level "AI"
---------
| O     |
|   X   |
|       |
---------
Enter the coordinates (According to Matrix, Space separated integers): 0 2
---------
| O   X |
|   X   |
|       |
---------
Making move level "AI"
---------
| O O X |    <-- 说明: AI 在 [0, 1] 位置上进行了移动,但应该在 [2, 0] 上进行移动,
|   X   |                    这将阻止我在右对角线上获胜。
|       |
---------
Enter the coordinates (According to Matrix, Space separated integers): 2 0
---------
| O O X |
|   X   |
| X     |
---------
X wins

希望这对你有所帮助。如果你有任何其他问题,请随时提问。

英文:

I have implemented Minimax Algorithm for Tic Tac Toe Game from GeeksForGeeks. I know how the Minimax Algorithm works but my code here doesn't work according to it. I have checked and checked for the things I might be doing wrong and also have debugged it. But it seems, I am not able to find it.

Please look into the algorithm, it would be much thankful for extra set of eyes and to find the incorrect part which I can't seem to find.

I have commented every part of the code that is helpful with Minimax Algorithm. I think it would be easy to catch up.

Please help me out here.

Thank you.

import java.util.Arrays;
import java.util.Scanner;
class Move {
// row index
protected int row;
// column index
protected int col;
// exp is 'Our Expression'.
protected char exp;
// oppExp is 'Opponent's Expression'
protected char oppExp;
}
class TicTacToe {
private final char[][] arr = new char[3][3];
// Move - to keep track of rowIndex and columnIndex
// from the minimax algorithm. And to keep track of
// 'X'/'O', who is our opponent for which the algorithm
// should find moves for. 
private final Move moves = new Move();
TicTacToe() {
// Initialising field
for (int i = 0; i < 3; i++) {
Arrays.fill(this.arr[i], ' ');
}
}
public String[] whosePlaying(Scanner sc) {
while (true) {
System.out.print("Input command: ");
// Getting input for players vs players
String str = sc.nextLine();
if (str.startsWith("start")) {
String[] players = str.replaceAll("start", "").trim().split(" ");
if (players.length == 2) {
if ((players[0].equals("user") || players[0].equals("ai")) &&
(players[1].equals("user") || players[1].equals("ai"))) {
return players;
}
}
}
System.out.println("Bad parameters!");
}
}
public void playUser (Scanner sc, char exp) {   // exp is expression('X' / 'O') for user
// x is RowIndex
// y is ColumnIndex
// To get from the user
int x, y;
System.out.print("Enter the coordinates (According to Matrix, Space separated integers): ");
while (true) {
// try - catch for input that might not be number
try {
sc = new Scanner(System.in);
x = sc.nextInt();
y = sc.nextInt();
break;
} catch (Exception e) {
System.out.println("You should enter numbers!");
playUser(sc, exp);  // Ask again for coordinates
}
}
if (x > 2 || y > 2 || x < 0 || y < 0) {
System.out.println("Coordinates should be from 0 to 2!");
playUser(sc, exp);  // Ask again for coordinates
} else {
// Check for availability.
if (this.arr[x][y] == ' ') {
this.arr[x][y] = exp;
displayField(); // Displaying TicTacToe field after user's turn.
} else {
System.out.println("This cell is occupied! Choose another one!");
playUser(sc, exp);  // Ask again for coordinates
}
}
}
public void playComputer (char exp) {
System.out.println("Making move level \"AI\"");
// Setting our expresssion that is X / O.
moves.exp = exp;
// Finding opponents expresssion that is X / O.
if (moves.exp == 'X') moves.oppExp = 'O';
else moves.oppExp = 'X';
// Searching for the best move.
searchBestMove();
// Setting the best coordinates from the minimax algorithm
// into the field with our expresssion.
this.arr[moves.row][moves.col] = moves.exp;
displayField(); // Displaying TicTacToe field after AI's turn.
}
// Start of Minimax Algorithm -  Contains all methods needed for the algorithm
private void searchBestMove() {
int bestVal = Integer.MIN_VALUE;
moves.row = -1;
moves.col = -1;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (this.arr[i][j] == ' ') {
this.arr[i][j] = moves.exp;
int moveVal = minimax(0, false);
this.arr[i][j] = ' ';
if (moveVal > bestVal) {
moves.row = i;
moves.col = j;
bestVal = moveVal;
}
}
}
}
}
private int minimax(int depth, boolean isMax) {
int score = checkGetScore();
if (score == 10 || score == -10) return score;
if (!checkForSpace()) return 0;
int best;
if (isMax) {
best = Integer.MIN_VALUE;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (this.arr[i][j] == ' ') {
this.arr[i][j] = moves.exp;
best = Math.max(best, minimax(depth + 1, false));
this.arr[i][j] = ' ';
}
}
}
} else {
best = Integer.MAX_VALUE;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (this.arr[i][j] == ' ') {
this.arr[i][j] = moves.oppExp;
best = Math.min(best, minimax(depth + 1, true));
this.arr[i][j] = ' ';
}
}
}
}
return best;
}
// Check for the score if the AI wins in the upcoming move
// This method helps AI to assign score for each way in the game while searching.
private int checkGetScore() {
// For Rows
for (int i = 0; i < 3; i++) {
if (this.arr[i][0] == moves.exp && this.arr[i][1] == moves.exp && this.arr[i][2] == moves.exp) {
return 10;
} else if (this.arr[i][0] == moves.oppExp && this.arr[i][1] == moves.oppExp && this.arr[i][2] == moves.oppExp) {
return -10;
}
}
// For Cols
for (int i = 0; i < 3; i++) {
if (this.arr[0][i] == moves.exp && this.arr[1][i] == moves.exp && this.arr[2][i] == moves.exp) {
return 10;
} else if (this.arr[0][i] == moves.oppExp && this.arr[1][i] == moves.oppExp && this.arr[2][i] == moves.oppExp) {
return -10;
}
}
// For Diagonals
if (this.arr[0][0] == moves.exp && this.arr[1][1] == moves.exp && this.arr[2][2] == moves.exp) {
return 10;
} else if (this.arr[0][0] == moves.oppExp && this.arr[1][1] == moves.oppExp && this.arr[2][2] == moves.oppExp) {
return -10;
} else if (this.arr[0][2] == moves.exp && this.arr[1][1] == moves.exp && this.arr[2][0] == moves.exp) {
return 10;
} else if (this.arr[0][2] == moves.oppExp && this.arr[1][1] == moves.oppExp && this.arr[2][0] == moves.oppExp) {
return -10;
}
return 0;
}
// End of Minimax Algoritm
// Displays results of Win / Tie by checking Rows, Columns and Diagonals.
public boolean displayResult() {
int valR = checkRows();
int valC = checkCols();
int diag = checkDiag();
if (diag == 1 || diag == 3 || valR == 3 || valC == 3) {
System.out.println("X wins");
return true;
} else if (diag ==  2 || diag == 4 || valR == 2 || valC == 2) {
System.out.println("O wins");
return true;
} else if ((diag == 0 || valC == 1 || valR == 1) && checkForSpace()) {
System.out.println("Draw");
return true;
}
return false;
}
// Prints the TicTacToe field
public void displayField () {
System.out.println("---------");
for (char[] a : this.arr) {
System.out.print("| ");
for (char b : a) {
System.out.print(b + " ");
}
System.out.println("|");
}
System.out.println("---------");
}
// Checks for the availability of space
// in the TicTacToe field.
private boolean checkForSpace() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (this.arr[i][j] == ' ') {
return false;
}
}
}
return true;
}
// Checks the Diagonals of the field
private int checkDiag() {
if (this.arr[0][0] == 'X' && this.arr[1][1] == 'X' && this.arr[2][2] == 'X') {
return 1;
} else if (this.arr[0][0] == 'O' && this.arr[1][1] == 'O' && this.arr[2][2] == 'O') {
return 2;
} else if (this.arr[0][2] == 'X' && this.arr[1][1] == 'X' && this.arr[2][0] == 'X') {
return 3;
} else if (this.arr[0][2] == 'O' && this.arr[1][1] == 'O' && this.arr[2][0] == 'O') {
return 4;
} else {
return 0;
}
}
// Checks the Rows of the field
private int checkRows () {
int cntX = 0,
cntO = 0;
for (int i = 0; i < 3; i++) {
if (this.arr[i][0] == 'X' && this.arr[i][1] == 'X' && this.arr[i][2] == 'X') {
cntX++;
} else if (this.arr[i][0] == 'O' && this.arr[i][1] == 'O' && this.arr[i][2] == 'O') {
cntO++;
}
}
if (cntX == 1) {
return 3;
} else if (cntO == 1) {
return 2;
} else {
return 1;
}
}
// Checks the Columns of the field
private int checkCols () {
int cntX = 0,
cntO = 0;
for (int i = 0; i < 3; i++) {
if (this.arr[0][i] == 'X' && this.arr[1][i] == 'X' && this.arr[2][i] == 'X') {
cntX++;
} else if (this.arr[0][i] == 'O' && this.arr[1][i] == 'O' && this.arr[2][i] == 'O') {
cntO++;
}
}
if (cntX == 1) {
return 3;
} else if (cntO == 1) {
return 2;
} else {
return 1;
}
}
}
public class MinimaxAlgorithm {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
TicTacToe tic = new TicTacToe();
// For game to start specify players
// Eg: start user ai    <-- user is X and user chance comes first.
// Eg: start ai user    <-- ai is X and ai chance comes first.
// Eg: start ai ai      <-- ai vs ai
// Eg: user user        <-- user vs user
String[] players = tic.whosePlaying(sc);
// Displays the empty field of TicTacToe
tic.displayField();
// turnX = true. X's turn
// turnX = false. O's turn
boolean turnX = true;
while (true) {
// Alternate player turns
// First chance of X, than O.
if (turnX) {
switch (players[0]) {
case "ai":
tic.playComputer('X');
break;
default:
tic.playUser(sc, 'X');
break;
}
turnX = false;
} else {
switch (players[1]) {
case "ai":
tic.playComputer('O');
break;
default:
tic.playUser(sc, 'O');
break;
}
turnX = true;
}
// displayresult() - Checks if the game is over by anyone winning or tie.
// If someone wins or ties the "check" becomes true and finishes the game.
// Checks after each move made by the players.
boolean check = tic.displayResult();
if (check) break;
}
sc.close();
}
}

The blue arrow indicates where the mistake happened.
The green marking specifies how it should have been.
TicTacToeAI with Minimax Algorithm

CONSOLE:

Input command: start user ai
---------
|       |
|       |
|       |
---------
Enter the coordinates (According to Matrix, Space separated integers): 1 1
---------
|       |
|   X   |
|       |
---------
Making move level "AI"
---------
| O     |
|   X   |
|       |
---------
Enter the coordinates (According to Matrix, Space separated integers): 0 2
---------
| O   X |
|   X   |
|       |
---------
Making move level "AI"
---------
| O O X |    <-- Explanation: The AI made its move on [0, 1] 
|   X   |                    but it should have did on [2, 0]
|       |                    which will block my win on right diagonal.
---------
Enter the coordinates (According to Matrix, Space separated integers): 2 0
---------
| O O X |
|   X   |
| X     |
---------
X wins

答案1

得分: 2

在你的函数checkForSpace()和它在minimax()中的使用之间存在不匹配。如果还有空间,你返回false,如果在minimax()中得到false,你会停止搜索,就好像存在平局一样。你需要在checkForSpace()中反转布尔值,或者在minimax()中删除逻辑非。

你应该重新命名checkForSpace()和其他返回布尔值的函数。来自《可读代码的艺术》(Dustin Boswell和Trevor Foucher)的建议:

当选择布尔变量的名称或返回布尔值的函数的名称时,确保真正理解了truefalse的含义。

这里有一个危险的例子:

bool read_password = true;

根据你如何阅读它(没有双关意思),有两种非常不同的解释:

  • 我们需要读取密码

  • 密码已经被读取

在这种情况下,最好避免使用单词“read”,而是将其命名为need_passworduser_is_authenticated等。

一般来说,添加像ishascanshould这样的词可以使布尔值更加清晰。

例如,名为SpaceLeft()的函数听起来可能会返回一个数字。如果它的目的是返回一个布尔值,一个更好的名称将是HasSpaceLeft()

最后,最好避免在名称中使用否定性术语。例如,不要使用:

bool disable_ssl = false;

阅读起来会更容易(并且更紧凑),而应该使用:

bool use_ssl = true;
英文:

There a mismatch between your function checkForSpace() and its use in minimax(). If there is space left you return false and if you get a false in minimax() you stop the search as if there is a tie. You need to invert the boolean in checkForSpace() or remove the logical not in minimax().

You should propably rename checkForSpace() and other function that return a boolean. From The Art of Readable Code (Dustin Boswell and Trevor Foucher):
> When picking a name for a boolean variable or a function that returns a boolean, be sure it’s clear what true and false really mean.
>
> Here’s a dangerous example:
>
> bool read_password = true;
>
> Depending on how you read it (no pun intended), there are two very different interpretations:
>
> - We need to read the password
>
> - The password has already been read
>
> In this case, it’s best to avoid the word "read," and name it need_password or user_is_authenticated instead.
>
> In general, adding words like is, has, can, or should can make booleans more clear.
>
> For example, a function named SpaceLeft() sounds like it might return a number. If it were meant to return a boolean, a better name would be HasSpaceLeft().
>
> Finally, it’s best to avoid negated terms in a name. For example, instead of:
>
> bool disable_ssl = false;
>
> it would be easier to read (and more compact) to say:
>
> bool use_ssl = true;

huangapple
  • 本文由 发表于 2020年8月10日 14:40:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/63335279.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定