diff --git a/runelite-api/src/main/java/net/runelite/api/InventoryID.java b/runelite-api/src/main/java/net/runelite/api/InventoryID.java index 53f5504709..de07152082 100644 --- a/runelite-api/src/main/java/net/runelite/api/InventoryID.java +++ b/runelite-api/src/main/java/net/runelite/api/InventoryID.java @@ -27,7 +27,8 @@ package net.runelite.api; public enum InventoryID { INVENTORY(93), - EQUIPMENT(94); + EQUIPMENT(94), + PUZZLE_BOX(140); private final int id; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index 58085d4827..f77a94a7dd 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -62,6 +62,7 @@ public class WidgetID public static final int BARROWS_REWARD_GROUP_ID = 155; public static final int MOTHERLODE_MINE_GROUP_ID = 382; public static final int EXPERIENCE_DROP_GROUP_ID = 122; + public static final int PUZZLE_BOX_GROUP_ID = 306; static class WorldMap { @@ -278,4 +279,9 @@ public class WidgetID static final int DROP_6 = 20; static final int DROP_7 = 21; } + + static class PuzzleBox + { + static final int VISIBLE_BOX = 4; + } } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 7632596ec2..c1c415e847 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -165,7 +165,9 @@ public enum WidgetInfo QUEST_COMPLETED(WidgetID.QUEST_COMPLETED_GROUP_ID, 0), QUEST_COMPLETED_NAME_TEXT(WidgetID.QUEST_COMPLETED_GROUP_ID, WidgetID.QuestCompleted.NAME_TEXT), - MOTHERLODE_MINE(WidgetID.MOTHERLODE_MINE_GROUP_ID, 0); + MOTHERLODE_MINE(WidgetID.MOTHERLODE_MINE_GROUP_ID, 0), + + PUZZLE_BOX(WidgetID.PUZZLE_BOX_GROUP_ID, WidgetID.PuzzleBox.VISIBLE_BOX); private final int groupId; private final int childId; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolver.java new file mode 100644 index 0000000000..ac68e79661 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolver.java @@ -0,0 +1,346 @@ +/* + * This code file is a derivative of code by Jaap Scherphuis . + * The original code can be found in the page source of https://www.jaapsch.net/puzzles/javascript/fifteenj.htm. + * + * Quote copied verbatim from the creator's grant to use this code in RuneLite: + * + * "You have my permission to incorporate, adapt, and distribute any part + * of my 15-puzzle javascript code as part of your free, open source project" + * + * + * The adaptions made to Jaap Scherphuis' code is copyrighted and licensed according to: + * + * + * Copyright (c) 2018, UniquePassive + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.puzzlesolver; + +import java.util.ArrayList; +import java.util.List; + +/** + * This 5x5 sliding puzzle solver algorithm is based on https://www.jaapsch.net/puzzles/fifteen.htm. + */ +public class PuzzleSolver +{ + public static final int BLANK_TILE_VALUE = -1; + public static final int DIMENSION = 5; + private static final int SIZE = DIMENSION * DIMENSION - 1; + + private int[] tiles = new int[SIZE + DIMENSION]; + private List seq = new ArrayList<>(); + + private int blankX; + private int blankY; + + private int mode; + + public PuzzleSolver(int[] arr) + { + System.arraycopy(arr, 0, tiles, 0, arr.length); + + for (int i = 0; i < arr.length; i++) + { + if (arr[i] == BLANK_TILE_VALUE) + { + blankX = i % DIMENSION; + blankY = i / DIMENSION; + break; + } + } + } + + public boolean hasNext() + { + for (int i = SIZE; i >= 0; i--) + { + if (tiles[i] != i) + { + return true; + } + } + return false; + } + + public void next() + { + if (mode == 0) + { + mode = 3; + seq.clear(); + + int[] oldTiles = new int[tiles.length]; + System.arraycopy(tiles, 0, oldTiles, 0, tiles.length); + int oldBlankX = blankX; + int oldBlankY = blankY; + + //restore top rows + int rr = 0; + for (int r = 0; r < DIMENSION - 2; r++) + { + for (int c = 0; c < DIMENSION; c++) + { + movePiece(rr + c, r, c); + } + rr += DIMENSION; + } + + //restore left columns + for (int c = 0; c < DIMENSION - 2; c++) + { + //restore top tile of column. + movePiece(rr, DIMENSION - 2, c); + //restore bottom tile of column + if (blankX == c) push(3); //fill destination spot + if (tiles[rr + DIMENSION] != rr + DIMENSION) + { + movePiece(rr + DIMENSION, DIMENSION - 1, c + 1); + if (blankY != DIMENSION - 1) + { //0=right, 1=down, 2=up, 3=left + //A.X or AX. + //XBX XBX + if (blankX == c + 1) push(3); + push(2); + } + //AXX + //XB. + while (blankX > c + 2) + { + push(0); + } + push(0, 0, 1, 3, 2, 3, 1, 0, 0, 2, 3); + } + rr++; + } + + //last 2x2 square + if (blankX < DIMENSION - 1) push(3); + if (blankY < DIMENSION - 1) push(2); + rr = SIZE - DIMENSION - 1; + if (tiles[rr] == rr + 1) push(1, 0, 2, 3); + if (tiles[rr] == rr + DIMENSION) push(0, 1, 3, 2); + + //restore pieces; + System.arraycopy(oldTiles, 0, tiles, 0, tiles.length); + blankX = oldBlankX; + blankY = oldBlankY; + } + + if (mode >= 3) + { + if (!seq.isEmpty()) + { + Integer c = seq.get(0); + for (int i = 1; i < seq.size(); i++) + { + seq.set(i - 1, seq.get(i)); + } + seq.remove(seq.size() - 1); + doMove(c); + } + + if (seq.isEmpty()) + { + mode = 0; + } + } + } + + private void movePiece(int p, int y, int x) + { + //moves piece p to position y,x without disturbing previously placed pieces. + int c = -1; + int i = 0; + int j = 0; + for (; i < DIMENSION; i++) + { + for (j = 0; j < DIMENSION; j++) + { + c++; + if (tiles[c] == p) break; + } + if (tiles[c] == p) break; + } + + //Move piece to same column //0=right, 1=down, 2=up, 3=left + if (j < x && blankY == y) push(2); // move blank down if it might disturb solved pieces. + while (j > x) + { + //move piece to left + //First move blank to left hand side of it + if (blankY == i && blankX > j) + { //if blank on wrong side of piece + if (i == DIMENSION - 1) push(1); + else push(2); //then move it to other row + } + while (blankX >= j) push(0); // move blank to column left of piece + while (blankX < j - 1) push(3); + while (blankY < i) push(2); // move blank to same row as piece + while (blankY > i) push(1); + push(3); // move piece to left. + j--; + } + while (j < x) + { + //move piece to right + //First move blank to right hand side of it + if (blankY == i && blankX < j) + { + if (i == DIMENSION - 1) push(1); + else push(2); + } + while (blankX <= j) push(3); + while (blankX > j + 1) push(0); + while (blankY < i) push(2); + while (blankY > i) push(1); + push(0); + j++; + } + + //Move piece up to same row //0=right, 1=down, 2=up, 3=left + while (i > y) + { + if (y < i - 1) + { + while (blankY < i - 1) push(2); + if (blankX == j) push(j == DIMENSION - 1 ? 0 : 3); + while (blankY > i - 1) push(1); + while (blankX < j) push(3); + while (blankX > j) push(0); + push(2); + } + else + { + if (j != DIMENSION - 1) + { + if (blankY == i) push(2); + while (blankX < j + 1) push(3); + while (blankX > j + 1) push(0); + while (blankY > i - 1) push(1); + while (blankY < i - 1) push(2); + push(0, 2); + } + else + { + if (blankY < i && blankX == j) + { + while (blankY < i) push(2); + } + else + { + while (blankY > i + 1) push(1); + while (blankY < i + 1) push(2); + while (blankX < j) push(3); + while (blankX > j) push(0); + push(1, 1, 0, 2, 3, 2, 0, 1, 1, 3, 2); + } + } + } + i--; + } + while (i < y) + { + //move piece downwards + //First move blank below tile + if (blankX == j && blankY < i) + { + if (j == DIMENSION - 1) push(0); + else push(3); + } + while (blankY > i + 1) push(1); + while (blankY < i + 1) push(2); + while (blankX < j) push(3); + while (blankX > j) push(0); + push(1); + i++; + } + } + + private void push(int... moves) + { + for (int c : moves) + { + if (!seq.isEmpty() && seq.get(seq.size() - 1) + c == 3) + { + seq.remove(seq.size() - 1); + } + else + { + seq.add(c); + } + + doMove(c); + } + } + + private void doMove(int m) + { + int d = blankX + blankY * DIMENSION; + + if (m == 0) + { + // RIGHT + tiles[d] = tiles[d - 1]; + tiles[d - 1] = SIZE; + blankX--; + } + else if (m == 1) + { + // DOWN + tiles[d] = tiles[d - DIMENSION]; + tiles[d - DIMENSION] = SIZE; + blankY--; + } + else if (m == 2) + { + // UP + tiles[d] = tiles[d + DIMENSION]; + tiles[d + DIMENSION] = SIZE; + blankY++; + } + else if (m == 3) + { + // LEFT + tiles[d] = tiles[d + 1]; + tiles[d + 1] = SIZE; + blankX++; + } + } + + public int[] getTiles() + { + return tiles; + } + + public int getBlankX() + { + return blankX; + } + + public int getBlankY() + { + return blankY; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverConfig.java new file mode 100644 index 0000000000..d6c2252822 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018, UniquePassive + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.puzzlesolver; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = "puzzlesolver", + name = "Puzzle Solver", + description = "Shows you where to press to solve puzzle boxes" +) +public interface PuzzleSolverConfig extends Config +{ + @ConfigItem( + keyName = "displaySolution", + name = "Display solution", + description = "Display a solution to the puzzle" + ) + default boolean displaySolution() + { + return true; + } + + @ConfigItem( + keyName = "displayRemainingMoves", + name = "Display remaining moves", + description = "Add a text line above puzzle boxes displaying the amount of remaining moves" + ) + default boolean displayRemainingMoves() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverOverlay.java new file mode 100644 index 0000000000..365eaf3bd1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverOverlay.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2018, UniquePassive + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.puzzlesolver; + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import javax.imageio.ImageIO; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import static net.runelite.client.plugins.puzzlesolver.PuzzleSolver.BLANK_TILE_VALUE; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.BackgroundComponent; +import net.runelite.client.ui.overlay.components.TextComponent; + +@Slf4j +public class PuzzleSolverOverlay extends Overlay +{ + private static final int MOVES_LEFT_WIDTH = 100; + private static final int MOVES_LEFT_OFFSET_Y = 50; + private static final int MOVES_LEFT_TOP_BORDER = 2; + private static final int MOVES_LEFT_BOTTOM_BORDER = 2; + + private static final int PUZZLE_TILE_SIZE = 39; + + private final Client client; + private final PuzzleSolverConfig config; + + private PuzzleSolver solver; + private Deque nextMoves; + private int[] cachedItems; + + private BufferedImage downArrow; + private BufferedImage upArrow; + private BufferedImage leftArrow; + private BufferedImage rightArrow; + + @Inject + public PuzzleSolverOverlay(Client client, PuzzleSolverConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + setPriority(OverlayPriority.HIGH); + this.client = client; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics, Point parent) + { + if ((!config.displaySolution() && !config.displayRemainingMoves()) + || client.getGameState() != GameState.LOGGED_IN) + { + return null; + } + + ItemContainer container = client.getItemContainer(InventoryID.PUZZLE_BOX); + + if (container == null) + { + return null; + } + + Widget puzzleBox = client.getWidget(WidgetInfo.PUZZLE_BOX); + + if (puzzleBox == null) + { + return null; + } + + int[] itemIds = getItemIds(container); + boolean shouldCache = false; + + if (nextMoves != null) + { + Integer nextMove = nextMoves.peek(); + + // If this is true, nextMove is actually the current state (player has moved the empty tile), + // so let's throw away this step and try to find the actual next move. + while (nextMove != null && itemIds[nextMove] == BLANK_TILE_VALUE) + { + nextMoves.pop(); // pop the current move + nextMove = nextMoves.peek(); + shouldCache = true; + } + + // If nextMove is null, the puzzle is either finished + // or the player has not followed the instructions. + if (nextMove != null) + { + net.runelite.api.Point puzzleBoxLocation = puzzleBox.getCanvasLocation(); + + if (config.displayRemainingMoves()) + { + int x = puzzleBoxLocation.getX() + puzzleBox.getWidth() / 2 - MOVES_LEFT_WIDTH / 2; + int y = puzzleBoxLocation.getY() - MOVES_LEFT_OFFSET_Y; + + String movesLeftString = "Moves left: " + nextMoves.size(); + + FontMetrics fm = graphics.getFontMetrics(); + int height = MOVES_LEFT_TOP_BORDER + fm.getHeight() + MOVES_LEFT_BOTTOM_BORDER; + + BackgroundComponent backgroundComponent = new BackgroundComponent(); + backgroundComponent.setRectangle(new Rectangle(x, y, MOVES_LEFT_WIDTH, height)); + backgroundComponent.render(graphics, parent); + + int textOffsetX = (MOVES_LEFT_WIDTH - fm.stringWidth(movesLeftString)) / 2; + int textOffsetY = fm.getHeight(); + + TextComponent textComponent = new TextComponent(); + textComponent.setPosition(new Point(x + textOffsetX, y + textOffsetY)); + textComponent.setText(movesLeftString); + textComponent.render(graphics, parent); + } + + if (config.displaySolution()) + { + int i = 0; + int lastBlankX = 0; + int lastBlankY = 0; + + // First find the current blank tile position + for (int j = 0; j < itemIds.length; j++) + { + if (itemIds[j] == BLANK_TILE_VALUE) + { + lastBlankX = j % PuzzleSolver.DIMENSION; + lastBlankY = j / PuzzleSolver.DIMENSION; + break; + } + } + + for (Integer futureMove : nextMoves) + { + int blankX = futureMove % PuzzleSolver.DIMENSION; + int blankY = futureMove / PuzzleSolver.DIMENSION; + + int xDelta = blankX - lastBlankX; + int yDelta = blankY - lastBlankY; + + BufferedImage arrow; + if (xDelta > 0) + { + arrow = getRightArrow(); + } + else if (xDelta < 0) + { + arrow = getLeftArrow(); + } + else if (yDelta > 0) + { + arrow = getDownArrow(); + } + else + { + arrow = getUpArrow(); + } + + int x = puzzleBoxLocation.getX() + blankX * PUZZLE_TILE_SIZE + + PUZZLE_TILE_SIZE / 2 - arrow.getWidth() / 2; + + int y = puzzleBoxLocation.getY() + blankY * PUZZLE_TILE_SIZE + + PUZZLE_TILE_SIZE / 2 - arrow.getHeight() / 2; + + OverlayUtil.renderImageLocation(graphics, new net.runelite.api.Point(x, y), arrow); + + lastBlankX = blankX; + lastBlankY = blankY; + + if (++i == 3) + { + break; + } + } + } + } + } + + if (solver == null || cachedItems == null || (!shouldCache && !Arrays.equals(cachedItems, itemIds))) + { + nextMoves = solve(itemIds); + shouldCache = true; + } + + if (shouldCache) + { + cacheItems(itemIds); + } + + return null; + } + + private int[] getItemIds(ItemContainer container) + { + return Arrays + .stream(container.getItems()) + .mapToInt(Item::getId) + .toArray(); + } + + private void cacheItems(int[] items) + { + cachedItems = new int[items.length]; + System.arraycopy(items, 0, cachedItems, 0, cachedItems.length); + } + + private Deque solve(int[] items) + { + Deque steps = new ArrayDeque<>(); + + solver = new PuzzleSolver(convertToSolverFormat(items)); + + while (solver.hasNext()) + { + solver.next(); + steps.add(solver.getBlankX() + solver.getBlankY() * PuzzleSolver.DIMENSION); + } + + return steps; + } + + /** + * This depends on there being no gaps in between item ids in puzzles. + */ + private int[] convertToSolverFormat(int[] items) + { + int lowestId = Integer.MAX_VALUE; + + int[] convertedItems = new int[items.length]; + + for (int id : items) + { + if (id == BLANK_TILE_VALUE) + { + continue; + } + + if (lowestId > id) + { + lowestId = id; + } + } + + for (int i = 0; i < items.length; i++) + { + if (items[i] != BLANK_TILE_VALUE) + { + convertedItems[i] = items[i] - lowestId; + } + else + { + convertedItems[i] = BLANK_TILE_VALUE; + } + } + + return convertedItems; + } + + private BufferedImage getDownArrow() + { + if (downArrow == null) + { + try + { + InputStream in = PuzzleSolverOverlay.class.getResourceAsStream("arrow.png"); + downArrow = ImageIO.read(in); + } + catch (IOException e) + { + log.warn("Error loading image", e); + } + } + return downArrow; + } + + private BufferedImage getUpArrow() + { + if (upArrow == null) + { + upArrow = getRotatedImage(getDownArrow(), Math.PI); + } + return upArrow; + } + + private BufferedImage getLeftArrow() + { + if (leftArrow == null) + { + leftArrow = getRotatedImage(getDownArrow(), Math.PI / 2); + } + return leftArrow; + } + + private BufferedImage getRightArrow() + { + if (rightArrow == null) + { + rightArrow = getRotatedImage(getDownArrow(), 3 * Math.PI / 2); + } + return rightArrow; + } + + private BufferedImage getRotatedImage(BufferedImage image, double theta) + { + AffineTransform transform = new AffineTransform(); + transform.rotate(theta, image.getWidth() / 2, image.getHeight() / 2); + AffineTransformOp transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + return transformOp.filter(image, null); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java new file mode 100644 index 0000000000..a93a130f3a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, UniquePassive + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.puzzlesolver; + +import com.google.inject.Binder; +import com.google.inject.Provides; +import javax.inject.Inject; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor( + name = "Puzzle solver plugin" +) +public class PuzzleSolverPlugin extends Plugin +{ + @Inject + private PuzzleSolverOverlay puzzleSolverOverlay; + + @Override + public void configure(Binder binder) + { + binder.bind(PuzzleSolverOverlay.class); + } + + @Provides + PuzzleSolverConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(PuzzleSolverConfig.class); + } + + @Override + public PuzzleSolverOverlay getOverlay() + { + return puzzleSolverOverlay; + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/puzzlesolver/arrow.png b/runelite-client/src/main/resources/net/runelite/client/plugins/puzzlesolver/arrow.png new file mode 100644 index 0000000000..904f3c296e Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/puzzlesolver/arrow.png differ diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverTest.java new file mode 100644 index 0000000000..c4e2b1a6c1 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, UniquePassive + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.puzzlesolver; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class PuzzleSolverTest +{ + private static final int[] TEST_STATE = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 15, 8, 23, 22, 16, -1, 19, 12, 17, 14, 9, 11, 10, 18, 21, 13, 20}; + private static final int[] FINISHED_STATE = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; + + @Test + public void testSolver() + { + PuzzleSolver solver = new PuzzleSolver(TEST_STATE); + + assertEquals(solver.hasNext(), true); + + while (solver.hasNext()) + { + solver.next(); + } + + for (int i = 0; i < FINISHED_STATE.length; i++) + { + assertEquals(solver.getTiles()[i], FINISHED_STATE[i]); + } + } +}