tictactoe minmax递归导致混乱

时间:2019-04-15 20:03:00

标签: java recursion tic-tac-toe

我使用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;    
    }

}

1 个答案:

答案 0 :(得分:0)

请注意,下图假定您未执行“ depth ++”,而是在递归调用中仅使用“ depth + 1”。前者是代码中的良性错误,导致打印出误导性深度。 当findBestMove用于查找计算机移动时,首先调用enter image description here bestScoreMove = 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..的地方。

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

  • Thread.sleep(2000);之后有很多println。您可以将这两行包装在函数中
  • 在很多但不是全部地方,逻辑假定电路板尺寸为3x3,例如&& availableMoves.size() < 9)。这是不一致的,并且此逻辑可能会造成混淆。为了清楚起见,您可以创建一个函数“ noMovesMade()”,该函数根据可用变量进行计算。
  • 此外,if(!humanGoes && availableMoves.size() < 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