1

I am making an AI game where the cpu stacks N numbered cubes in a table in 3 rows using the Uniform Cost Search algorithm. What I'm trying to do is the generateMoves() method, which will return an ArrayList with all the possible moves from a given state.

Cube is a class representing a cube with its own ID (the number of the cube), X axis position and Y axis position.

K is user input

getCubes() is a method that returns an ArrayList<Cube> which is the current state of the table.

setCubes() is a method that takes an ArrayList<Cube> and sets that list as the current state.

isValid() is a Cube method that checks the copyOfTable list if the given cube is in a valid position.

isFree() is a Cube method that checks the copyOfTable list if the given cube is free to be moved.

public class Table{
  private fields

  constructor{}

  public Arraylist<Table> generateMoves(){
    Arraylist<Table> moves = new ArrayList<Table>();
    ArrayList<Cube> currentTable = this.getCubes();
    ArrayList<Cube> copyOfTable = new ArrayList<Cube>(currentTable);

    int X = 1;
    int Y = 1;
    int K = this.getK();

    for(Cube cube : currentTable){
      int backupX = cube.getPosX();
      int backupY = cube.getPosY();

      for(Y = 1; Y <= 3; Y++){
        for(X = 1; X <= 4*K; X++){
          cube.setPosX(X);
          cube.setPosY(Y);
          if(cube.isValid(copyOfTable) && cube.isFree(copyOfTable)){
            this.setCubes(currentTable);

            moves.add(this);

            moves.get(0).printTable();

          }else{
            cube.setPosX(backupX);
            cube.setPosY(backupY);
            continue;
          }
        }
      }
    }

    moves.get(anyIndex).printTable();

    return moves;
  }
}
// main is in a different file
main(){
  ArrayList<Table> moves = new ArrayList<Table>();
  Table table = new Table();
  
  moves = table.generateMoves();
}

What I want is the moves.add(this) to add the current state of the board in the moves arraylist, so that I can return all the tables (states) that are possible to be generated by the given table.

The problem is that the first printTable() (inside the 3rd for loop), prints every state that can be generated. Not only the first one that I ask it for.

The second printTable() (above the return statement), no matter the index, only prints the first state of the table.

What am I missing here?

I tried the above code

5
  • this doesn't reference any persistent state but only a reference to the instance of Table you are using. Commented Jun 25, 2023 at 12:56
  • 3
    To really "save" the state, you would need to design you Table class to be immutable and create new Tables when the state changes. Commented Jun 25, 2023 at 12:58
  • @ValerijDobler I created a new table, set its state and added it to moves arraylist but to no avail. Do I really need to make my whole class final? Commented Jun 25, 2023 at 20:58
  • 1
    You don’t necessarily need to make the whole class final, but you do need to avoid changing data that you don’t want to change. Commented Jun 26, 2023 at 1:17
  • 2
    Immutable means, that the class and its fields are not changed not internally or externally. This means, that if your fields contain collections, you have to copy them defensively before returning. Making the class final also helps that it would be extended with a mutable state. Take a look at jdk15 records. Commented Jun 26, 2023 at 8:12

2 Answers 2

0

"... What am I missing here? ..."

Your code is correct, you are just misinterpreting the context of the generateMoves method.
The returned ArrayList will not persist, for each call—there will be a new ArrayList per call.

This type of method will need to be placed within a different class.
In which, you can utilize a ArrayList class field, to populate a list.

Consider the following.
By passing the Table object to generateMoves, you can populate the class field moves.

class TableUtil {
    static ArrayList<Table> moves = new ArrayList<>();

    static ArrayList<Table> getMoves() {
        return moves;
    }
    
    static void generateMoves(Table table) {
        ArrayList<Cube> currentTable = table.getCubes();
        ArrayList<Cube> copyOfTable = new ArrayList<Cube>(currentTable);

        int X = 1;
        int Y = 1;
        int K = table.getK();

        for(Cube cube : currentTable){
            int backupX = cube.getPosX();
            int backupY = cube.getPosY();

            for(Y = 1; Y <= 3; Y++){
                for(X = 1; X <= 4*K; X++){
                    cube.setPosX(X);
                    cube.setPosY(Y);
                    if(cube.isValid(copyOfTable) && cube.isFree(copyOfTable)){
                        table.setCubes(currentTable);

                        moves.add(table);

                        moves.get(0).printTable();

                    }else{
                        cube.setPosX(backupX);
                        cube.setPosY(backupY);
                        continue;
                    }
                }
            }
        }

        moves.get(table.anyIndex).printTable();
    }
}

Create your Table objects, and then call the generateMoves method, for each instance.

For example,

Table tableA = new Table();
Table tableB = new Table();
Table tableC = new Table();

TableUtil.generateMoves(tableA);
TableUtil.generateMoves(tableB);
TableUtil.generateMoves(tableC);

ArrayList<Table> moves = TableUtil.getMoves();
Sign up to request clarification or add additional context in comments.

6 Comments

is this considered good practice, or am I in an extreme case where the line is blurred?
Also, since we have a static ArrayList moves, why do we need to create an other normal one inside the generateMoves method?
@SteliosP.98, yes, of course there are many ways to harness the data, although this design is referred to as a "utility class". en.wikipedia.org/wiki/Helper_class.
@SteliosP.98, in terms of the static moves, you're right, I forgot to remove the local variable. So, you'll want to be appending the class field, rather.
@SteliosP.98, I'm not sure I entirely understand the application you're creating. I recommend setting a breakpoint on the first for-loop and stepping through each line. I'm sure you'll locate the inaccuracy. Additionally, you can override the toString method for both Table and Cube, which would allow you to print out the list with System.out.println(moves), or ...println(currentTable).
|
0

I don't know why (please someone explain that to me), but after i spent a very long time trial-and-erroring, the solution is to make a method that creates a "deep" copy of the ArrayList in question like so:

private ArrayList<Cube> createCopyOfTable(ArrayList<Cube> toCopy){
        ArrayList<Cube> copy = new ArrayList<>();

        for(Cube cube : toCopy){
            Cube _cube = new Cube(cube.getID(), cube.getPosX(), cube.getPosY());
            copy.add(_cube);
        }

        return copy;
    }

and the updated part on my generateMoves() method:

.
.
.
for(X = 1; X <= 4*K; X++){
    cube.setPosX(X);
    cube.setPosY(Y);

    ArrayList<Cube> copyOfTable = createCopyOfTable(currentTable);

    if(cube.isValid(copyOfTable) && cube.isFree(copyOfTable)){
        Table table = new Table(K);
        table.setCubes(copyOfTable);
                        
        this.moves.add(table);
    }else{
        cube.setPosX(backupX);
        cube.setPosY(backupY);
        continue;
    }
}
.
.
.

1 Comment

ArrayList clone() method is used to create a shallow copy of the list. In the new list, only object references are copied. If we change the object state inside the first ArrayList, then the changed object state will be reflected in the cloned ArrayList as well. If we want to keep the contents separately from source list to the target list, then deep copy is required.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.