4x4 TicTacToe板中的Minimax算法

时间:2015-11-30 13:56:31

标签: java algorithm artificial-intelligence tic-tac-toe minmax

我在人工智能项目中使用 Minimax 算法开发TicTacToe 4X4

我现有的程序在 3x3 TicTacToe板上运行 Minimax 算法。

我想将其扩展为 4x4 TicTacToe

但我不知道我该怎么办?

   import java.util.*;


//defines the point where to place 1 or 2
class Point {

    int x, y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "[" + x + ", " + y + "]";
    }
}

//defines the score per point -1,0,1
class PointsAndScores {

    int score;
    Point point;

    PointsAndScores(int score, Point point) {
        this.score = score;
        this.point = point;
    }
}

//defince the game board
class Board {

    List<Point> availablePoints;
    Scanner scan = new Scanner(System.in);
    int[][] board = new int[3][3];

    public Board() {
    }

    public boolean isGameOver() {
        //Game is over is someone has won, or board is full (draw)
        return (hasXWon() || hasOWon() || getAvailableStates().isEmpty());
    }
    //check if X have won represented by 1
    public boolean hasXWon() {
        if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 1) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 1)) {
            //System.out.println("O Diagonal Win");
            return true;
        }
        for (int i = 0; i < 3; ++i) {
            if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 1)
                    || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
                // System.out.println("O Row or Column win");
                return true;
            }
        }
        return false;
    }

    //check if O has won represented by 2
    public boolean hasOWon() {
        if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 2) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 2)) {
            // System.out.println("X Diagonal Win");
            return true;
        }
        for (int i = 0; i < 3; ++i) {
            if ((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 2)
                    || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 2)) {
                //  System.out.println("X Row or Column win");
                return true;
            }
        }

        return false;
    }

    //check available states in the board
    public List<Point> getAvailableStates() {
        availablePoints = new ArrayList<>();
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                if (board[i][j] == 0) {
                    availablePoints.add(new Point(i, j));
                }
            }
        }
        return availablePoints;
    }

    //put player move in the board
    public void placeAMove(Point point, int player) {
        board[point.x][point.y] = player;   //player = 1 for O, 2 for X..
    }

    //get best movement according to the board state
    public Point returnBestMove() {
        int MAX = -100000;
        int best = -1;

        for (int i = 0; i < rootsChildrenScores.size(); ++i) { 
            if (MAX < rootsChildrenScores.get(i).score) {
                MAX = rootsChildrenScores.get(i).score;
                best = i;
            }
        }

        return rootsChildrenScores.get(best).point;
    }

    //accepts input from user
    void takeHumanInput() {
        System.out.println("Your move: ");
        int x = scan.nextInt();
        int y = scan.nextInt();
        Point point = new Point(x, y);
        placeAMove(point, 2); 
    }

    //display current board
    public void displayBoard() {
        System.out.println();

        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();

        }
    }

    //get min value
    public int returnMin(List<Integer> list) {
        int min = Integer.MAX_VALUE;
        int index = -1;
        for (int i = 0; i < list.size(); ++i) {
            if (list.get(i) < min) {
                min = list.get(i);
                index = i;
            }
        }
        return list.get(index);
    }

    //get max value
    public int returnMax(List<Integer> list) {
        int max = Integer.MIN_VALUE;
        int index = -1;
        for (int i = 0; i < list.size(); ++i) {
            if (list.get(i) > max) {
                max = list.get(i);
                index = i;
            }
        }
        return list.get(index);
    }

    //declares a list for scores
    List<PointsAndScores> rootsChildrenScores;

    //excutes minimax algorithm
    public void callMinimax(int depth, int turn){
        rootsChildrenScores = new ArrayList<>();
        minimax(depth, turn);
    }

    //minimax algorithm
    public int minimax(int depth, int turn) {

        if (hasXWon()) return +1;
        if (hasOWon()) return -1;

        //get available states from the board
        List<Point> pointsAvailable = getAvailableStates();
        if (pointsAvailable.isEmpty()) return 0; 

        //stores scores
        List<Integer> scores = new ArrayList<>(); 

        for (int i = 0; i < pointsAvailable.size(); ++i) {
            Point point = pointsAvailable.get(i);  

            if (turn == 1) { //O's turn select the highest from below minimax() call
                placeAMove(point, 1); 
                int currentScore = minimax(depth + 1, 2);
                scores.add(currentScore);//add scores to the list

                if (depth == 0) 
                    rootsChildrenScores.add(new PointsAndScores(currentScore, point));

            } else if (turn == 2) {//X's turn select the lowest from below minimax() call
                placeAMove(point, 2); 
                scores.add(minimax(depth + 1, 1));
            }
            board[point.x][point.y] = 0; //Reset this point
        }
        return turn == 1 ? returnMax(scores) : returnMin(scores);
    }
}

