【问题标题】:tictactoe minmax recursion causing confusion井字游戏极小极大递归导致混乱
【发布时间】:2019-04-15 20:02:59
【问题描述】:

我阅读了许多使用 minmax 算法的井字游戏示例,但没有找到一个可以真正解释发生了什么的好示例。我写了一个有效但需要帮助理解递归过程的一小部分。

如果您加载下面的代码,请输入您的姓名、O(非零)和 0(零),您将获得我正在查看的结果。计算机移动后停止程序并查看console.log。

如果您查看控制台输出,您会看到出现在 return 语句之前的行 best 0 7 [[0, 7]]。下一行来自0 simulateMove = 6 scoreMove[1] = 7 depth = 2

另一个问题是为什么在第 423 行我需要 scoreMoveAvailable[i][1] = simulateMove; 而不是 scoreMoveAvailable[i][1] = scoreMove[1];

import java.util.Scanner;
import java.util.ArrayList;
import java.util.Random;
import java.util.Arrays;

public class TicTacToeV2{      
    private static final int boardRowDim = 3;    
    private static final int boardColDim = 3; 
    private String[][] board;
    private String playerName;
    private String playerMark;
    private String computerMark;
    private boolean humanGoes;
    private boolean winner;
    private boolean draw
    private int gameTargetScore;
    private boolean output = true;
    private boolean toSeed = true;    
    private ArrayList<Integer> availableMoves;

    public TicTacToeV2(String name, boolean whoGoesFirst){

        availableMoves = new ArrayList<Integer>();    
        board = new String[boardRowDim][boardColDim];        


        for (int i = 0; i < board.length; i++){
            for(int j = 0; j < board[0].length; j++){               
                board[i][j] = ((Integer)(double2single(i,j))).toString();                
                availableMoves.add(double2single(i,j));
            }
        }
        playerName = name;
        humanGoes = whoGoesFirst;
        playerMark = "X";
        computerMark = "O";
        gameTargetScore = 15;
        if(!humanGoes){
            playerMark = "O";
            computerMark = "X";
            gameTargetScore = - 15;
        }
        winner = false;
        draw = false;        
    }

    public static void main(String[] args)throws Exception{
       System.out.println("\u000C");         
        Scanner kboard = new Scanner(System.in);
        printHeader();                   

        System.out.print("          Please enter your name ; ");
        String name = kboard.next();
        name = capitalize(name);

        System.out.print("\n\n          X's go first. " + name + ", please enter your mark ('X' or 'O')");
        String mark = kboard.next().toUpperCase();       
        boolean whoPlaysFirst = (mark.equals("X")) ? true : false;

        TicTacToeV2 myGame = new TicTacToeV2(name,whoPlaysFirst); 

        myGame.playGame(kboard);
    }

