Android Java 井字游戏中的 Minimax 实现

Android Java Minimax implementation in a tic-tac-toe game

我正在尝试制作井字游戏 Android 应用程序,当然是在 Java 中。 经过几个小时的调试,在这里和那里修复东西后,我遇到了一个问题,此时我无法靠自己解决,有了这些知识。我推断问题出在 AndroidPerform() 方法中,在第一行要求找到最佳移动并将其放置在适当命名的变量 bestMove 中,这是一个对象 Move.

public void AndroidPerform(){
    Move bestMove = AndroidMove(NOUGHT);
    placeAMove(bestMove.x, bestMove.y, NOUGHT);
    minimaxActivity.setMove(bestMove.x, bestMove.y, NOUGHT);
}

现在,很明显,通过查看上面的代码,我们可以看到方法 AndroidMove(player) 被调用,这基本上是一个 minimax 算法,或者至少它应该是。在调试时我看到它确实找到了好的动作,但是当涉及到这一行时

Move bestMove = AndroidMove(NOUGHT);

对于 x 和 y,它 returns 为空 这是我遇到无法解决的问题的地方。 我提供了两个最重要的 类,实际上所有工作都在这些 类 中完成。无论如何,希望你能帮上忙,不管怎样,先谢谢你了。

我的游戏Class

public class MyGame {

    // Name-constants to represent the seeds and cell contents
    public final int EMPTY = 0;
    public final int CROSS = 1;
    public final int NOUGHT = 2;

    // Name-constants to represent the various states of the game
    public final int PLAYING = 0;
    public final int CROSS_WON = 1;
    public final int NOUGHT_WON = 2;
    public final int DRAW = 3;

    // The game board and the game status
    public static final int ROWS = 3, COLS = 3; // number of rows and columns
    public static int[][] board = new int[ROWS][COLS]; // game board in 2D array
    //  containing (EMPTY, CROSS, NOUGHT)
    public static int currentState;  // the current state of the game
    // (PLAYING, DRAW, CROSS_WON, NOUGHT_WON)
    public static int currentPlayer; // the current player (CROSS or NOUGHT)
    public static int currentRow, currentCol; // current seed's row and column

    public int AndroidPlayer, HumanPlayer;
    MinimaxActivity minimaxActivity = new MinimaxActivity();

    class Move {
        int x, y, score, player;

        public Move(int score){
            this.score = score;
        }

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

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


    }

    public void resetBoard() {
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                board[i][j] = 0;
            }
        }
    }

    List<Move> availableMoves;
    public Move AndroidMove(int player) {
        // Computer is always NOUGHT

        // Base case
        int State = CheckGameState();
        if (State == NOUGHT_WON){
            return new Move(10);
        } else if (State == CROSS_WON){
            return new Move(-10);
        } else if (State == DRAW){
            return new Move(0);
        }

        List<Move> moves = getAvailableStates();
        //if (moves.isEmpty()) return new Move(0);

        for (int i = 0; i < ROWS; ++i) {
            for (int j = 0; j < COLS; ++j) {
                 if (board[i][j] == EMPTY){
                     Move move = new Move(i, j, player);
                     placeAMove(i, j, player);

                     if (player == NOUGHT){
                         move.score = AndroidMove(CROSS).score;
                     } else {
                         move.score = AndroidMove(NOUGHT).score;
                     }
                     moves.add(move);

                     placeAMove(i, j, EMPTY);
                 }
            }
        }

        int bestMove = 0;
        if (player == NOUGHT) {
            int bestScore = -1000000;
            for (int i = 0; i < moves.size(); i++) {
                if (moves.get(i).score > bestScore) {
                    bestMove = i;
                    bestScore = moves.get(i).score;
                }
            }
        } else {
            int bestScore = 1000000;
            for (int i = 0; i < moves.size(); i++) {
                if (moves.get(i).score < bestScore) {
                    bestMove = i;
                    bestScore = moves.get(i).score;
                }
            }
        }
        return moves.get(bestMove);
    }

    public void AndroidPerform(){
        Move bestMove = AndroidMove(NOUGHT);
        placeAMove(bestMove.x, bestMove.y, NOUGHT);
        minimaxActivity.setMove(bestMove.x, bestMove.y, NOUGHT);
    }

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

    public List<Move> getAvailableStates() {
        availableMoves = new ArrayList<>();
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                if (board[i][j] == EMPTY) {
                    availableMoves.add(new Move(i, j));
                }
            }
        }
        return availableMoves;
    }

    public int CheckGameState() {
        /*
        0 - Playing
        1 - X Won
        2 - O Won
        3 - Draw
         */

        // Check Rows - Horizontal Lines
        for (int i = 0; i< ROWS; i++){
            if (board[i][0] == CROSS &&
                board[i][1] == CROSS &&
                board[i][2] == CROSS){
                return CROSS_WON;
            }
            if (board[i][0] == NOUGHT &&
                board[i][1] == NOUGHT &&
                board[i][2] == NOUGHT){
                return NOUGHT_WON;
            }
        }

        // Check Columns - Vertical Lines
        for (int i = 0; i< COLS; i++){
            if (board[0][i] == CROSS &&
                board[1][i] == CROSS &&
                board[2][i] == CROSS){
                return CROSS_WON;
            }
            if (board[0][i] == NOUGHT &&
                board[1][i] == NOUGHT &&
                board[2][i] == NOUGHT){
                return NOUGHT_WON;
            }
        }

        // Check Diagonal
        if (board[0][0] == CROSS &&
            board[1][1] == CROSS &&
            board[2][2] == CROSS){
            return CROSS_WON;
        }
        if (board[0][0] == NOUGHT &&
            board[1][1] == NOUGHT &&
            board[2][2] == NOUGHT){
            return NOUGHT_WON;
        }


        // Check Reverse-Diagonal
        if (board[0][2] == CROSS &&
            board[1][1] == CROSS &&
            board[2][0] == CROSS){
            return CROSS_WON;
        }
        if (board[0][2] == NOUGHT &&
            board[1][1] == NOUGHT &&
            board[2][0] == NOUGHT){
            return NOUGHT_WON;
        }

        // Check for Tie
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                if (board[i][j] != CROSS && board[i][j] != NOUGHT){
                    return PLAYING;
                }
            }
        }

        return DRAW;
    }

}

