Android Java implementation of Minimax in games with tic-tac-toe

I am trying to make an Android Tic-Tac-Toe application, of course, in Java. After debugging, perhaps in a few hours, fixing things here and there, I ran into a problem that I cannot solve on my own at this time, with this knowledge. I came to the conclusion that the problem was found in the AndroidPerform () method, where the first line suggests finding the best move and putting it in the corresponding named variable bestMove, which is a Move object.

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

Now, obviously, looking at the above code, we see that the AndroidMove method (player) is called, which is basically a minimax algorithm, or at least should be. During debugging, I saw that it finds good moves, but when it comes to this line

Move bestMove = AndroidMove(NOUGHT);

It returns null for x and for y and this is where I get my problem, which I cannot fix. I have provided the two most important classes, in fact it is in these classes where all the work is done. Anyway, hope you can help, however that may be, thanks in advance.

Class mygame

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 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;
    }

}

Class MinimaxActivity

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 Turn.");
        //myGame.AndroidPerform();
        setMove(random.nextInt(3), random.nextInt(3), COMPUTER);
        GoesFirst = false;
    }else{
        //Player Goes First

        mInfoTextView.setText("Human 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);
    }
}
+4
source share
3 answers

Working code, changes made to both of these classes.

Class MinimaxActivity

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 Turn.");
        setMove(random.nextInt(3), random.nextInt(3), COMPUTER);
        GoesFirst = false;
    }else{
        //Player Goes First
        mInfoTextView.setText("Human 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 mygame

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 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;
}

}
+2
source

EDIT: Looks like this helped fix the crash, but now you're looking for help on Minimax. You are on the right track, but there are several problems with your implementation. Here is an example of how I can do this using some of your code:

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 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 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 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 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;
}

, , Android , , pickBestMove():

mInfoTextView.setText("Android Turn.");
myGame.performComputerMove();

, . , .

:

, , , .

, bestMove NULL bestMove.x .y null. , MyGame, MinimaxActivity.setMove(), setMove :

myGame.placeAMove(x, y, player);

, myGame , MyGame. . MyGame :

MinimaxActivity minimaxActivity = new MinimaxActivity();

, MinimaxActivity , MyGame. setMove MinimaxActivity , . MinimaxActivity, myGame . , :

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

, , Minimax, MyGame.

, , MyGame. , , MyGame:

MinimaxActivity minimaxActivity;

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

, , MinimaxActivity MyGame() :

myGame = new MyGame(MinimaxActivity.this);
+3

, , x, y, score null. , , null.

Move java . "extends Move" "public class myGame"

, , , x, y score Move myGame x, y score.

public class MyGame { 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 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 must be in a separate Java file than in the myGame class.

public class Move {

int x, y, score, player; it should 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;
    }


}
+3
source

Source: https://habr.com/ru/post/1616782/


All Articles