    public void playGame(Scanner kboard)throws Exception{   

        Integer move = null;
        boolean goodMove;
        String kboardInput = null;
        Scanner input;
        int[] cell2D = new int[2];
        Random random = new Random();
        int nextComputerMove;

        if(toSeed){
           board = seedBoard();          
           availableMoves = seedAvailable(board); 
           int x = 0;
           int o = 0;
           for(int i = 0; i < 3;i++){
              for(int j = 0;j < 3;j++){
                 if(board[i][j].equals("X"))x++;
                 else if(board[i][j].equals("O"))o++;

              }
           }

              if((x - o) == 1) humanGoes = true;
              else if((x - o) == 0) humanGoes = false;
              else{
                 System.out.println("Fatal Error: seed bad");
                 System.exit(0);
              }

          System.out.println("humangoes = " + humanGoes + x + o);
        }

        while(!winner && !draw){            
            printHeader();
            goodMove = false; 
            drawBoard(board);           

            if(!humanGoes && availableMoves.size() < 9){
                System.out.println("That's a great move, I'll have to think about this");
                Thread.sleep(2000);   
            }

            if(humanGoes){
                while(!goodMove){ 
                    System.out.print("\n\n          Please enter a number for your move : ");
                    kboardInput = kboard.next();
                    input = new Scanner(kboardInput);
                    if(input.hasNextInt()){
                        move = input.nextInt();
                        if(move == 99){
                            System.out.println("You found the secret exit code");
                            Thread.sleep(2000);
                            printHeader();
                            System.out.println("bye");
                            System.exit(0);
                        }
                        goodMove = checkMove(move);
                        if(!goodMove)System.out.println("          WARNING: Incorrect input, try again");
                    }else{
                        System.out.println("          WARNING: Incorrect input, try again");
                    }
                }
                cell2D = single2Double(move);
                board[cell2D[0]][cell2D[1]] = playerMark;

            }else{

                //nextComputerMove = random.nextInt(availableMoves.size());
                //move = availableMoves.get(nextComputerMove);               


                String[][] currentBoard = new String[boardRowDim][boardColDim];
                currentBoard = copyBoard(board);

                ArrayList<Integer> currentAvailableMoves= new ArrayList<Integer>();
                currentAvailableMoves = copyAvailableMoves(availableMoves);

                //System.out.println(System.identityHashCode(currentAvailableMoves));

                int[] bestScoreMove = new int[2];

                bestScoreMove = findBestMove(currentBoard,currentAvailableMoves,true,0,kboard); 
                move = availableMoves.get(availableMoves.indexOf(bestScoreMove[1]));

                cell2D = single2Double(move);
                board[cell2D[0]][cell2D[1]] = computerMark;
            }

            humanGoes = humanGoes ? false:true; 

            availableMoves = updateAvailableMoves(move,availableMoves);

            if (Math.abs(score(board)) == 15) winner = true;
            if (availableMoves.size() == 0) draw = true; 

            if(winner || draw){ 
                printHeader();             
                drawBoard(board); 
            }

            if(score(board) == gameTargetScore)System.out.println(playerName + " you are too good for me. \n" +
                    "Congratulations you won!!\n\n");
            else if(score(board) == -gameTargetScore)System.out.println("IWONIWONIWONohboyIWONIWONIWON");            
            else if(draw)System.out.println("Good game. It's a draw!");

        }  
    }

    public void drawBoard(String[][] someBoard){ 

        String mark = " ";
        Integer row,col;
        String type;



        for( int i = 0;i < 15; i++){ 
            System.out.print("          "); 
            for (int  j = 0; j < 27; j++){ 

                mark = " ";
                if(i==5 || i == 10)mark = "-";
                if(j==8 || j == 17)mark = "|";

                row = i/5;
                col = j/9;

                type = someBoard[row][col];

                if(type == "X"){
                    if( ((i%5 == 1 || i%5 == 3) &&
                        (j%9 == 3 || j%9 == 5)) ||
                    (i%5 == 2 && 
                        j%9 == 4))mark = "X";
                }else if(type == "O"){
                    if( ((i%5 == 1 || i%5 == 3) &&
                        (j%9 == 3 || j%9 == 4 || j%9 == 5)) ||
                    ((i%5 == 2) && 
                        (j%9 == 3 || j%9 == 5))) mark = "O";   
                }else{
                    if( i%5 == 2 && j%9 == 4){
                        mark = ((Integer)(row * 3 + col)).toString();                        
                    }
                }
                System.out.print(mark);
            } 
            System.out.println();
        }
        System.out.println("\n\n\n");
    }    

    public boolean checkMove(Integer move){
        /*
         * to sanitize user input we have to check if what
         * they entered is an available square
         */
        boolean goodMove = false;

        for(Integer available : availableMoves){
            if (available == move) goodMove = true;
        }       

        return goodMove;       
    }

    public int score(String[][] newBoard){

        int row;
        int newCol;
        int score = 0; 
        for (int strategy = 0; strategy < 8; strategy++){
            score = 0; 
            for (int col = 0; col < 3; col++){
                if(strategy < 3){  //rows
                    row = strategy ;
                    newCol = col;
                }else if (strategy < 6){ //cols
                    row = col;
                    newCol = strategy - 3;
                }else{//diag
                    int diag = strategy - 6;
                    row = col - 2 * diag * (col - 1);
                    newCol = col;                    
                }
                if(newBoard[row][newCol].equals("X")){
                    score+=5;                   
                }else if(newBoard[row][newCol].equals("O")){
                    score+=-5;                   
                }
            }
            score = (Math.abs(score)== 15) ? score : 0;            
            if(Math.abs(score) == 15) break;
        } 


        return score;         
    }