MinimaxActivity Class

public class MinimaxActivity extends AppCompatActivity {

private Board BoardGame;
private MyGame myGame;

private Button mBoardButtons[][];

private TextView mInfoTextView;
private TextView mPlayerOneCount;
private TextView mTieCount;
private TextView mPlayerTwoCount;
private TextView mPlayerOneText;
private TextView mPlayerTwoText;

private int mPlayerOneCounter = 0;
private int mTieCounter = 0;
private int mPlayerTwoCounter = 0;

private Button newGame, exitGame;

public int HUMAN = 1;
public int COMPUTER = 2;
Random random;

private int First=0;
private int Counter = 0;
private boolean isGameOver = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_play);

    mBoardButtons = new Button[3][3];
    mBoardButtons[0][0] = (Button) findViewById(R.id.one);
    mBoardButtons[0][1] = (Button) findViewById(R.id.two);
    mBoardButtons[0][2] = (Button) findViewById(R.id.three);
    mBoardButtons[1][0] = (Button) findViewById(R.id.four);
    mBoardButtons[1][1] = (Button) findViewById(R.id.five);
    mBoardButtons[1][2] = (Button) findViewById(R.id.six);
    mBoardButtons[2][0] = (Button) findViewById(R.id.seven);
    mBoardButtons[2][1] = (Button) findViewById(R.id.eight);
    mBoardButtons[2][2] = (Button) findViewById(R.id.nine);

    newGame = (Button) findViewById(R.id.newGame1);
    exitGame = (Button) findViewById(R.id.exitGame1);
    // Text Fields
    mInfoTextView = (TextView) findViewById(R.id.information);
    mPlayerOneCount = (TextView) findViewById(R.id.humanCount);
    mTieCount = (TextView) findViewById(R.id.tiesCount);
    mPlayerTwoCount = (TextView) findViewById(R.id.androidCount);
    mPlayerOneText = (TextView) findViewById(R.id.human);
    mPlayerTwoText = (TextView) findViewById(R.id.android);
    // Counters
    mPlayerOneCount.setText(Integer.toString(mPlayerOneCounter));
    mTieCount.setText(Integer.toString(mTieCounter));
    mPlayerTwoCount.setText(Integer.toString(mPlayerTwoCounter));

    random = new Random();

    exitGame.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            MinimaxActivity.this.finish();
        }
    });

    final CharSequence[] items = {"Computer", "Player"};

    final AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
    alertDialog.setCancelable(false);
    alertDialog.setTitle("Who goes first?");
    alertDialog.setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int item) {
            if (items[item] == "Computer") {
                First = 1; // Computer
            } else if (items[item] == "Player") {
                First = 2; // Player
            }
            dialog.dismiss();

            //BoardGame = new Board();
            myGame = new MyGame();

            if (First == 1) {
                startNewGame(true); // True For Computer
            }
            if (First == 2) {
                startNewGame(false); // False For Player
            }

        }
    });
    alertDialog.show();

    newGame.setOnClickListener(new View.OnClickListener() { // FIX STARTNEWGAME
        @Override
        public void onClick(View v) {
            if (Counter % 2 == 0) {
                startNewGame(false);
                Counter++;
            } else {
                startNewGame(true);
                Counter++;
            }
            // Here we stop, counter can be used for session concept
        }
    });

    // http://developer.android.com/guide/topics/ui/dialogs.html
    // Adding a persistent multiple-choice or single-choice list
}

