If you want to introduce a Map in your project, that's doable.
As a key you can generate a String combined from x and y coordinates, and Boolean will be a convenient option as a value, if you don't need to represent other cases apart from coin is present or not.
The HashMap can entirely substitute the basement array in your code. In addition to a map you need to introduce two int fields that will represent the size in x and y directions.
You can prepopulate the map in the same way you've done it with the array. And instead of initializing all coordinates with the same default value, you can generate random starting values.
In the game-loop you can toggle the value mapped to a particular tile like that:
map.put(key, !map.get(key));
or this way by using Java 8 method compute():
hasCoinByTileKey.compute(key, (k, v) -> !v);
And to find out whether the tile contains the coin, use get():
if (map.get(key)) {
// do something
}
In regard to performance, it'll perform reasonably well, but slightly slower than an array-based solution. Because under the hood, HashMap is backed by the array. In short, each array element constitutes a bucket, which correspond to a particular range of hash-values. Map-entries will form a linked list, if several keys are mapped to the same bucket (after a certain threshold, the list will get transformed into a tree). The same will holds true for a HashSet, because the HashSet class is build on top of the HashMap.
If you replace an array with the HashMap your Basement class might look like that:
public class Basement {
public static void main(String[] args) {
new Basement(20, 20).update();
}
private static final int STEPS = 20;
private Map<String, Boolean> hasCoinByTileKey = new HashMap<>();
private List<Tile> path = new ArrayList<>();
private int rows;
private int cols;
private record Tile(int x, int y) {}
public Basement(int rows, int cols) {
this.rows = rows;
this.cols = cols;
Random random = new Random();
for (int row = 0; row < cols; row++) {
for (int col = 0; col < cols; col++) {
hasCoinByTileKey.put(getKey(row, col), random.nextBoolean());
}
}
}
public static String getKey(int row, int col) {
return row + "," + col;
}
public void update() {
path.clear(); // cleaning the previous keys
int x = rows / 2;
int y = cols / 2;
int orientation = 0;
/*
0 - up
1 - right
2 - left
3 - down
*/
for (int steps = 0; steps < STEPS; steps++) {
path.add(new Tile(x, y)); // updating the path will current coordinates
String key = getKey(x, y);
if (!hasCoinByTileKey.get(key)) {
hasCoinByTileKey.compute(key, (k, v) -> !v); // toggling the boolean value of the tile
if (orientation == 0) {
y += 1;
orientation = 1;
} else if (orientation == 1) {
x += 1;
orientation = 3;
} else if (orientation == 2) {
x -= 1;
orientation = 0;
} else {
y -= 1;
orientation = 2;
}
} else {
hasCoinByTileKey.compute(key, (k, v) -> !v); // toggling the boolean value of the tile
if (orientation == 0) {
y -= 1;
orientation = 2;
} else if (orientation == 1) {
x -= 1;
orientation = 0;
} else if (orientation == 2) {
x += 1;
orientation = 3;
} else {
y += 1;
orientation = 1;
}
}
}
System.out.println(path.size());
System.out.println(path);
this.print();
}
public void print() {
boolean[][] grid = new boolean[rows][cols];
for (Tile tile: path) {
grid[tile.x][tile.y] = true;
}
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
System.out.print(grid[row][col] ? 'a' : ' ');
}
System.out.println();
}
}
}
In order to print the path, a separate list is used to track the path durin the execution of update method. Object that hold coordinates is a record.
main() - demo
public static void main(String[] args) {
new Basement(20, 20).update();
}
Output - 20 steps (the pattern differs from your example because roughly a half of the tiles will be prepopulated with coins)
aaaa
aaaa
aaa
aaaaa
aaaa
aa