package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.PriorityQueue; import java.util.Set; import it.unimi.dsi.fastutil.Pair; /** * A utility class that provides methods to solve the Boulder puzzle using the A* search algorithm. * The BoulderSolver class is responsible for finding the shortest path from the starting position * to the target position by exploring possible moves and evaluating their costs. */ public class BoulderSolver { /** * Finds the shortest path to solve the Boulder puzzle using the A* search algorithm. * * @param initialStates The list of initial game states from which to start the search. * @return A list of coordinates representing the shortest path to solve the puzzle, * or null if no solution is found within the maximum number of iterations. */ public static List aStarSolve(List initialStates) { Set visited = new HashSet<>(); PriorityQueue>> queue = new PriorityQueue<>(new AStarComparator()); for (GameState initialState : initialStates) { queue.add(Pair.of(initialState, new ArrayList<>())); } int maxIterations = 10000; int iterations = 0; while (!queue.isEmpty() && iterations < maxIterations) { Pair> pair = queue.poll(); GameState state = pair.left(); List path = pair.right(); if (state.isSolved()) { return path; } if (visited.contains(state)) { continue; } visited.add(state); int[] currentCoord = {state.playerX, state.playerY}; path.add(currentCoord); for (int[] direction : new int[][]{{-1, 0}, {0, -1}, {0, 1}, {1, 0}}) { GameState newState = new GameState(state.grid, state.playerX, state.playerY); if (newState.movePlayer(direction[0], direction[1])) { queue.add(Pair.of(newState, new ArrayList<>(path))); } } iterations++; } return null; } /** * A comparator used to compare game states based on their A* search cost. * States with lower costs are prioritized for exploration. */ private static class AStarComparator implements Comparator>> { /** * Compares two pairs of game states and their associated paths based on their costs. * * @param a The first pair to compare. * @param b The second pair to compare. * @return A negative integer if a has a lower cost than b, * a positive integer if a has a higher cost than b, * or zero if both have the same cost. */ @Override public int compare(Pair> a, Pair> b) { int costA = a.right().size() + a.left().heuristic(); int costB = b.right().size() + b.left().heuristic(); return Integer.compare(costA, costB); } } /** * Represents the game state for the Boulder puzzle, including the current grid configuration * and the position of the theoretical player. */ public static class GameState { private final char[][] grid; private int playerX; private int playerY; /** * Constructs a new game state with the specified grid and theoretical player position. * * @param grid The grid representing the Boulder puzzle configuration. * @param playerX The x-coordinate of the player's position. * @param playerY The y-coordinate of the player's position. */ public GameState(char[][] grid, int playerX, int playerY) { this.grid = copyGrid(grid); this.playerX = playerX; this.playerY = playerY; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; GameState gameState = (GameState) obj; return Arrays.deepEquals(grid, gameState.grid) && playerX == gameState.playerX && playerY == gameState.playerY; } @Override public int hashCode() { int result = Arrays.deepHashCode(grid); result = 31 * result + playerX; result = 31 * result + playerY; return result; } /** * Moves the theoretical player in the specified direction and updates the game state accordingly. * * @param dx The change in x-coordinate (horizontal movement). * @param dy The change in y-coordinate (vertical movement). * @return true if the move is valid and the player is moved, false otherwise. */ public boolean movePlayer(int dx, int dy) { int newX = playerX + dx; int newY = playerY + dy; if (isValidPosition(newX, newY)) { if (grid[newX][newY] == 'B') { int nextToBoxX = newX + dx; int nextToBoxY = newY + dy; if (isValidPosition(nextToBoxX, nextToBoxY) && grid[nextToBoxX][nextToBoxY] == '.') { grid[newX][newY] = '.'; grid[nextToBoxX][nextToBoxY] = 'B'; playerX = newX; playerY = newY; return true; } } else { playerX = newX; playerY = newY; return true; } } return false; } private boolean isValidPosition(int x, int y) { return x >= 0 && y >= 0 && x < grid.length && y < grid[0].length; } /** * Checks if the puzzle is solved, i.e., if the player is positioned on the target BoulderObject. * * @return true if the theoretical puzzle is solved, false otherwise. */ public boolean isSolved() { return grid[playerX][playerY] == 'T'; } /** * Calculates the heuristic value for the current game state, representing the estimated * distance from the player's position to the target BoulderObject. * * @return The heuristic value for the current game state. */ public int heuristic() { // should be improved maybe prioritize empty path first for (int i = 0; i < grid.length; i++) { for (int j = 0; j < grid[0].length; j++) { if (grid[i][j] == 'T') { return Math.abs(playerX - i) + Math.abs(playerY - j); } } } return Integer.MAX_VALUE; } /** * Creates a deep copy of the grid array to avoid modifying the original grid. * * @param original The original grid array to copy. * @return A deep copy of the original grid array. */ private char[][] copyGrid(char[][] original) { char[][] copy = new char[original.length][]; for (int i = 0; i < original.length; i++) { copy[i] = original[i].clone(); } return copy; } } }