private void startNewGame(boolean GoesFirst) {

    MyResetBoard(); // Look at board reset

    mPlayerOneText.setText("Human:");
    mPlayerTwoText.setText("Android:");


    if(GoesFirst){
        // Computer Goes First

        mInfoTextView.setText("Android's Turn.");
        //myGame.AndroidPerform();
        setMove(random.nextInt(3), random.nextInt(3), COMPUTER);
        GoesFirst = false;
    }else{
        //Player Goes First

        mInfoTextView.setText("Human's Turn.");
        GoesFirst = true;
    }
    isGameOver = false;
}

private void MyResetBoard(){
    myGame.resetBoard();
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            mBoardButtons[i][j].setText("");
            mBoardButtons[i][j].setEnabled(true);
            mBoardButtons[i][j].setOnClickListener(new ButtonClickListener(i,j));
            mBoardButtons[i][j].setBackgroundResource(R.drawable.empty);
        }
    }
}

private class ButtonClickListener implements View.OnClickListener {

    int x,y;

    public ButtonClickListener(int i, int j) {
        this.x = i;
        this.y = j;
    }

    @Override
    public void onClick(View v) {
        if (!isGameOver){ // If the game is not over
            if (mBoardButtons[x][y].isEnabled()){
                setMove(x, y, myGame.CROSS); // Human makes a move

                int winner = myGame.CheckGameState();

                if (winner == myGame.PLAYING) { // If still playing
                    mInfoTextView.setText(R.string.turn_computer);
                    myGame.AndroidPerform();
                    //int move = mGame.getComputerMove();
                    //setMove(TicTacToeGame.PLAYER_TWO, move);
                    winner = myGame.CheckGameState();
                }

                if (winner == myGame.PLAYING){
                    mInfoTextView.setText(R.string.turn_human);
                }
                else if (winner == myGame.DRAW) { // If draw
                    mInfoTextView.setText(R.string.result_tie);
                    mTieCounter++;
                    mTieCount.setText(Integer.toString(mTieCounter));
                    isGameOver = true;
                } else if (winner == myGame.CROSS_WON) { // X Won
                    mInfoTextView.setText(R.string.result_human_wins);
                    mPlayerOneCounter++;
                    mPlayerOneCount.setText(Integer.toString(mPlayerOneCounter));
                    isGameOver = true;
                } else if (winner == myGame.NOUGHT_WON){ // O Won
                    mInfoTextView.setText(R.string.result_android_wins);
                    mPlayerTwoCounter++;
                    mPlayerTwoCount.setText(Integer.toString(mPlayerTwoCounter));
                    isGameOver = true;
                }


            }
        }
    }
}

public void setMove(int x, int y, int player){
    myGame.placeAMove(x, y, player);
    mBoardButtons[x][y].setEnabled(false);
    if (player == 1) {
        mBoardButtons[x][y].setBackgroundResource(R.drawable.x);
    } else {
        mBoardButtons[x][y].setBackgroundResource(R.drawable.o);
    }
}

编辑:看起来这有助于修复崩溃,但您现在正在寻求有关 Minimax 的帮助。您走在正确的轨道上,但您的实施存在一些问题。这是我如何使用您的一些代码的示例:

public static final int SCORE_DRAW = 0;
public static final int SCORE_WIN = 999;
public static final int SCORE_LOSS = -999;
public static final int SCORE_ILLEGAL_MOVE = Integer.MIN_VALUE;

/**
 * Look at all moves for the specified player, score them, and return the move with the highest score
 *
 * @param currentPlayer the player who will be making the move
 * @param board current state of the board
 *
 * @return the move with the highest score
 */
@NonNull
public Move pickBestMove(int currentPlayer, int[][] board){
    List<Move> availableMoves = getAvailableMoves(board);

    if(availableMoves.size() < 1){
        //Something went wrong.
        throw new IllegalStateException("No available moves to pick from.");
    }

    //Default to the first available move
    Move bestMove = availableMoves.get(0);

    //Iterate through the available moves
    for(Move move : availableMoves){
        move.player = currentPlayer;
        move.score = getScoreForMove(move, board, currentPlayer);

        if(move.score == SCORE_WIN){
            //If the move would win the game, it's obviously the best move.
            return move;
        }else if(move.score > bestMove.score){
            //Found a better move. update bestMove.
            bestMove = move;
        }
    }

    //Return the best one
    return bestMove;
}