//main class
public class TicTacToe {

    public static void main(String[] args) {
        Board b = new Board();//instantiate board
        Random rand = new Random();//instantiate random value

        b.displayBoard();//display board

        System.out.println("Who's gonna move first? (1)Computer (2)User: ");
        int choice = b.scan.nextInt();
        if(choice == 1){
            Point p = new Point(rand.nextInt(3), rand.nextInt(3));
            b.placeAMove(p, 1);
            b.displayBoard();
        }

        while (!b.isGameOver()) {
            System.out.println("Your move: ");
            Point userMove = new Point(b.scan.nextInt(), b.scan.nextInt());

            b.placeAMove(userMove, 2); //2 for X and X is the user
            b.displayBoard();
            if (b.isGameOver()) {
                break;
            } 
            b.callMinimax(0, 1);
            for (PointsAndScores pas : b.rootsChildrenScores) {
                System.out.println("Point: " + pas.point + " Score: " + pas.score);
            }
            b.placeAMove(b.returnBestMove(), 1);
            b.displayBoard();
        }
        if (b.hasXWon()) {
            System.out.println("Unfortunately, you lost!");
        } else if (b.hasOWon()) {
            System.out.println("You win! This is not going to get printed.");
        } else {
            System.out.println("It's a draw!");
        }
    }
}

1 个答案:

答案 0 :(得分:0)

你需要引入一个整数变量,比方说n,它将包含电路板的大小(即.of of cells = n * n)。在游戏开始之前,将向玩家询问首选的棋盘尺寸,3或4,并创建相应的棋盘。为了让您的程序像使用3x3一样使用4x4,我们需要将变量放在任何需要遍历板的方法或循环的地方。换句话说,我们将放置n而不是3。这将“概括”我们的计划。例如,在显示方法中,只要i和j小于3,我们就有2个循环运行。为了将我们的程序“推广”到半任意的板尺寸(3或4),我们放置一个n到位只要i和j小于n(3或4),循环就会运行。从3到n的变化将适用于我们有3表示电路板尺寸的地方。我们需要改变的另一件事是检查方法hasXWon()和hasOWon()。在这里,我们需要为板尺寸为4而不是3时添加一个额外的部分。这看起来或多或少与它的对应部分相同,唯一的区别是它将检查额外的行,列和更长的对角线存在于4x4板中。插入这些更改后,程序现在看起来像这样:

import java.util.*;
//defines the point where to place 1 or 2
class Point {

    int x, y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "[" + x + ", " + y + "]";
    }
}

//defines the score per point -1,0,1
class PointsAndScores {

    int score;
    Point point;

    PointsAndScores(int score, Point point) {
        this.score = score;
        this.point = point;
    }
}

//defince the game board
class board {

    List<Point> availablePoints;
    Scanner scan = new Scanner(System.in);
    public int n;
    int[][] board;

    public board(int n) {
        this.n = n;
        board = new int[n][n];
    }

