Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to make a Tic-Tac-Toe Android application, of course in Java. After debugging for maybe a few hours, fixing things here and there, I have come upon a problem that I can not solve by myself at this time, with this knowledge. I have deduced that the problem is found in the AndroidPerform() method where at the first line it is asked to find the best move and place it in the appropriately named variable bestMove, which is an object Move.

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

Now, clearly, by looking at the code above we can see that the method AndroidMove(player) is called which is basically a minimax algorithm, or at least it should be. While debugging I've seen that it does find good moves, but when it comes to this line

Move bestMove = AndroidMove(NOUGHT);

It returns null for x and for y and here is where I get my problem which I can't fix. I've provided the two most important classes, actually it is in these classes where all of the work is done. Anyway, hope you can help, how ever it may be, thank you in advance.

MyGame 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);
    }
}
like image 550
Filip Markoski Avatar asked Feb 08 '23 12:02

Filip Markoski


1 Answers

Alright I think I solved your problem of both x, y, and score is returning null. When you change the code noted below the variables won't all be null.

Also put the class Move in a new java file. Add "extends Move" after "public class myGame"

The reason why they were becoming null is because the variables x,y,and score were in the Move class and cannot be accessed from myGame class without public static in front of the variables x,y,and score.

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


}
like image 89
Felix Avatar answered Feb 16 '23 04:02

Felix