/**
 * Score a move by making the move and then assuming each player will play optimally until
 * we reach the end of the game.
 *
 * @param move the move to score
 * @param board current state of the game board. This will be copied.
 * @param scoredPlayer the player for whom to score the move
 *
 * @return a score for the move, assuming each player plays optimally
 */
private int getScoreForMove(@NonNull Move move, int[][] board, int scoredPlayer){
    //Make a copy of the board so we can change it
    int[][] boardCopy = copyArray(board);

    if(boardCopy[move.x][move.y] != EMPTY){
        //this is not a legal move, score negative infinity.
        return SCORE_ILLEGAL_MOVE;
    }else{
        //the move is legal. Update the board with the proposed move
        boardCopy[move.x][move.y] = move.player;

        //Get the game state based on the proposed move.
        int newState = checkGameState(boardCopy);

        //check if it's a draw, win, or a loss
        if(newState == DRAW){
            //This move would cause a draw.
            return SCORE_DRAW;
        }else if(newState == NOUGHT_WON){
            //Somebody wins with this move. Check if it's the player we're scoring
            return scoredPlayer == NOUGHT ? SCORE_WIN : SCORE_LOSS;
        }else if(newState == CROSS_WON){
            //Somebody wins with this move. Check if it's the player we're scoring
            return scoredPlayer == CROSS ? SCORE_WIN : SCORE_LOSS;
        }

        //Game isn't over yet. Assume the next player will make their best possible move,
        //and check what the resulting score would be.
        int nextPlayer = move.player == NOUGHT ? CROSS : NOUGHT;

        //note that we change the player for pickBestMove, but not the scored player
        return getScoreForMove(pickBestMove(nextPlayer, boardCopy), boardCopy, scoredPlayer);
    }
}

public void performComputerMove(){
    Move bestMove = pickBestMove(NOUGHT, mBoard);
    minimaxActivity.setMove(bestMove.x, bestMove.y, NOUGHT);
}

/**
 * Perform a deep copy of the array. Most library array copy methods 
 * (Arrays.copyOf, System.arrayCopy) create a shallow copy and are
 * therefore unsuitable for making an editable copy of a 2d array
 *
 * @param toCopy the array to copy
 * @return a deep copy of the array
 */
private int[][] copyArray(int[][] toCopy) {
    int[][] newArray = new int[toCopy.length][toCopy[0].length];
    for (int i = 0; i < toCopy.length; i++) {
        for(int j = 0; j < toCopy[i].length; j++) {
            newArray[i][j] = toCopy[i][j];
        }
    }

    return newArray;
}

此外,在 activity 中,当轮到 Android 先走时,您应该使用 pickBestMove():

而不是选择随机移动
mInfoTextView.setText("Android's Turn.");
myGame.performComputerMove();

希望这是可以理解的。如果您有任何问题,请告诉我。

上一个答案:

你的问题没有具体说明你的实际问题是什么,但我认为在查看代码后一定是崩溃。

问题不在于 bestMove 为空,也不在于 bestMove.x 或 .y 为空。问题在于,在 MyGame 中,当您调用 MinimaxActivity.setMove() 时,setMove 会执行以下操作:

myGame.placeAMove(x, y, player);

不幸的是,当从 MyGame 调用它时,myGame 本身将为 null。原因很简单。在 MyGame 中你有:

MinimaxActivity minimaxActivity = new MinimaxActivity();

这意味着您每次创建新的 MyGame 时都会创建一个全新的 MinimaxActivity。然后,您将在新创建的 MinimaxActivity 而不是您已经设置的那个上调用 setMove。当您创建 MinimaxActivity 时,myGame 开始时为 null。因为你从来没有设置它,你会得到一个例外:

public void setMove(int x, int y, int player) {
    myGame.placeAMove(x, y, player);  //---myGame = null!!! Crash.
    ...
}

不过,方便的是,您已经有了一个 Minimax activity,您已经在其中设置了一个新的 MyGame。

因此,要解决此问题,您需要实际将 activity 的现有实例传递给 MyGame。最简单的地方可能在MyGame的构造函数中:

MinimaxActivity minimaxActivity;

public MyGame(MinimaxActivity minimaxActivity) {
    this.minimaxActivity = minimaxActivity;
}

然后,当然,在 MinimaxActivity 中,您必须这样做,而不是 new MyGame():

myGame = new MyGame(MinimaxActivity.this);

好的,我想我解决了您的 x、y 和分数都返回 null 的问题。 当您更改下面注明的代码时,变量不会全部为空。

同时将 class Move 放入新的 java 文件中。在 "public class myGame"

之后添加 "extends Move"

它们变为空的原因是因为变量 x、y 和分数在 Move class 中,如果没有 public static 就无法从 myGame class 访问变量 x、y 和分数的前面。