    public String[][] copyBoard(String[][] originalBoard){
        String[][] duplicateBoard = new String[boardRowDim][boardColDim];
        for (int i = 0;i < boardRowDim; i++){
            for(int j = 0; j < boardColDim; j++){                
                duplicateBoard[i][j] = originalBoard[i][j]; 
            } 
        }
        return duplicateBoard;
    }

    public String[][] updateBoard(Integer move, String mark, String[][]oldBoard){
        String[][] currentBoard = new String[boardRowDim][boardColDim];
        int[] cell2D = new int[2];

        currentBoard = copyBoard(oldBoard);
        cell2D = single2Double(move);
        currentBoard[cell2D[0]][cell2D[1]] = mark;

        return currentBoard;        
    }

    public ArrayList<Integer> copyAvailableMoves(ArrayList<Integer> originalAvailableMoves){
        ArrayList<Integer> duplicateAvailableMoves = new ArrayList<Integer>(); 
        for(int i = 0; i < originalAvailableMoves.size();i++){
            duplicateAvailableMoves.add(originalAvailableMoves.get(i));            
        }
        return duplicateAvailableMoves;        
    }

    public ArrayList<Integer> updateAvailableMoves(Integer move, ArrayList<Integer> oldAvailableMoves){
        ArrayList<Integer> currentAvailableMoves = new ArrayList<Integer>();
        currentAvailableMoves = copyAvailableMoves(oldAvailableMoves);
        currentAvailableMoves.remove(move);
        return currentAvailableMoves;

    }
    public String[][] seedBoard(){
       String[][] sampleBoard ={{"0","O","X"},{"X","4","O"},{"6","7","X"}}; 
       //String[][] sampleBoard ={{"X","O","O"},{"3","4","X"},{"6","7","8"}}; 
       return sampleBoard;
    }

    public ArrayList<Integer> seedAvailable(String[][] seedBoard){
        ArrayList seedMoves = new ArrayList<Integer>();
        int index = -1;
        for(int i = 0; i < 3;i++){
            for (int j = 0; j < 3; j++){
                if(!seedBoard[i][j].equals("X") && !seedBoard[i][j].equals("O")){
                    index = i*3 + j;
                    seedMoves.add(index);
                }
            }
        }
        //System.out.println(seedMoves);

        return seedMoves;

    }

