-1

I am trying to create a simple, local tic tac toe game for some practice in android studio so just for fun I thought I'd add a button that allows the user to undo the last move played, all the way back to before the first move if they wanted.

In order to do this, I have a 2 dimensional array that stores the current state of the board (i.e where each "x" or "o" is with respect to the actual board) that is updated every time someone plays. I am also creating a second arraylist that stores 2 dimensional arrays in order to keep track of the state of the board for every single round. So the first element of the arraylist should contain a 2 dimensional array that corresponds to the state of the board when no player has played, the second element should be after one user has played once and so on.

So what I wanted to happen is that when the user plays, before the state of the board is updated, it should copy the current state of the board into the arraylist containing all states (at the index of the current round) and then the board should be updated as well as the round count. Therefore when the user clicks the undo button, it should revert the board state back to what it was before the board was most recently updated.

My problem is : that it seems like the elements in my arraylist are not being added properly. All elements inside it contain the array to the most recent board state for some reason and I cannot for the life of me figure out why.

For example, if the game just started and I was to click any button on the grid, and then click the undo button immediately after and print the arraylist contents at the 0th index, I should be getting the default values of each grid (i.e. a blank board) but instead I am getting what the board looks like after I have played which is not intended. At no point am I putting that board state in the 0th index as far as I can see.

I show a sample output below where I have played only once and clicked the undo button once. The first set of numbers are being printed from the onclick of the grid button and the second set from the undo button. Numbers 0-9 represent the grid positions and any "X" or "O" represents where a player has played. I should technically be getting 0-9 printed for both sets of numbers since I've only played once, but I am instead getting an "X" where I played for the second set.

Here is the sample output:

2020-02-03 09:12:14.411 14808-14808/com.example.tictactoe I/Anton: From button on grid: 
2020-02-03 09:12:14.411 14808-14808/com.example.tictactoe I/Anton: 0
2020-02-03 09:12:14.411 14808-14808/com.example.tictactoe I/Anton: 1
2020-02-03 09:12:14.411 14808-14808/com.example.tictactoe I/Anton: 2
2020-02-03 09:12:14.411 14808-14808/com.example.tictactoe I/Anton: 3
2020-02-03 09:12:14.411 14808-14808/com.example.tictactoe I/Anton: 4
2020-02-03 09:12:14.411 14808-14808/com.example.tictactoe I/Anton: 5
2020-02-03 09:12:14.412 14808-14808/com.example.tictactoe I/Anton: 6
2020-02-03 09:12:14.412 14808-14808/com.example.tictactoe I/Anton: 7
2020-02-03 09:12:14.412 14808-14808/com.example.tictactoe I/Anton: 8
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: From undo button: 
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: X
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: 1
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: 2
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: 3
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: 4
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: 5
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: 6
2020-02-03 09:12:16.509 14808-14808/com.example.tictactoe I/Anton: 7
2020-02-03 09:12:16.510 14808-14808/com.example.tictactoe I/Anton: 8