public class MyGame { needs to be public class MyGame extends Move{

// Name-constants to represent the seeds and cell contents
public final int EMPTY = 0;
public final int CROSS = 1;
public final int NOUGHT = 2;

// Name-constants to represent the various states of the game
public final int PLAYING = 0;
public final int CROSS_WON = 1;
public final int NOUGHT_WON = 2;
public final int DRAW = 3;

// The game board and the game status
public static final int ROWS = 3, COLS = 3; // number of rows and columns
public static int[][] board = new int[ROWS][COLS]; // game board in 2D array
//  containing (EMPTY, CROSS, NOUGHT)
public static int currentState;  // the current state of the game
// (PLAYING, DRAW, CROSS_WON, NOUGHT_WON)
public static int currentPlayer; // the current player (CROSS or NOUGHT)
public static int currentRow, currentCol; // current seed's row and column

public int AndroidPlayer, HumanPlayer;
MinimaxActivity minimaxActivity = new MinimaxActivity();

public void resetBoard() {
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            board[i][j] = 0;
        }
    }
}

List<Move> availableMoves;
public Move AndroidMove(int player) {
    // Computer is always NOUGHT

    // Base case
    int State = CheckGameState();
    if (State == NOUGHT_WON){
        return new Move(10);
    } else if (State == CROSS_WON){
        return new Move(-10);
    } else if (State == DRAW){
        return new Move(0);
    }

    List<Move> moves = getAvailableStates();
    //if (moves.isEmpty()) return new Move(0);

    for (int i = 0; i < ROWS; ++i) {
        for (int j = 0; j < COLS; ++j) {
             if (board[i][j] == EMPTY){
                 Move move = new Move(i, j, player);
                 placeAMove(i, j, player);

                 if (player == NOUGHT){
                     move.score = AndroidMove(CROSS).score;
                 } else {
                     move.score = AndroidMove(NOUGHT).score;
                 }
                 moves.add(move);

                 placeAMove(i, j, EMPTY);
             }
        }
    }

    int bestMove = 0;
    if (player == NOUGHT) {
        int bestScore = -1000000;
        for (int i = 0; i < moves.size(); i++) {
            if (moves.get(i).score > bestScore) {
                bestMove = i;
                bestScore = moves.get(i).score;
            }
        }
    } else {
        int bestScore = 1000000;
        for (int i = 0; i < moves.size(); i++) {
            if (moves.get(i).score < bestScore) {
                bestMove = i;
                bestScore = moves.get(i).score;
            }
        }
    }
    return moves.get(bestMove);
}

public void AndroidPerform(){
    Move bestMove = AndroidMove(NOUGHT);
    placeAMove(bestMove.x, bestMove.y, NOUGHT);
    minimaxActivity.setMove(bestMove.x, bestMove.y, NOUGHT);
}

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

public List<Move> getAvailableStates() {
    availableMoves = new ArrayList<>();
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            if (board[i][j] == EMPTY) {
                availableMoves.add(new Move(i, j));
            }
        }
    }
    return availableMoves;
}

public int CheckGameState() {
    /*
    0 - Playing
    1 - X Won
    2 - O Won
    3 - Draw
     */

    // Check Rows - Horizontal Lines
    for (int i = 0; i< ROWS; i++){
        if (board[i][0] == CROSS &&
            board[i][1] == CROSS &&
            board[i][2] == CROSS){
            return CROSS_WON;
        }
        if (board[i][0] == NOUGHT &&
            board[i][1] == NOUGHT &&
            board[i][2] == NOUGHT){
            return NOUGHT_WON;
        }
    }

    // Check Columns - Vertical Lines
    for (int i = 0; i< COLS; i++){
        if (board[0][i] == CROSS &&
            board[1][i] == CROSS &&
            board[2][i] == CROSS){
            return CROSS_WON;
        }
        if (board[0][i] == NOUGHT &&
            board[1][i] == NOUGHT &&
            board[2][i] == NOUGHT){
            return NOUGHT_WON;
        }
    }

    // Check Diagonal
    if (board[0][0] == CROSS &&
        board[1][1] == CROSS &&
        board[2][2] == CROSS){
        return CROSS_WON;
    }
    if (board[0][0] == NOUGHT &&
        board[1][1] == NOUGHT &&
        board[2][2] == NOUGHT){
        return NOUGHT_WON;
    }


    // Check Reverse-Diagonal
    if (board[0][2] == CROSS &&
        board[1][1] == CROSS &&
        board[2][0] == CROSS){
        return CROSS_WON;
    }
    if (board[0][2] == NOUGHT &&
        board[1][1] == NOUGHT &&
        board[2][0] == NOUGHT){
        return NOUGHT_WON;
    }

    // Check for Tie
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            if (board[i][j] != CROSS && board[i][j] != NOUGHT){
                return PLAYING;
            }
        }
    }

    return DRAW;
}

}

