首页 > 解决方案 > 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

标签: javaalgorithmminimax

解决方案


您的函数checkForSpace()与其在minimax(). 如果还有剩余空间,则返回false,如果得到 a falseminimax()则停止搜索,就好像有平局一样。您需要反转布尔值 incheckForSpace()或删除逻辑 not in minimax()

您应该适当地重命名checkForSpace()和其他返回布尔值的函数。来自可读代码的艺术(Dustin Boswell 和 Trevor Foucher):

在为布尔变量或返回布尔值的函数选择名称时,请确保清楚其含义truefalse真正含义。

这是一个危险的例子:

bool read_password = true;

根据您的阅读方式(没有双关语),有两种截然不同的解释:

  • 我们需要读取密码

  • 密码已读取

在这种情况下,最好避免使用“阅读”一词,而是命名need_passworduser_is_authenticated

通常,添加诸如ishascan或之类的词should可以使布尔值更清晰。

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

最后,最好避免名称中的否定词。例如,而不是:

bool disable_ssl = false;

说起来会更容易阅读(也更紧凑):

bool use_ssl = true;

推荐阅读