Any help would be greatly appreciated. My code and xml is below:

    package com.example.tictactoe;

    import androidx.appcompat.app.AlertDialog;
    import androidx.appcompat.app.AppCompatActivity;

    import android.content.DialogInterface;
    import android.content.res.Resources;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;

    import com.example.tictactoe.Utility.TicTacAlertDialog;

    import java.util.ArrayList;
    import java.util.Arrays;

    public class MainActivity extends AppCompatActivity {

    private boolean player1Turn = true;
    private int roundCount = 1;
    private int player1Points = 0;
    private int player2Points = 0;
    private static String[][] board;


    private ArrayList<String [][]> allBoards;
    private int testing = 0;

    private TextView player1TextView;
    private TextView player2TextView;
    AlertDialog.Builder gameOver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        player1TextView = findViewById(R.id.player1);

        player2TextView = findViewById(R.id.player2);

        gameOver = new AlertDialog.Builder(this);

        board = new String[3][3];
        allBoards = new ArrayList<String[][]>();

        boardSet();
        allBoards.add(0,board);

    }

    //function defines behavior of when a user plays, i.e. clicks any button on the grid

    public void playerPlayed(View view) {
        Button button = (Button) view;
        String userSelect = button.getText().toString();


        //adding current board to arraylist containing all board states per round. Using roundcount as index.
        allBoards.add(roundCount,board);
        Log.i("Anton", "From button on grid: ");
        //debugging code to see content of arraylist
        for(int a = 0; a <3; a++){
            for(int b = 0; b<3;b++){
                Log.i("Anton", "\n" + allBoards.get(0)[a][b]);
            }
        }




        //getting i,j index of what button was just clicked on the grid
        int i = Character.getNumericValue(button.getTag().toString().charAt(4));
        int j = Character.getNumericValue(button.getTag().toString().charAt(5));


        //Printing of X or O on buttons depending on button clicked by user
        if (userSelect.equals("") && player1Turn) {
            button.setText("X");
            roundCount++;


        } else if (userSelect.equals("") && !player1Turn) {
            button.setText("O");
            roundCount++;
        }

        //copying board most recently played button into board state array
        board[i][j] = button.getText().toString();
        Boolean whoWon = winner();

        //checking for winner to display dialog box
        if (player1Turn == true && whoWon == true) {
            player1TextView.setText("Player 1: " + ++player1Points);
            whoWon = false;
            boardSet();

            //Alerts
            new TicTacAlertDialog(this, "Helo", "you won");
            gameOver.setMessage("Game over, Player 1 Won");
            gameOver.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    gameWinReset();
                    boardSet();
                }
            });
            AlertDialog gameEnded = gameOver.create();
            gameEnded.show();

        } else if (player1Turn == false && whoWon == true) {
            player2TextView.setText("Player 2: " + ++player2Points);
            whoWon = false;
            boardSet();

            gameOver.setMessage("Game over, Player 2 Won");
            gameOver.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    gameWinReset();
                    boardSet();
                }
            });
            AlertDialog gameEnded = gameOver.create();
            gameEnded.show();

        } else if (roundCount == 9) {
            whoWon = false;
            boardSet();

            gameOver.setMessage("Game over, it is a draw.");
            gameOver.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    gameWinReset();
                    boardSet();
                }
            });
            AlertDialog gameEnded = gameOver.create();
            gameEnded.show();

        }
        player1Turn = !player1Turn;



    }

    //function to determine winner of the game

    public boolean winner() {

        //columns

        if (board[0][0].equals(board[1][0]) && board[0][0].equals(board[2][0])) {
            return true;
        }

        if (board[0][1].equals(board[1][1]) && board[0][1].equals(board[2][1])) {

            return true;
        }

        if (board[0][2].equals(board[1][2]) && board[0][2].equals(board[2][2])) {
            return true;
        }

        //rows

        if (board[0][0].equals(board[0][1]) && board[0][0].equals(board[0][2])) {

            return true;
        }

        if (board[1][0].equals(board[1][1]) && board[1][0].equals(board[1][2])) {

            return true;
        }

        if (board[2][0].equals(board[2][1]) && board[2][0].equals(board[2][2])) {

            return true;
        }

        //diagonals

        if (board[0][0].equals(board[1][1]) && board[0][0].equals(board[2][2])) {
            return true;
        }

        if (board[0][2].equals(board[1][1]) && board[0][2].equals(board[2][0])) {
            return true;
        }
        return false;
    }

    //reset board button behavior

    public void resetBoard(View view) {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                Button b = (Button) findViewById(getResources().getIdentifier("grid" + i + j, "id", getPackageName()));
                b.setText("");
            }
        }
        boardSet();
    }



    //function to reset the board state array
    public void boardSet() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                board[i][j] = Integer.toString(testing);
                testing++;
            }
        }
        roundCount = 0;
        testing = 0;
    }




    //function to set board to initial blank state after game has ended or been drawn
    public void gameWinReset() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                Button b = (Button) findViewById(getResources().getIdentifier("grid" + i + j, "id", getPackageName()));
                b.setText("");
                roundCount = 0;
            }
        }
        boardSet();
    }


    //undo button behavior
    public void undoLast(View view) {
        Button eachButton;
        Resources res = getResources();

        //debugging code for weird arraylist behavior
        Log.i("Anton", "From undo button: ");
        for(int i = 0; i<3;i++){
            for(int j = 0; j<3; j++){

                Log.i("Anton", "\n\n" + allBoards.get(0)[i][j]);
            }
        }


        //Resetting board to all blank provided undo button is clicked when round count =1
        if(roundCount ==1)
        {
            //set entire  grid to blank
            for(int i = 0; i <3; i++)
            {
                for(int j = 0; j<3;j++)
                {
                    int id = res.getIdentifier("grid" + i + j, "id", getBaseContext().getPackageName());
                    eachButton = findViewById(id);
                        eachButton.setText("");
                }
            }


        }
        else{
            for(int i = 0; i <3; i++)
            {
                for(int j = 0; j<3;j++)
                {
                    int id = res.getIdentifier("grid" + i + j, "id", getBaseContext().getPackageName());
                    eachButton = findViewById(id);
                    String cellValue = allBoards.get(roundCount-2)[i][j];
                    if(cellValue.equals("X") || cellValue.equals("O"))
                    {
                        eachButton.setText(cellValue);
                    }else{
                        eachButton.setText("");
                    }
                }
            }

        }
    }
}




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">



    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:orientation="horizontal"
        >

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:layout_weight="2"
            >
            <TextView
                android:id="@+id/player1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Player 1: 0"
                android:textSize="16dp"
                android:fontFamily="serif"
                />

            <TextView
                android:id="@+id/player2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Player 2: 0"
                android:textSize="16dp"
                android:fontFamily="serif"/>
        </LinearLayout>

        <Button
            android:id="@+id/undo"
            android:onClick="undoLast"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/reset_button_drawable"
            />


        <Button
            android:id="@+id/reset"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@drawable/reset_button_drawable"
            android:onClick="resetBoard"
            android:text="Reset"
            android:textAllCaps="false"
            android:textColor="#fff"
            android:textSize="20dp"
            android:layout_weight="3"
            />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_weight="1">

        <Button
            android:id="@+id/grid00"
            android:tag="grid00"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:onClick="playerPlayed"
            android:textSize="100dp"
            android:textColor="#fff"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            />
        <Button
            android:id="@+id/grid01"
            android:tag="grid01"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:onClick="playerPlayed"
            android:text=""
            android:textSize="100dp"
            android:textColor="#fff"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            />
        <Button
            android:id="@+id/grid02"
            android:tag="grid02"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:onClick="playerPlayed"
            android:text=""
            android:textSize="100dp"
            android:textColor="#fff"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_marginBottom="5dp"
            />

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_weight="1">

        <Button
            android:id="@+id/grid10"
            android:tag="grid10"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textSize="100dp"
            android:textColor="#fff"
            android:onClick="playerPlayed"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"

            />
        <Button
            android:id="@+id/grid11"
            android:tag="grid11"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textSize="100dp"
            android:textColor="#fff"
            android:onClick="playerPlayed"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            />
        <Button
            android:id="@+id/grid12"
            android:tag="grid12"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textSize="100dp"
            android:textColor="#fff"
            android:onClick="playerPlayed"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            android:layout_marginRight="5dp"
            />

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_weight="1">

        <Button
            android:id="@+id/grid20"
            android:tag="grid20"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textSize="100dp"
            android:textColor="#fff"
            android:onClick="playerPlayed"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            />
        <Button
            android:id="@+id/grid21"
            android:tag="grid21"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textSize="100dp"
            android:textColor="#fff"
            android:onClick="playerPlayed"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            />
        <Button
            android:id="@+id/grid22"
            android:tag="grid22"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:textSize="100dp"
            android:textColor="#fff"
            android:onClick="playerPlayed"
            android:background="@drawable/button_looks"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            android:layout_marginRight="5dp"
            />

    </LinearLayout>