    public int[] findBestMove(String[][] currentBoard, ArrayList<Integer> currentAvailableMoves,boolean currentComputerMoves,int depth,Scanner kboard){
        ArrayList<Integer> simulateAvailableMoves = new ArrayList<Integer>();
        String[][] simulateBoard = new String[boardRowDim][boardColDim];




        //System.out.println(System.identityHashCode(currentAvailableMoves));


        int[] scoreMove = new int[2]; //return array with score and associated move       
        int[] cell2D = new int[2];        //array holding i and j of board to place Mark (X or O)  
        int computerTargetScore = (computerMark.equals("X")) ? 15:-15;

        /*
         * scoreMoveAvailable is an array that holds scores for each available move 
         * inside loop.  The bestScoreMove will be the min or max of this array
         * depending on whether it's X's or O's turn to move 
         */       
        int[][] scoreMoveAvailable = new int[currentAvailableMoves.size()][2];        
        Integer simulateMove = null; //current move inside loop


        Boolean simulateComputerMoves = null;  


        for(int  i  = 0; i < currentAvailableMoves.size(); i++){
            scoreMoveAvailable[i][0] = 0; //score
            scoreMoveAvailable[i][1] = -1; // square 0 - 8
        }

        if(output)System.out.println("on enter available moves " + currentAvailableMoves);



        for (int i = 0; i <  currentAvailableMoves.size() ;i++){ 




            simulateAvailableMoves = copyAvailableMoves(currentAvailableMoves);
            simulateBoard = copyBoard(currentBoard);
            simulateComputerMoves = currentComputerMoves; 

            if(output)System.out.println("in loop available moves " + i + "  " + simulateAvailableMoves);


            simulateMove = simulateAvailableMoves.get(i); 

            simulateAvailableMoves = updateAvailableMoves(simulateMove,simulateAvailableMoves);
            cell2D = single2Double(simulateMove);

            if(simulateComputerMoves){ 
                if(output)System.out.println("computer moves " + simulateMove);


                simulateBoard[cell2D[0]][cell2D[1]] = computerMark;


                simulateComputerMoves = false; 

                if(score(simulateBoard) ==  computerTargetScore || simulateAvailableMoves.size() == 0){
                    scoreMove[0] = score(simulateBoard);
                    scoreMove[1] = simulateMove; 
                    if(output)System.out.println("score computer" + Arrays.toString(scoreMove) +" computer moves = " + simulateMove + " i = " + i);
                    if(output)drawBoard(simulateBoard);                    
                }else{                    
                    depth++;                                      
                    if(output)System.out.println("computer calling findbest " +simulateAvailableMoves);
                    if(output)drawBoard(simulateBoard);
                    scoreMove = findBestMove(simulateBoard,simulateAvailableMoves,simulateComputerMoves,depth,kboard);                    
                }
            }else{ 
                if(output)System.out.println("human moves" + simulateMove);


                simulateBoard[cell2D[0]][cell2D[1]] = playerMark;


                simulateComputerMoves = true;

                if(score(simulateBoard) == (-computerTargetScore) || simulateAvailableMoves.size() == 0){
                    scoreMove[0] = score(simulateBoard);
                    scoreMove[1] = simulateMove;
                    if(output)System.out.println("score human "+ Arrays.toString(scoreMove) +" human moves " + simulateMove + " i = " + i);
                    if(output)drawBoard(simulateBoard);                   
                }else{                    
                    depth++;  
                    if(output)System.out.println("human calling findbest " + simulateAvailableMoves);
                    if(output)drawBoard(simulateBoard);                
                    scoreMove = findBestMove(simulateBoard,simulateAvailableMoves,simulateComputerMoves,depth,kboard);                    
                }
            }



            if(output)System.out.println(i + " simulateMove =  " + simulateMove + " scoreMove[1] =  " + scoreMove[1] + " depth = " + depth);
            //  drawBoard(simulateBoard);

            scoreMoveAvailable[i][0] =  scoreMove[0] ;
            scoreMoveAvailable[i][1] =  simulateMove;
            if(output)System.out.println("score array =  " + i + "  " + Arrays.deepToString(scoreMoveAvailable));
        }

        int[] bestScoreMove = new int[2];
        bestScoreMove[0] = scoreMoveAvailable[0][0];  //set bestScoreMove to first element in arraylist
        bestScoreMove[1] = scoreMoveAvailable[0][1];


         if(output)System.out.println("****************************************");

        if( (currentComputerMoves && computerMark.equals("X") ) || (!currentComputerMoves && computerMark.equals("O") ) ) {

            for (int i = 0; i < scoreMoveAvailable.length;i++){
                if(scoreMoveAvailable[i][0]  > bestScoreMove[0]){
                   bestScoreMove[0] = scoreMoveAvailable[i][0] ;
                   bestScoreMove[1] = scoreMoveAvailable[i][1];
                }
                if(output)System.out.printf("MAX X scores and moves = %d   %d   %d %s\n",i,scoreMoveAvailable[i][0],scoreMoveAvailable[i][1],"XXX"); 
            }
            if(output)System.out.println("\n");
        }else{

            for (int i = 0; i < scoreMoveAvailable.length;i++){
                if(scoreMoveAvailable[i][0]  < bestScoreMove[0]){
                   bestScoreMove[0] = scoreMoveAvailable[i][0] ;
                   bestScoreMove[1] = scoreMoveAvailable[i][1];
                }
                if(output)System.out.printf("MIN O scores and moves =%d %d   %d %s\n",i,scoreMoveAvailable[i][0],scoreMoveAvailable[i][1],"OOO"); 
            }
            if(output)System.out.println("\n");
        }    

        if(output)System.out.println("best " + bestScoreMove[0] + "  " + bestScoreMove[1] + "  " + Arrays.deepToString(scoreMoveAvailable));
        return bestScoreMove;

    }

    /*
     * just some static methods to help make things easy
     */