    public boolean isGameOver() {
        //Game is over is someone has won, or board is full (draw)
        return (hasXWon() || hasOWon() || getAvailableStates().isEmpty());
    }
    //check if X have won represented by 1
    public boolean hasXWon() {
        if(n==3){
            if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 1) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 1)) {
                //System.out.println("O Diagonal Win");
                return true;
            }
            for (int i = 0; i < n; ++i) {
                if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 1)
                        || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
                    // System.out.println("O Row or Column win");
                    return true;
                }
            }
            return false;
        }
        else {
            if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == board[3][3]&& board[0][0] == 1) || (board[0][3] == board[1][2] && board[0][3] == board[2][1] && board[0][3] == board[3][0] && board[0][3] == 1)) {
                //System.out.println("O Diagonal Win");
                return true;
            }
            for (int i = 0; i < n; ++i) {
                if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 1)
                        || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
                    // System.out.println("O Row or Column win");
                    return true;
                }
            }
            return false;
        }
    }

    //check if O has won represented by 2
    public boolean hasOWon() {
        if(n==3){
            if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 2) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 2)) {
                //System.out.println("O Diagonal Win");
                return true;
            }
            for (int i = 0; i < n; ++i) {
                if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 2)
                        || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 2))) {
                    // System.out.println("O Row or Column win");
                    return true;
                }
            }
            return false;
        }
        else {
            if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == board[3][3]&& board[0][0] == 2) || (board[0][3] == board[1][2] && board[0][3] == board[2][1] && board[0][3] == board[3][0] && board[0][3] == 2)) {
                //System.out.println("O Diagonal Win");
                return true;
            }
            for (int i = 0; i < n; ++i) {
                if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 2)
                        || (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 2))) {
                    // System.out.println("O Row or Column win");
                    return true;
                }
            }
            return false;
        }
    }

    //check available states in the board
    public List<Point> getAvailableStates() {
        availablePoints = new ArrayList<>();
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (board[i][j] == 0) {
                    availablePoints.add(new Point(i, j));
                }
            }
        }
        return availablePoints;
    }

    //put player move in the board
    public void placeAMove(Point point, int player) {
        board[point.x][point.y] = player;   //player = 1 for O, 2 for X..
    }

    //get best movement according to the board state
    public Point returnBestMove() {
        int MAX = -100000;
        int best = -1;

        for (int i = 0; i < rootsChildrenScores.size(); ++i) { 
            if (MAX < rootsChildrenScores.get(i).score) {
                MAX = rootsChildrenScores.get(i).score;
                best = i;
            }
        }

        return rootsChildrenScores.get(best).point;
    }

    //accepts input from user
    void takeHumanInput() {
        System.out.println("Your move: ");
        int x = scan.nextInt();
        int y = scan.nextInt();
        Point point = new Point(x, y);
        placeAMove(point, 2); 
    }

    //display current board
    public void displayboard() {
        System.out.println();

        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();

        }
    }

    //get min value
    public int returnMin(List<Integer> list) {
        int min = Integer.MAX_VALUE;
        int index = -1;
        for (int i = 0; i < list.size(); ++i) {
            if (list.get(i) < min) {
                min = list.get(i);
                index = i;
            }
        }
        return list.get(index);
    }

    //get max value
    public int returnMax(List<Integer> list) {
        int max = Integer.MIN_VALUE;
        int index = -1;
        for (int i = 0; i < list.size(); ++i) {
            if (list.get(i) > max) {
                max = list.get(i);
                index = i;
            }
        }
        return list.get(index);
    }

    //declares a list for scores
    List<PointsAndScores> rootsChildrenScores;

    //excutes minimax algorithm
    public void callMinimax(int depth, int turn){
        rootsChildrenScores = new ArrayList<>();
        minimax(depth, turn);
    }

    //minimax algorithm
    public int minimax(int depth, int turn) {

        if (hasXWon()) return +1;
        if (hasOWon()) return -1;

        //get available states from the board
        List<Point> pointsAvailable = getAvailableStates();
        if (pointsAvailable.isEmpty()) return 0; 

        //stores scores
        List<Integer> scores = new ArrayList<>(); 

        for (int i = 0; i < pointsAvailable.size(); ++i) {
            Point point = pointsAvailable.get(i);  

            if (turn == 1) { //O's turn select the highest from below minimax() call
                placeAMove(point, 1); 
                int currentScore = minimax(depth + 1, 2);
                scores.add(currentScore);//add scores to the list

                if (depth == 0) 
                    rootsChildrenScores.add(new PointsAndScores(currentScore, point));

            } else if (turn == 2) {//X's turn select the lowest from below minimax() call
                placeAMove(point, 2); 
                scores.add(minimax(depth + 1, 1));
            }
            board[point.x][point.y] = 0; //Reset this point
        }
        return turn == 1 ? returnMax(scores) : returnMin(scores);
    }
}