The Move class needs to be in a separate Java file than myGame class.

public class Move {

int x, y, score, player; needs to be public static int x, y, score, player;

    public Move(int score){
        this.score = score;
    }

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

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


}

工作代码,在两个 类.

中都进行了更改

MinimaxActivity Class

public class MinimaxActivity extends AppCompatActivity {

private Board BoardGame;
private MyGame myGame;

private Button mBoardButtons[][];

private TextView mInfoTextView;
private TextView mPlayerOneCount;
private TextView mTieCount;
private TextView mPlayerTwoCount;
private TextView mPlayerOneText;
private TextView mPlayerTwoText;

private int mPlayerOneCounter = 0;
private int mTieCounter = 0;
private int mPlayerTwoCounter = 0;

private Button newGame, exitGame;

public int HUMAN = 1;
public int COMPUTER = 2;
Random random;

private int First=0;
private int Counter = 0;
private boolean isGameOver = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_play);

    mBoardButtons = new Button[3][3];
    mBoardButtons[0][0] = (Button) findViewById(R.id.one);
    mBoardButtons[0][1] = (Button) findViewById(R.id.two);
    mBoardButtons[0][2] = (Button) findViewById(R.id.three);
    mBoardButtons[1][0] = (Button) findViewById(R.id.four);
    mBoardButtons[1][1] = (Button) findViewById(R.id.five);
    mBoardButtons[1][2] = (Button) findViewById(R.id.six);
    mBoardButtons[2][0] = (Button) findViewById(R.id.seven);
    mBoardButtons[2][1] = (Button) findViewById(R.id.eight);
    mBoardButtons[2][2] = (Button) findViewById(R.id.nine);

    newGame = (Button) findViewById(R.id.newGame1);
    exitGame = (Button) findViewById(R.id.exitGame1);
    // Text Fields
    mInfoTextView = (TextView) findViewById(R.id.information);
    mPlayerOneCount = (TextView) findViewById(R.id.humanCount);
    mTieCount = (TextView) findViewById(R.id.tiesCount);
    mPlayerTwoCount = (TextView) findViewById(R.id.androidCount);
    mPlayerOneText = (TextView) findViewById(R.id.human);
    mPlayerTwoText = (TextView) findViewById(R.id.android);
    // Counters
    mPlayerOneCount.setText(Integer.toString(mPlayerOneCounter));
    mTieCount.setText(Integer.toString(mTieCounter));
    mPlayerTwoCount.setText(Integer.toString(mPlayerTwoCounter));

    random = new Random();

    exitGame.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            MinimaxActivity.this.finish();
        }
    });

    final CharSequence[] items = {"Computer", "Player"};

    final AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
    alertDialog.setCancelable(false);
    alertDialog.setTitle("Who goes first?");
    alertDialog.setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int item) {
            if (items[item] == "Computer") {
                First = 1; // Computer
            } else if (items[item] == "Player") {
                First = 2; // Player
            }
            dialog.dismiss();

            myGame = new MyGame(MinimaxActivity.this);

            if (First == 1) {
                startNewGame(true); // True For Computer
            }
            if (First == 2) {
                startNewGame(false); // False For Player
            }

        }
    });
    alertDialog.show();

    newGame.setOnClickListener(new View.OnClickListener() { // FIX STARTNEWGAME
        @Override
        public void onClick(View v) {
            if (Counter % 2 == 0) {
                startNewGame(false);
                Counter++;
            } else {
                startNewGame(true);
                Counter++;
            }
        }
    });

}

private void startNewGame(boolean GoesFirst) {

    MyResetBoard(); // Look at board reset

    mPlayerOneText.setText("Human:");
    mPlayerTwoText.setText("Android:");

    if(GoesFirst){
        // Computer Goes First
        mInfoTextView.setText("Android's Turn.");
        setMove(random.nextInt(3), random.nextInt(3), COMPUTER);
        GoesFirst = false;
    }else{
        //Player Goes First
        mInfoTextView.setText("Human's Turn.");
        GoesFirst = true;
    }
    isGameOver = false;
}

private void MyResetBoard(){
    myGame.resetBoard();
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            mBoardButtons[i][j].setText("");
            mBoardButtons[i][j].setEnabled(true);
            mBoardButtons[i][j].setOnClickListener(new ButtonClickListener(i,j));
            mBoardButtons[i][j].setBackgroundResource(R.drawable.empty);
        }
    }
}

private class ButtonClickListener implements View.OnClickListener {

    int x,y;

    public ButtonClickListener(int i, int j) {
        this.x = i;
        this.y = j;
    }

