-
-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Boulder Solver #540
Merged
kevinthegreat1
merged 2 commits into
SkyblockerMod:master
from
LifeIsAParadox:boulder-puzzle
Feb 15, 2024
Merged
Boulder Solver #540
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
217 changes: 217 additions & 0 deletions
217
src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; | ||
|
||
import de.hysky.skyblocker.config.SkyblockerConfigManager; | ||
import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonPuzzle; | ||
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; | ||
import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; | ||
import de.hysky.skyblocker.utils.render.RenderHelper; | ||
import de.hysky.skyblocker.utils.render.title.Title; | ||
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; | ||
import net.minecraft.block.Block; | ||
import net.minecraft.block.BlockState; | ||
import net.minecraft.block.Blocks; | ||
import net.minecraft.client.MinecraftClient; | ||
import net.minecraft.client.world.ClientWorld; | ||
import net.minecraft.util.DyeColor; | ||
import net.minecraft.util.Formatting; | ||
import net.minecraft.util.math.BlockPos; | ||
import net.minecraft.util.math.Box; | ||
import net.minecraft.util.math.Vec3d; | ||
import net.minecraft.world.BlockView; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
public class Boulder extends DungeonPuzzle { | ||
private static final Logger LOGGER = LoggerFactory.getLogger(Boulder.class.getName()); | ||
private static final Boulder INSTANCE = new Boulder(); | ||
private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); | ||
private static final float[] ORANGE_COLOR_COMPONENTS = DyeColor.ORANGE.getColorComponents(); | ||
private static final int BASE_Y = 65; | ||
static Vec3d[] linePoints; | ||
static Box boundingBox; | ||
|
||
private Boulder() { | ||
super("boulder", "boxes-room"); | ||
} | ||
|
||
public static void init() { | ||
} | ||
|
||
@Override | ||
public void tick(MinecraftClient client) { | ||
|
||
if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || client.world == null || !DungeonManager.isCurrentRoomMatched()) { | ||
return; | ||
} | ||
|
||
Room room = DungeonManager.getCurrentRoom(); | ||
|
||
BlockPos chestPos = new BlockPos(15, BASE_Y, 29); | ||
BlockPos start = new BlockPos(25, BASE_Y, 25); | ||
BlockPos end = new BlockPos(5, BASE_Y, 8); | ||
// Create a target BoulderObject for the puzzle | ||
BoulderObject target = new BoulderObject(chestPos.getX(), chestPos.getX(), chestPos.getZ(), "T"); | ||
// Create a BoulderBoard representing the puzzle's grid | ||
BoulderBoard board = new BoulderBoard(8, 7, target); | ||
|
||
// Populate the BoulderBoard grid with BoulderObjects based on block types in the room | ||
int column = 1; | ||
for (int z = start.getZ(); z > end.getZ(); z--) { | ||
int row = 0; | ||
for (int x = start.getX(); x > end.getX(); x--) { | ||
if (Math.abs(start.getX() - x) % 3 == 1 && Math.abs(start.getZ() - z) % 3 == 1) { | ||
String blockType = getBlockType(client.world, x, BASE_Y, z); | ||
board.placeObject(column, row, new BoulderObject(x, BASE_Y, z, blockType)); | ||
row++; | ||
} | ||
} | ||
if (row == board.getWidth()) { | ||
column++; | ||
} | ||
} | ||
|
||
// Generate initial game states for the A* solver | ||
char[][] boardArray = board.getBoardCharArray(); | ||
List<BoulderSolver.GameState> initialStates = Arrays.asList( | ||
new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 0), | ||
new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 1), | ||
new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 2), | ||
new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 3), | ||
new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 4), | ||
new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 5), | ||
new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 6) | ||
); | ||
|
||
// Solve the puzzle using the A* algorithm | ||
List<int[]> solution = BoulderSolver.aStarSolve(initialStates); | ||
|
||
if (solution != null) { | ||
linePoints = new Vec3d[solution.size()]; | ||
int index = 0; | ||
// Convert solution coordinates to Vec3d points for rendering | ||
for (int[] coord : solution) { | ||
int x = coord[0]; | ||
int y = coord[1]; | ||
// Convert relative coordinates to actual coordinates | ||
linePoints[index++] = Vec3d.ofCenter(room.relativeToActual(board.getObject3DPosition(x, y))); | ||
} | ||
|
||
BlockPos button = null; | ||
if (linePoints != null && linePoints.length > 0) { | ||
// Check for buttons along the path of the solution | ||
for (int i = 0; i < linePoints.length - 1; i++) { | ||
Vec3d point1 = linePoints[i]; | ||
Vec3d point2 = linePoints[i + 1]; | ||
button = checkForButtonBlocksOnLine(client.world, point1, point2); | ||
if (button != null) { | ||
// If a button is found, calculate its bounding box | ||
boundingBox = getBlockBoundingBox(client.world, button); | ||
break; | ||
} | ||
} | ||
if (button == null){ | ||
// If no button is found along the path the puzzle is solved; reset the puzzle | ||
reset(); | ||
} | ||
} | ||
} else { | ||
// If no solution is found, display a title message and reset the puzzle | ||
Title title = new Title("skyblocker.dungeons.puzzle.boulder.noSolution", Formatting.GREEN); | ||
RenderHelper.displayInTitleContainerAndPlaySound(title, 15); | ||
reset(); | ||
} | ||
} | ||
|
||
/** | ||
* Retrieves the type of block at the specified position in the world. | ||
* If the block is Birch or Jungle plank, it will return "B"; otherwise, it will return ".". | ||
* | ||
* @param world The client world. | ||
* @param x The x-coordinate of the block. | ||
* @param y The y-coordinate of the block. | ||
* @param z The z-coordinate of the block. | ||
* @return The type of block at the specified position. | ||
*/ | ||
public static String getBlockType(ClientWorld world, int x, int y, int z) { | ||
Block block = world.getBlockState(DungeonManager.getCurrentRoom().relativeToActual(new BlockPos(x, y, z))).getBlock(); | ||
return (block == Blocks.BIRCH_PLANKS || block == Blocks.JUNGLE_PLANKS) ? "B" : "."; | ||
} | ||
|
||
/** | ||
* Checks for blocks along the line between two points in the world. | ||
* Returns the position of a block if it found a button on the line, if any. | ||
* | ||
* @param world The client world. | ||
* @param point1 The starting point of the line. | ||
* @param point2 The ending point of the line. | ||
* @return The position of the block found on the line, or null if no block is found. | ||
*/ | ||
private static BlockPos checkForButtonBlocksOnLine(ClientWorld world, Vec3d point1, Vec3d point2) { | ||
double x1 = point1.getX(); | ||
double y1 = point1.getY() + 1; | ||
double z1 = point1.getZ(); | ||
|
||
double x2 = point2.getX(); | ||
double y2 = point2.getY() + 1; | ||
double z2 = point2.getZ(); | ||
|
||
int steps = (int) Math.max(Math.abs(x2 - x1), Math.max(Math.abs(y2 - y1), Math.abs(z2 - z1))); | ||
|
||
double xStep = (x2 - x1) / steps; | ||
double yStep = (y2 - y1) / steps; | ||
double zStep = (z2 - z1) / steps; | ||
|
||
|
||
for (int step = 0; step <= steps; step++) { | ||
double currentX = x1 + step * xStep; | ||
double currentY = y1 + step * yStep; | ||
double currentZ = z1 + step * zStep; | ||
|
||
BlockPos blockPos = BlockPos.ofFloored(currentX, currentY, currentZ); | ||
Block block = world.getBlockState(blockPos).getBlock(); | ||
|
||
if (block == Blocks.STONE_BUTTON) { | ||
return blockPos; | ||
} | ||
|
||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Retrieves the bounding box of a block in the world. | ||
* | ||
* @param world The client world. | ||
* @param pos The position of the block. | ||
* @return The bounding box of the block. | ||
*/ | ||
public static Box getBlockBoundingBox(BlockView world, BlockPos pos) { | ||
BlockState blockState = world.getBlockState(pos); | ||
return blockState.getOutlineShape(world, pos).getBoundingBox().offset(pos); | ||
} | ||
|
||
@Override | ||
public void render(WorldRenderContext context) { | ||
if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || !DungeonManager.isCurrentRoomMatched()) | ||
return; | ||
float alpha = 1.0f; | ||
float lineWidth = 5.0f; | ||
|
||
if (linePoints != null && linePoints.length > 0) { | ||
RenderHelper.renderLinesFromPoints(context, linePoints, ORANGE_COLOR_COMPONENTS, alpha, lineWidth, true); | ||
if (boundingBox != null) { | ||
RenderHelper.renderOutline(context, boundingBox, RED_COLOR_COMPONENTS, 5, false); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void reset() { | ||
super.reset(); | ||
linePoints = null; | ||
boundingBox = null; | ||
} | ||
} |
127 changes: 127 additions & 0 deletions
127
src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; | ||
|
||
import net.minecraft.util.math.BlockPos; | ||
import net.minecraft.util.math.Direction; | ||
|
||
/** | ||
* Represents the game board for the Boulder puzzle, managing the grid of BoulderObjects. | ||
* This class handles operations such as placing objects on the board, retrieving objects, | ||
* and generating a character representation of the game board. | ||
*/ | ||
public class BoulderBoard { | ||
private final int height; | ||
private final int width; | ||
private final BoulderObject[][] grid; | ||
|
||
/** | ||
* Constructs a BoulderBoard with the specified height, width, and target BoulderObject. | ||
* | ||
* @param height The height of the board. | ||
* @param width The width of the board. | ||
* @param target The target BoulderObject that needs to be reached to solve the puzzle. | ||
*/ | ||
public BoulderBoard(int height, int width, BoulderObject target) { | ||
this.height = height; | ||
this.width = width; | ||
this.grid = new BoulderObject[height][width]; | ||
|
||
int offsetX = target.x() - 23; | ||
int y = 65; | ||
|
||
for (int z = 0; z < width; z++) { | ||
if (z == width / 2) { | ||
grid[0][z] = target; | ||
} else { | ||
grid[0][z] = new BoulderObject(offsetX, y, z, "B"); | ||
} | ||
grid[height - 1][z] = new BoulderObject(24 - (3 * z), y, 6, "P"); | ||
} | ||
} | ||
|
||
/** | ||
* Retrieves the BoulderObject at the specified position on the board. | ||
* | ||
* @param x The x-coordinate of the position. | ||
* @param y The y-coordinate of the position. | ||
* @return The BoulderObject at the specified position, or null if no object is present. | ||
*/ | ||
public BoulderObject getObjectAtPosition(int x, int y) { | ||
return isValidPosition(x, y) ? grid[x][y] : null; | ||
} | ||
|
||
/** | ||
* Retrieves the 3D position of the BoulderObject at the specified position on the board. | ||
* | ||
* @param x The x-coordinate of the position. | ||
* @param y The y-coordinate of the position. | ||
* @return The BlockPos representing the 3D position of the BoulderObject, | ||
* or null if no object is present at the specified position. | ||
*/ | ||
public BlockPos getObject3DPosition(int x, int y) { | ||
BoulderObject object = getObjectAtPosition(x, y); | ||
return (object != null) ? object.get3DPosition().offset(Direction.Axis.Y, -1) : null; | ||
} | ||
|
||
/** | ||
* Places a BoulderObject at the specified position on the board. | ||
* | ||
* @param x The x-coordinate of the position. | ||
* @param y The y-coordinate of the position. | ||
* @param object The BoulderObject to place on the board. | ||
*/ | ||
public void placeObject(int x, int y, BoulderObject object) { | ||
grid[x][y] = object; | ||
} | ||
|
||
public int getHeight() { | ||
return height; | ||
} | ||
|
||
public int getWidth() { | ||
return width; | ||
} | ||
|
||
/** | ||
* Checks whether the specified position is valid within the bounds of the game board. | ||
* | ||
* @param x The x-coordinate of the position to check. | ||
* @param y The y-coordinate of the position to check. | ||
* @return {@code true} if the position is valid within the bounds of the board, {@code false} otherwise. | ||
*/ | ||
private boolean isValidPosition(int x, int y) { | ||
return x >= 0 && y >= 0 && x < height && y < width; | ||
} | ||
|
||
/** | ||
* Generates a character array representation of the game board. | ||
* Each character represents a type of BoulderObject or an empty space. | ||
* | ||
* @return A 2D character array representing the game board. | ||
*/ | ||
public char[][] getBoardCharArray() { | ||
char[][] boardCharArray = new char[height][width]; | ||
for (int x = 0; x < height; x++) { | ||
for (int y = 0; y < width; y++) { | ||
BoulderObject boulderObject = grid[x][y]; | ||
boardCharArray[x][y] = (boulderObject != null) ? boulderObject.type().charAt(0) : '.'; | ||
} | ||
} | ||
return boardCharArray; | ||
} | ||
|
||
/** | ||
* Prints the current state of the game board to the console. | ||
* Each character represents a type of BoulderObject or an empty space. | ||
*/ | ||
public void printBoard() { | ||
for (int x = 0; x < height; x++) { | ||
for (int y = 0; y < width; y++) { | ||
BoulderObject boulderObject = grid[x][y]; | ||
String displayChar = (boulderObject != null) ? boulderObject.type() : "."; | ||
System.out.print(displayChar); | ||
} | ||
System.out.println(); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't really matter but maybe something like |
||
|
||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; | ||
|
||
import net.minecraft.util.math.BlockPos; | ||
|
||
public record BoulderObject(int x, int y, int z, String type) { | ||
public BlockPos get3DPosition() { | ||
return new BlockPos(x, y, z); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lines need to be rendered one at a time for the line width and normals to not get messed up. (See the last part of
Waterboard#render
.)