    public static void printHeader(){
        System.out.println("u000C          Welcome to TicTacToe\n" +
            "        where you can match wits\n" +
            "          against the computer\n" +
            "(the real challenge is making it a draw)\n");
    }


    public static int double2single(int row, int col){
        int singleCell = 0; 
        singleCell = boardRowDim * row + col;
        return singleCell;
    }

    public static int[] single2Double(int cell){
        int[] cell2D = new int[2];

        cell2D[0] = cell / boardColDim;
        cell2D[1] = cell % boardColDim;

        return cell2D;
    }

    public static String capitalize(String word){
        word = word.substring(0,1).toUpperCase() + word.substring(1); 
        return word;    
    }

}

【问题讨论】:

    标签: java recursion tic-tac-toe


    【解决方案1】:

    请注意,下图假设您没有执行“depth++”,而只是在递归调用中使用“depth + 1”。前者是代码中的良性错误,会导致打印误导深度。 findBestMovebestScoreMove = findBestMove... 用于查找计算机的移动时首先调用。根据种子棋盘和人类取“0”,现在可用的步数是 4、6 和 7。

    1. 一、电脑模拟移动4
    2. 这将创建一个递归调用,将一个框架添加到函数堆栈。人类现在有 6 和 7 作为可能的移动。由于 6 是第一个,因此人类首先“尝试” 6。现在在这个模拟中,它是计算机/4 -> 人类/6
    3. 再次向函数堆栈添加一个框架,用于人类“运行模拟”的递归调用。现在,还剩下一步:计算机需要 7。是打印 best 0 7 [[0, 7]] 的地方。
    4. 在最后一次递归调用返回后,函数堆栈被弹出,我们又回到了人类移动的位置 6,这就是 0 simulateMove = 6.. 的打印位置。

    您可以使用调试器来显示有关堆栈的更多详细信息,但这里有一些额外的建议(主要是为了使函数更短)以使其更易于调试:

    • println 之后有很多Thread.sleep(2000);。您可以将这两行包装在一个函数中
    • 在许多但不是全部地方,逻辑假定电路板大小为 3x3,例如&amp;&amp; availableMoves.size() &lt; 9)。这是不一致的,这种逻辑可能会令人困惑。为了清楚起见,您可以创建一个函数“noMovesMade()”,根据可用变量计算此值。
    • 此外,if(!humanGoes &amp;&amp; availableMoves.size() &lt; 9){ 可以简单地进入“else”块(因为这已经暗示轮到计算机了)
    • if((x - o) == 1) humanGoes = true; 假设人类正在玩“O”,而其余代码并未假设这一点。
    • 在很多情况下,变量被初始化、分配,然后重新分配而不被使用。例如,String[][] currentBoard = new String[boardRowDim][boardColDim]; currentBoard = copyBoard(board); 是多余的,与bestScoreMove 相同。
    • Arrays.copyOf 可用于复制数组(例如在copyBoard 的内部循环中)
    • 可以使用ArrayList.contains 而不是遍历checkMove 中的数组
    • humanGoes = humanGoes ? false:true; 可以缩短为 humanGoes = !humanGoes

    【讨论】:

    • 感谢有用的提示。这并不能真正回答我的问题。为什么我需要使用模拟移动而不是 showMove[1] 但可以使用 showMove[0] 作为分数?
    • 在极小极大算法中,递归调用scoreMove = findBestMove(... 是根据正在采取的行动(simulateMove )。 当前玩家(例如计算机)正在做的实际移动存储在simulateMove。递归调用执行上下文不知道这是哪一步,因为它只“关心”当前棋盘状态。
    • 这没有意义。为什么我需要使用 showMove[0] 进行得分,但不能使用 showMove[1] 进行移动?有没有一种方法可以绘制堆栈中发生的事情?
    • showMove[1] 与 findBestMove 调用的执行上下文有关。此调用是针对 下一个 播放器的。如果您使用 IDE 调试器检查状态会有所帮助。请看附图。
    • 还有 String[][] currentBoard = new String[boardRowDim][boardColDim]; currentBoard = 抄写板(板);不是多余的。如果您注释掉第一行,您将无法找到第二行的符号
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-07
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    • 2023-03-25
    • 1970-01-01
    相关资源
    最近更新 更多