    @Override
    public void onClick(View v) {
        if (!isGameOver){ // If the game is not over
            if (mBoardButtons[x][y].isEnabled()){
                setMove(x, y, HUMAN); // Human makes a move CROSS


                int winner = myGame.CheckGameState();

                if (winner == myGame.PLAYING) { // If still playing
                    mInfoTextView.setText(R.string.turn_computer);
                    int[] result = myGame.move();
                    setMove(result[0], result[1], COMPUTER);
                    winner = myGame.CheckGameState();
                }

                winner = myGame.CheckGameState();

                if (winner == myGame.PLAYING){
                    mInfoTextView.setText(R.string.turn_human);
                }
                else if (winner == myGame.DRAW) { // If draw
                    mInfoTextView.setText(R.string.result_tie);
                    mTieCounter++;
                    mTieCount.setText(Integer.toString(mTieCounter));
                    isGameOver = true;
                } else if (winner == myGame.CROSS_WON) { // X Won
                    mInfoTextView.setText(R.string.result_human_wins);
                    mPlayerOneCounter++;
                    mPlayerOneCount.setText(Integer.toString(mPlayerOneCounter));
                    isGameOver = true;
                } else if (winner == myGame.NOUGHT_WON){ // O Won
                    mInfoTextView.setText(R.string.result_android_wins);
                    mPlayerTwoCounter++;
                    mPlayerTwoCount.setText(Integer.toString(mPlayerTwoCounter));
                    isGameOver = true;
                }


            }
        }
    }
}

public void setMove(int x, int y, int player){
    myGame.placeAMove(x, y, player);
    mBoardButtons[x][y].setEnabled(false);
    if (player == 1) {
        mBoardButtons[x][y].setBackgroundResource(R.drawable.x);
    } else {
        mBoardButtons[x][y].setBackgroundResource(R.drawable.o);
    }
}
}

我的游戏Class

public class MyGame {

// Name-constants to represent the seeds and cell contents
public final int EMPTY = 0;
public final int CROSS = 1;
public final int NOUGHT = 2;

// Name-constants to represent the various states of the game
public final int PLAYING = 0;
public final int CROSS_WON = 1;
public final int NOUGHT_WON = 2;
public final int DRAW = 3;

// The game board and the game status
public static final int ROWS = 3, COLS = 3; // number of rows and columns
public static int[][] board = new int[ROWS][COLS]; // game board in 2D array
//  containing (EMPTY, CROSS, NOUGHT)
public static int currentState;  // the current state of the game
// (PLAYING, DRAW, CROSS_WON, NOUGHT_WON)
public static int currentPlayer; // the current player (CROSS or NOUGHT)
public static int currentRow, currentCol; // current seed's row and column

MinimaxActivity minimaxActivity;

public MyGame(MinimaxActivity minimaxActivity) {
    this.minimaxActivity = minimaxActivity;
}

public void resetBoard() {
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            board[i][j] = 0;
        }
    }
}

/** Get next best move for computer. Return int[2] of {row, col} */
public int[] move() {
    int[] result = minimax(2, NOUGHT); // depth, max turn
    return new int[] {result[1], result[2]};   // row, col
}

public int[] minimax(int depth, int player){
    // Generate possible next moves in a List of int[2] of {row, col}.
    List<int[]> nextMoves = generateMoves();

    // mySeed(NOUGHT) is maximizing; while oppSeed(CROSS) is minimizing
    int bestScore = (player == NOUGHT) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
    int currentScore;
    int bestRow = -1;
    int bestCol = -1;

    if (nextMoves.isEmpty() || depth == 0){
        bestScore = evaluate();
    } else {
        for (int[] move : nextMoves){
            board[move[0]][move[1]] = player;
            if (player == NOUGHT) { // NOUGHT is Maximizing Player
                currentScore = minimax(depth - 1, CROSS)[0];
                if (currentScore > bestScore) {
                    bestScore = currentScore;
                    bestRow = move[0];
                    bestCol = move[1];
                }
            } else { // CROSS is Minimizing Player
                    currentScore = minimax(depth - 1, NOUGHT)[0];
                    if (currentScore < bestScore) {
                        bestScore = currentScore;
                        bestRow = move[0];
                        bestCol = move[1];
                    }
            }
            // Undo move
            board[move[0]][move[1]] = EMPTY;
        }
    }
    return new int[] {bestScore, bestRow, bestCol};
}

