java - TicTacToeAI 与 Minimax 算法
问题描述
我已经为GeeksForGeeks的井字游戏实现了 Minimax 算法。我知道 Minimax Algorithm 是如何工作的,但我的代码在这里不起作用。我已经检查并检查了我可能做错的事情,并且还对其进行了调试。但似乎,我无法找到它。
请查看算法,非常感谢您有额外的眼睛并找到我似乎找不到的不正确部分。
我已经评论了对 Minimax 算法有帮助的代码的每一部分。我认为这很容易赶上。
请帮帮我。
谢谢你。
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();
}
}
安慰:
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
解决方案
您的函数checkForSpace()
与其在minimax()
. 如果还有剩余空间,则返回false
,如果得到 a false
,minimax()
则停止搜索,就好像有平局一样。您需要反转布尔值 incheckForSpace()
或删除逻辑 not in minimax()
。
您应该适当地重命名checkForSpace()
和其他返回布尔值的函数。来自可读代码的艺术(Dustin Boswell 和 Trevor Foucher):
在为布尔变量或返回布尔值的函数选择名称时,请确保清楚其含义
true
和false
真正含义。这是一个危险的例子:
bool read_password = true;
根据您的阅读方式(没有双关语),有两种截然不同的解释:
我们需要读取密码
密码已被读取
在这种情况下,最好避免使用“阅读”一词,而是命名
need_password
它user_is_authenticated
。通常,添加诸如
is
、has
、can
或之类的词should
可以使布尔值更清晰。例如,一个名为
SpaceLeft()
听起来可能会返回一个数字的函数。如果它打算返回一个布尔值,一个更好的名字应该是HasSpaceLeft()
.最后,最好避免名称中的否定词。例如,而不是:
bool disable_ssl = false;
说起来会更容易阅读(也更紧凑):
bool use_ssl = true;
推荐阅读
- rust - 如何通过元组枚举过滤集合
- ios - Bitrise 构建无法在 Apple Watch 上安装
- android - 我无法更改变量的值
- python - 如何使用python搜索奇数
- r - 如何将回归的汇总统计数据从 R studio 导出到 Excel
- r - 如何使用具有两个感兴趣变量的 emmeans 仅指定感兴趣的比较
- c++ - 为当前程序直接从任何 GPU 内存中提取(原始或编码)视频帧?
- python - python:以十六进制打印netlink消息
- windows - Windows:一个 Window 可以有多少个区域的限制?
- python - 从尚未使用 snakemake 创建的文件中读取参数