//main class
public class TicTacToe {

    public static void main(String[] args) {
        //board b = new board();//instantiate board
        Random rand = new Random();//instantiate random value
        Point p;
        Scanner s = new Scanner(System.in);
        System.out.println("Choose board size: 3 or 4?");
        int n=s.nextInt();
        board b = new board(n);    //Instantiating board after value of n has been read. This is important because the value of n is required for the instantiation to take place.
        System.out.println(b.n);
        b.displayboard();//display board
        System.out.println("Who's gonna move first? (1)Computer (2)User: ");
        int choice = b.scan.nextInt();
        if(choice == 1){
            if(b.n==3)
                p = new Point(rand.nextInt(3), rand.nextInt(3));
            else 
                p = new Point(rand.nextInt(4), rand.nextInt(4));
            b.placeAMove(p, 1);
            b.displayboard();
        }

        while (!b.isGameOver()) {
            System.out.println("Your move: ");
            Point userMove = new Point(b.scan.nextInt(), b.scan.nextInt());

            b.placeAMove(userMove, 2); //2 for X and X is the user
            b.displayboard();
            if (b.isGameOver()) {
                break;
            } 
            b.callMinimax(0, 1);
            for (PointsAndScores pas : b.rootsChildrenScores) {
                System.out.println("Point: " + pas.point + " Score: " + pas.score);
            }
            b.placeAMove(b.returnBestMove(), 1);
            b.displayboard();
        }
        if (b.hasXWon()) {
            System.out.println("Unfortunately, you lost!");
        } else if (b.hasOWon()) {
            System.out.println("You win! This is not going to get printed.");
        } else {
            System.out.println("It's a draw!");
        }
    }
}

您的程序现在可以创建4x4游戏。但是,您需要做两件事。首先,你的placeAMove(...)方法在填充之前不检查单元格是否被占用。这可能会导致单元被覆盖,因此您必须通过在尝试填充单元格之前检查单元是否已被占用来更改它。第二个也是更重要的是,尽管该程序现在能够创建4x4游戏,但它无法进行正确的游戏。其原因在于,由minimax创建的用于寻找下一步行动的节点(状态)数量虽然在3x3游戏中可以控制,但在4x4游戏中大幅上升。这意味着计算计算机的第二步将花费你的计算机很多时间,而且我的意思是数小时。即使我操纵游戏(即在允许调用minimax以选择计算机的移动之前手动插入单元格中的随机1和2),它仍然需要计算机超过30秒来计算它的移动。这使得你的游戏当然在4x4模式下无法播放。当然,有一些方法,例如MemoizationAlpha-Beta Pruning或两者一起,可以克服这个问题并加快算法速度。 Here是一个先前提出的问题,可帮助您开始使用这些主题。 chess programming wiki也是这些主题的一个很好的信息来源,如果它的结构有点复杂的话。这是他们在Memoization上的条目(这里称为换位表)。