private int evaluate() {
    int score = 0;
    // Evaluate score for each of the 8 lines (3 rows, 3 columns, 2 diagonals)
    score += evaluateLine(0, 0, 0, 1, 0, 2);  // row 0
    score += evaluateLine(1, 0, 1, 1, 1, 2);  // row 1
    score += evaluateLine(2, 0, 2, 1, 2, 2);  // row 2
    score += evaluateLine(0, 0, 1, 0, 2, 0);  // col 0
    score += evaluateLine(0, 1, 1, 1, 2, 1);  // col 1
    score += evaluateLine(0, 2, 1, 2, 2, 2);  // col 2
    score += evaluateLine(0, 0, 1, 1, 2, 2);  // diagonal
    score += evaluateLine(0, 2, 1, 1, 2, 0);  // alternate diagonal
    return score;
}

/** The heuristic evaluation function for the given line of 3 cells
     @Return +100, +10, +1 for 3-, 2-, 1-in-a-line for computer.
             -100, -10, -1 for 3-, 2-, 1-in-a-line for opponent.
             0 otherwise */
private int evaluateLine(int row1, int col1, int row2, int col2, int row3, int col3) {
    int score = 0;

    // First cell
    if (board[row1][col1] == NOUGHT) {
        score = 1;
    } else if (board[row1][col1] == CROSS) {
        score = -1;
    }

    // Second cell
    if (board[row2][col2] == NOUGHT) {
        if (score == 1) {   // cell1 is mySeed
            score = 10;
        } else if (score == -1) {  // cell1 is oppSeed
            return 0;
        } else {  // cell1 is empty
            score = 1;
        }
    } else if (board[row2][col2] == CROSS) {
        if (score == -1) { // cell1 is oppSeed
            score = -10;
        } else if (score == 1) { // cell1 is mySeed
            return 0;
        } else {  // cell1 is empty
            score = -1;
        }
    }

    // Third cell
    if (board[row3][col3] == NOUGHT) {
        if (score > 0) {  // cell1 and/or cell2 is mySeed
            score *= 10;
        } else if (score < 0) {  // cell1 and/or cell2 is oppSeed
            return 0;
        } else {  // cell1 and cell2 are empty
            score = 1;
        }
    } else if (board[row3][col3] == CROSS) {
        if (score < 0) {  // cell1 and/or cell2 is oppSeed
            score *= 10;
        } else if (score > 1) {  // cell1 and/or cell2 is mySeed
            return 0;
        } else {  // cell1 and cell2 are empty
            score = -1;
        }
    }
    return score;
}

private List<int[]> generateMoves() {
    List<int[]> nextMoves = new ArrayList<int[]>(); // allocate List

    int State = CheckGameState();
    // If gameover, i.e., no next move
    if (State == 1 || // X Won
        State == 2 || // O Won
        State == 3)   // Draw
    {
        return nextMoves;   // return empty list
    }

    // Search for empty cells and add to the List
    for (int row = 0; row < ROWS; ++row) {
        for (int col = 0; col < COLS; ++col) {
            if (board[row][col] == EMPTY) {
                nextMoves.add(new int[] {row, col});
            }
        }
    }
    return nextMoves;
}

public void placeAMove(int x, int y, int player) {
    board[x][y] = player;   //player = 1 for X, 2 for O
}

public int CheckGameState() {
    /*
    0 - Playing
    1 - X Won
    2 - O Won
    3 - Draw
     */

    // Check Rows - Horizontal Lines
    for (int i = 0; i< ROWS; i++){
        if (board[i][0] == CROSS &&
            board[i][1] == CROSS &&
            board[i][2] == CROSS){
            return CROSS_WON;
        }
        if (board[i][0] == NOUGHT &&
            board[i][1] == NOUGHT &&
            board[i][2] == NOUGHT){
            return NOUGHT_WON;
        }
    }

    // Check Columns - Vertical Lines
    for (int i = 0; i< COLS; i++){
        if (board[0][i] == CROSS &&
            board[1][i] == CROSS &&
            board[2][i] == CROSS){
            return CROSS_WON;
        }
        if (board[0][i] == NOUGHT &&
            board[1][i] == NOUGHT &&
            board[2][i] == NOUGHT){
            return NOUGHT_WON;
        }
    }

    // Check Diagonal
    if (board[0][0] == CROSS &&
        board[1][1] == CROSS &&
        board[2][2] == CROSS){
        return CROSS_WON;
    }
    if (board[0][0] == NOUGHT &&
        board[1][1] == NOUGHT &&
        board[2][2] == NOUGHT){
        return NOUGHT_WON;
    }


    // Check Reverse-Diagonal
    if (board[0][2] == CROSS &&
        board[1][1] == CROSS &&
        board[2][0] == CROSS){
        return CROSS_WON;
    }
    if (board[0][2] == NOUGHT &&
        board[1][1] == NOUGHT &&
        board[2][0] == NOUGHT){
        return NOUGHT_WON;
    }

    // Check for Tie
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            if (board[i][j] != CROSS && board[i][j] != NOUGHT){
                return PLAYING;
            }
        }
    }

    return DRAW;
}

}