</LinearLayout>
Markus Kauppinen
  • 3,025
  • 4
  • 20
  • 30
KoolaidLips
  • 247
  • 1
  • 8
  • 20
  • Please post code that you have tried instead of posting only textual information. Adding Code will help others to understand more about your problem – S.Ambika Feb 03 '20 at 09:58
  • Yeah I tried doing it initially but stack overflow was giving me formatting issues for reasons unknown, so decided to post the plaintext first and then edit in the code. Sorry about that – KoolaidLips Feb 03 '20 at 10:00
  • 2
    Please post [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) - there is a way too much code. And that wall of text is really hard to read as well. – Amongalen Feb 03 '20 at 10:03
  • 1
    Does this answer your question? [Make copy of an array](https://stackoverflow.com/questions/5785745/make-copy-of-an-array) – Amongalen Feb 03 '20 at 10:14
  • 1
    Try to short down your code to the necessary parts, for example we dont need the imports –  Feb 03 '20 at 10:20
  • 2
    Please consider revising the code sample you posted in this question. As it currently stands, its formatting and scope make it hard for us to help you; here is a [great resource](http://stackoverflow.com/help/mcve) to get you started on that. -1, don't take it the wrong way. A down vote is how we indicate a content problem around here; improve your formatting and code sample and I (or someone will) gladly revert it. Good luck with your code! – Alessandro Mandelli Feb 03 '20 at 11:15
  • Alright, will try and make everything shorter and easier to read. Sorry for the inconvenience – KoolaidLips Feb 03 '20 at 12:56

1 Answers1

1

You'll need to clone the board when adding to allBoards - you're currently saving a reference to the one board state (on each move) and that will change on every move and thus all the entries in allBoards appear to change - this line (and similar) is your issue:

allBoards.add(0,board);

So wherever you allBoards.add(slot,board); replace with call to cloneBoard(slot) found in 2nd snippet...(and please note the citation for the deep clone method).

Example...

Before

public class Main
{

    private static String[][] board;
    private static ArrayList<String[][]> allBoards = new ArrayList<>();
    public static void main(String[] args) {

        board = new String[3][3];
        setBoard("A");
        allBoards.add(0,board);
        setBoard("B");
        allBoards.add(1,board);
        dumpAllBoards();
    }
    private static void dumpAllBoards() {
        int bCnt = 0;
        for (String[][] b : allBoards) {
            System.out.print(bCnt+": ");
            for (int i = 0; i < 9; i++) {
                System.out.print(b[i/3][i%3]+" ");
            }
            System.out.println();
            bCnt++;
        }
    }

    private static void setBoard(String v) {
        for (int i = 0; i < 9; i++) {
            board[i/3][i%3] = v;
        }
    }
}

prints:

0: B B B B B B B B B                                                                                                                                                               
1: B B B B B B B B B

After

public class Main
{

    private static String[][] board;
    private static ArrayList<String[][]> allBoards = new ArrayList<>();
    public static void main(String[] args) {

        board = new String[3][3];
        setBoard("A");
        cloneBoard(0);
        setBoard("B");
        cloneBoard(1);
        dumpAllBoards();


    }

    // omitted dumpAllBoards - see previous

    private static void cloneBoard(int slot) {
            allBoards.add(slot,deepCopyStrMatrix(board));
    }

    // copied from: https://stackoverflow.com/a/9106176/2711811
    public static String[][] deepCopyStrMatrix(String[][] input) {
        if (input == null)
            return null;
        String[][] result = new String[input.length][];
        for (int r = 0; r < input.length; r++) {
            result[r] = input[r].clone();
        }
        return result;
    }
}

prints:

0: A A A A A A A A A                                                                                                                                                               
1: B B B B B B B B B                                                                                                                                                               
  • This totally solved my issue. Thank you so much! Sorry for forcing you to read through all that code and text. Lesson learnt! – KoolaidLips Feb 04 '20 at 08:15