From 90d7f9c56ecea336f51091373f52ca468f0e3ed3 Mon Sep 17 00:00:00 2001 From: Lotto Date: Fri, 23 Feb 2018 22:58:42 +0100 Subject: [PATCH 1/7] puzzlesolver: add IDA* + manhattan distance solver --- .../puzzlesolver/solver/PuzzleSolver.java | 100 ++++++++++ .../puzzlesolver/solver/PuzzleState.java | 176 ++++++++++++++++++ .../solver/heuristics/Heuristic.java | 33 ++++ .../solver/heuristics/ManhattanDistance.java | 121 ++++++++++++ .../solver/pathfinding/IDAStar.java | 111 +++++++++++ .../solver/pathfinding/Pathfinder.java | 47 +++++ 6 files changed, 588 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/Heuristic.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/ManhattanDistance.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/IDAStar.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/Pathfinder.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java new file mode 100644 index 0000000000..2b0bbfcee8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018, Lotto + * 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.solver; + +import java.util.List; +import java.util.stream.Collectors; +import net.runelite.client.plugins.puzzlesolver.solver.pathfinding.Pathfinder; + +public class PuzzleSolver implements Runnable +{ + public static final int DIMENSION = 5; + + private Pathfinder pathfinder; + private PuzzleState startState; + + private List solution; + private int position; + private boolean failed = false; + + public PuzzleSolver(Pathfinder pathfinder, PuzzleState startState) + { + this.pathfinder = pathfinder; + this.startState = startState; + } + + public Integer getStep(int stepIdx) + { + if (stepIdx < 0 || stepIdx >= solution.size()) + { + return null; + } + + return solution.get(stepIdx); + } + + public int getStepCount() + { + return solution.size(); + } + + public boolean hasSolution() + { + return solution != null; + } + + public int getPosition() + { + return position; + } + + public void setPosition(int position) + { + this.position = position; + } + + public boolean hasFailed() + { + return failed; + } + + @Override + public void run() + { + List solution = pathfinder.computePath(startState); + + if (solution != null) + { + this.solution = solution.stream() + .map(PuzzleState::getEmptyPiece) + .collect(Collectors.toList()); + } + else + { + failed = true; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java new file mode 100644 index 0000000000..80f0415626 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2018, Lotto + * 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.solver; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import net.runelite.client.plugins.puzzlesolver.solver.heuristics.Heuristic; +import static net.runelite.client.plugins.puzzlesolver.solver.PuzzleSolver.DIMENSION; + +public class PuzzleState +{ + private PuzzleState parent; + + private int[] pieces; + private int emptyPiece = -1; + + private int h = -1; + + public PuzzleState(int[] pieces) + { + if (pieces == null) + { + throw new IllegalStateException("Pieces cannot be null"); + } + + if (DIMENSION * DIMENSION != pieces.length) + { + throw new IllegalStateException("Piece array does not have the right dimensions"); + } + + for (int i = 0; i < pieces.length; i++) + { + if (pieces[i] == -1) + { + emptyPiece = i; + } + } + + if (emptyPiece == -1) + { + throw new IllegalStateException("Incorrect empty piece passed in!"); + } + + this.pieces = pieces; + } + + private PuzzleState(PuzzleState state) + { + this.pieces = Arrays.copyOf(state.pieces, state.pieces.length); + this.emptyPiece = state.emptyPiece; + } + + public List computeMoves() + { + List moves = new ArrayList<>(); + + int emptyPieceX = emptyPiece % DIMENSION; + int emptyPieceY = emptyPiece / DIMENSION; + + // Move left if there is space to the left + if (emptyPieceX > 0) + { + if (parent == null || parent.emptyPiece != emptyPiece - 1) + { + PuzzleState state = new PuzzleState(this); + state.parent = this; + + state.pieces[emptyPiece - 1] = -1; + state.pieces[emptyPiece] = pieces[emptyPiece - 1]; + state.emptyPiece--; + + moves.add(state); + } + } + + // Move right if there is space to the right + if (emptyPieceX < DIMENSION - 1) + { + if (parent == null || parent.emptyPiece != emptyPiece + 1) + { + PuzzleState state = new PuzzleState(this); + state.parent = this; + + state.pieces[emptyPiece + 1] = -1; + state.pieces[emptyPiece] = pieces[emptyPiece + 1]; + state.emptyPiece++; + + moves.add(state); + } + } + + // Move up if there is space upwards + if (emptyPieceY > 0) + { + if (parent == null || parent.emptyPiece != emptyPiece - DIMENSION) + { + PuzzleState state = new PuzzleState(this); + state.parent = this; + + state.pieces[emptyPiece - DIMENSION] = -1; + state.pieces[emptyPiece] = pieces[emptyPiece - DIMENSION]; + state.emptyPiece -= DIMENSION; + + moves.add(state); + } + } + + // Move down if there is space downwards + if (emptyPieceY < DIMENSION - 1) + { + if (parent == null || parent.emptyPiece != emptyPiece + DIMENSION) + { + PuzzleState state = new PuzzleState(this); + state.parent = this; + + state.pieces[emptyPiece + DIMENSION] = -1; + state.pieces[emptyPiece] = pieces[emptyPiece + DIMENSION]; + state.emptyPiece += DIMENSION; + + moves.add(state); + } + } + + return moves; + } + + public PuzzleState getParent() + { + return parent; + } + + public int getPiece(int x, int y) + { + return pieces[y * DIMENSION + x]; + } + + public int getEmptyPiece() + { + return emptyPiece; + } + + public int getHeuristicValue(Heuristic heuristic) + { + if (h == -1) + { + // cache the value + h = heuristic.computeValue(this); + } + + return h; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/Heuristic.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/Heuristic.java new file mode 100644 index 0000000000..b450280992 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/Heuristic.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018, Lotto + * 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.solver.heuristics; + +import net.runelite.client.plugins.puzzlesolver.solver.PuzzleState; + +public interface Heuristic +{ + int computeValue(PuzzleState state); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/ManhattanDistance.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/ManhattanDistance.java new file mode 100644 index 0000000000..7bcb3891b8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/heuristics/ManhattanDistance.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Henke + * 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.solver.heuristics; + +import net.runelite.client.plugins.puzzlesolver.solver.PuzzleState; +import static net.runelite.client.plugins.puzzlesolver.solver.PuzzleSolver.DIMENSION; + +/** + * An implementation of the manhattan distance heuristic function. + * + * https://heuristicswiki.wikispaces.com/Manhattan+Distance + */ +public class ManhattanDistance implements Heuristic +{ + @Override + public int computeValue(PuzzleState state) + { + int value = 0; + + PuzzleState parent = state.getParent(); + + if (parent == null) + { + for (int x = 0; x < DIMENSION; x++) + { + for (int y = 0; y < DIMENSION; y++) + { + int piece = state.getPiece(x, y); + + if (piece == -1) + { + continue; + } + + int goalX = piece % DIMENSION; + int goalY = piece / DIMENSION; + + value += Math.abs(x - goalX) + Math.abs(y - goalY); + } + } + } + else + { + /* + If the Manhattan distance for the parent has already been + calculated, we can take advantage of that and just + add/subtract from their heuristic value. + + Doing this decreases the execution time of the heuristic by about 25%. + */ + value = parent.getHeuristicValue(this); + + int x = parent.getEmptyPiece() % DIMENSION; + int y = parent.getEmptyPiece() / DIMENSION; + + int x2 = state.getEmptyPiece() % DIMENSION; + int y2 = state.getEmptyPiece() / DIMENSION; + + int piece = state.getPiece(x, y); + + if (x2 > x) + { + int targetX = piece % DIMENSION; + + // right + if (targetX > x) value++; + else value--; + } + else if (x2 < x) + { + int targetX = piece % DIMENSION; + + // left + if (targetX < x) value++; + else value--; + } + else if (y2 > y) + { + int targetY = piece / DIMENSION; + + // down + if (targetY > y) value++; + else value--; + } + else + { + int targetY = piece / DIMENSION; + + // up + if (targetY < y) value++; + else value--; + } + } + + return value; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/IDAStar.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/IDAStar.java new file mode 100644 index 0000000000..1c1275a25e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/IDAStar.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018, Lotto + * 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.solver.pathfinding; + +import java.util.ArrayList; +import java.util.List; +import net.runelite.client.plugins.puzzlesolver.solver.PuzzleState; +import net.runelite.client.plugins.puzzlesolver.solver.heuristics.Heuristic; + +/** + * An implementation of the IDA* algorithm. + * + * https://en.wikipedia.org/wiki/Iterative_deepening_A* + */ +public class IDAStar extends Pathfinder +{ + public IDAStar(Heuristic heuristic) + { + super(heuristic); + } + + @Override + public List computePath(PuzzleState root) + { + PuzzleState goalNode = path(root); + + if (goalNode == null) + { + return null; + } + + List path = new ArrayList<>(); + + PuzzleState parent = goalNode; + while (parent != null) + { + path.add(0, parent); + parent = parent.getParent(); + } + + return path; + } + + private PuzzleState path(PuzzleState root) + { + int bound = root.getHeuristicValue(getHeuristic()); + + while (true) + { + PuzzleState t = search(root, 0, bound); + + if (t != null) + { + return t; + } + + bound += 1; + } + } + + private PuzzleState search(PuzzleState node, int g, int bound) + { + int h = node.getHeuristicValue(getHeuristic()); + int f = g + h; + + if (f > bound) + { + return null; + } + + if (h == 0) + { + return node; + } + + for (PuzzleState successor : node.computeMoves()) + { + PuzzleState t = search(successor, g + 1, bound); + + if (t != null) + { + return t; + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/Pathfinder.java new file mode 100644 index 0000000000..c8715253f9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/pathfinding/Pathfinder.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Lotto + * 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.solver.pathfinding; + +import java.util.List; +import net.runelite.client.plugins.puzzlesolver.solver.PuzzleState; +import net.runelite.client.plugins.puzzlesolver.solver.heuristics.Heuristic; + +public abstract class Pathfinder +{ + private Heuristic heuristic; + + Pathfinder(Heuristic heuristic) + { + this.heuristic = heuristic; + } + + Heuristic getHeuristic() + { + return heuristic; + } + + public abstract List computePath(PuzzleState start); +} From fa619af0aaae706079f3b5f1c4bd47e9e0dffb6c Mon Sep 17 00:00:00 2001 From: Lotto Date: Fri, 23 Feb 2018 23:20:11 +0100 Subject: [PATCH 2/7] puzzlesolver: don't spit out exceptions when the puzzle is finished --- .../puzzlesolver/PuzzleSolverOverlay.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) 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 index f41c08c185..96c728ffe9 100644 --- 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 @@ -67,6 +67,9 @@ public class PuzzleSolverOverlay extends Overlay private static final int PUZZLE_TILE_SIZE = 39; + private static final int BLANK_TILE_VALUE = -1; + private static final int DIMENSION = 5; + private final Client client; private final PuzzleSolverConfig config; @@ -236,10 +239,22 @@ public class PuzzleSolverOverlay extends Overlay private int[] getItemIds(ItemContainer container) { - return Arrays - .stream(container.getItems()) - .mapToInt(Item::getId) - .toArray(); + int[] itemIds = new int[DIMENSION * DIMENSION]; + + Item[] items = container.getItems(); + + for (int i = 0; i < items.length; i++) + { + itemIds[i] = items[i].getId(); + } + + // If blank is in the last position, items doesn't contain it, so let's add it manually + if (itemIds.length > items.length) + { + itemIds[items.length] = BLANK_TILE_VALUE; + } + + return itemIds; } private void cacheItems(int[] items) From 9fb2ba3fda8d02ef3dfdb1cccd98f4e7c1feaf3c Mon Sep 17 00:00:00 2001 From: Lotto Date: Fri, 23 Feb 2018 23:25:03 +0100 Subject: [PATCH 3/7] puzzlesolver: use the new solver and display Solving.. + Solved! msgs --- .../plugins/puzzlesolver/PuzzleSolver.java | 346 ------------------ .../puzzlesolver/PuzzleSolverOverlay.java | 251 ++++++++----- .../puzzlesolver/PuzzleSolverPlugin.java | 4 + 3 files changed, 162 insertions(+), 439 deletions(-) delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolver.java 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 deleted file mode 100644 index 79d222f785..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolver.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * 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, Lotto - * 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/PuzzleSolverOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverOverlay.java index 96c728ffe9..12d559c07b 100644 --- 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Lotto + * Copyright (c) 2018, Henke * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,9 +36,9 @@ 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 java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import javax.imageio.ImageIO; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; @@ -48,7 +49,10 @@ 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.plugins.puzzlesolver.solver.PuzzleSolver; +import net.runelite.client.plugins.puzzlesolver.solver.PuzzleState; +import net.runelite.client.plugins.puzzlesolver.solver.heuristics.ManhattanDistance; +import net.runelite.client.plugins.puzzlesolver.solver.pathfinding.IDAStar; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; @@ -60,10 +64,10 @@ 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 INFO_BOX_WIDTH = 100; + private static final int INFO_BOX_OFFSET_Y = 50; + private static final int INFO_BOX_TOP_BORDER = 2; + private static final int INFO_BOX_BOTTOM_BORDER = 2; private static final int PUZZLE_TILE_SIZE = 39; @@ -72,9 +76,10 @@ public class PuzzleSolverOverlay extends Overlay private final Client client; private final PuzzleSolverConfig config; + private final ScheduledExecutorService executorService; private PuzzleSolver solver; - private Deque nextMoves; + private Future solverFuture; private int[] cachedItems; private BufferedImage downArrow; @@ -83,13 +88,14 @@ public class PuzzleSolverOverlay extends Overlay private BufferedImage rightArrow; @Inject - public PuzzleSolverOverlay(Client client, PuzzleSolverConfig config) + public PuzzleSolverOverlay(Client client, PuzzleSolverConfig config, ScheduledExecutorService executorService) { setPosition(OverlayPosition.DYNAMIC); setPriority(OverlayPriority.HIGH); setLayer(OverlayLayer.ABOVE_WIDGETS); this.client = client; this.config = config; + this.executorService = executorService; } @Override @@ -115,117 +121,177 @@ public class PuzzleSolverOverlay extends Overlay return null; } + net.runelite.api.Point puzzleBoxLocation = puzzleBox.getCanvasLocation(); + + String infoString = "Solving.."; + int[] itemIds = getItemIds(container); boolean shouldCache = false; - if (nextMoves != null) + if (solver != 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) + if (solver.hasFailed()) { - nextMoves.pop(); // pop the current move - nextMove = nextMoves.peek(); - shouldCache = true; + infoString = "The puzzle could not be solved"; } - - // If nextMove is null, the puzzle is either finished - // or the player has not followed the instructions. - if (nextMove != null) + else { - net.runelite.api.Point puzzleBoxLocation = puzzleBox.getCanvasLocation(); - - if (config.displayRemainingMoves()) + if (solver.hasSolution()) { - int x = puzzleBoxLocation.getX() + puzzleBox.getWidth() / 2 - MOVES_LEFT_WIDTH / 2; - int y = puzzleBoxLocation.getY() - MOVES_LEFT_OFFSET_Y; + boolean foundPosition = false; - 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++) + // Find the current state by looking at the current step and then the next 3 steps + for (int i = 0; i < 4; i++) { - if (itemIds[j] == BLANK_TILE_VALUE) + int j = solver.getPosition() + i; + + if (j == solver.getStepCount()) { - lastBlankX = j % PuzzleSolver.DIMENSION; - lastBlankY = j / PuzzleSolver.DIMENSION; + break; + } + + Integer currentState = solver.getStep(j); + + // If this is false, player has moved the empty tile + if (currentState != null && itemIds[currentState] == BLANK_TILE_VALUE) + { + foundPosition = true; + solver.setPosition(j); + if (i > 0) + { + shouldCache = true; + } break; } } - for (Integer futureMove : nextMoves) + // If looking at the next steps didn't find the current state, + // see if we can find the current state in the 3 previous steps + if (!foundPosition) { - int blankX = futureMove % PuzzleSolver.DIMENSION; - int blankY = futureMove / PuzzleSolver.DIMENSION; + for (int i = 1; i < 4; i++) + { + int j = solver.getPosition() - i; - int xDelta = blankX - lastBlankX; - int yDelta = blankY - lastBlankY; + if (j < 0) + { + break; + } - BufferedImage arrow; - if (xDelta > 0) - { - arrow = getRightArrow(); + Integer currentState = solver.getStep(j); + + if (currentState != null && itemIds[currentState] == BLANK_TILE_VALUE) + { + foundPosition = true; + shouldCache = true; + solver.setPosition(j); + break; + } } - else if (xDelta < 0) + } + + if (foundPosition) + { + int stepsLeft = solver.getStepCount() - solver.getPosition() - 1; + + if (stepsLeft == 0) { - arrow = getLeftArrow(); + infoString = "Solved!"; } - else if (yDelta > 0) + else if (config.displayRemainingMoves()) { - arrow = getDownArrow(); + infoString = "Moves left: " + stepsLeft; } else { - arrow = getUpArrow(); + infoString = null; } - 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) + if (config.displaySolution()) { - break; + // Find the current blank tile position + Integer currentMove = solver.getStep(solver.getPosition()); + + int lastBlankX = currentMove % DIMENSION; + int lastBlankY = currentMove / DIMENSION; + + // Display the next 3 steps + for (int j = 1; j < 4; j++) + { + Integer futureMove = solver.getStep(solver.getPosition() + j); + + if (futureMove == null) + { + break; + } + + int blankX = futureMove % DIMENSION; + int blankY = futureMove / 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; + } } } } } } + // Draw info box + if (infoString != null) + { + int x = puzzleBoxLocation.getX() + puzzleBox.getWidth() / 2 - INFO_BOX_WIDTH / 2; + int y = puzzleBoxLocation.getY() - INFO_BOX_OFFSET_Y; + + FontMetrics fm = graphics.getFontMetrics(); + int height = INFO_BOX_TOP_BORDER + fm.getHeight() + INFO_BOX_BOTTOM_BORDER; + + BackgroundComponent backgroundComponent = new BackgroundComponent(); + backgroundComponent.setRectangle(new Rectangle(x, y, INFO_BOX_WIDTH, height)); + backgroundComponent.render(graphics, parent); + + int textOffsetX = (INFO_BOX_WIDTH - fm.stringWidth(infoString)) / 2; + int textOffsetY = fm.getHeight(); + + TextComponent textComponent = new TextComponent(); + textComponent.setPosition(new Point(x + textOffsetX, y + textOffsetY)); + textComponent.setText(infoString); + textComponent.render(graphics, parent); + } + + // Solve the puzzle if we don't have an up to date solution if (solver == null || cachedItems == null || (!shouldCache && !Arrays.equals(cachedItems, itemIds))) { - nextMoves = solve(itemIds); + solve(itemIds); shouldCache = true; } @@ -263,19 +329,18 @@ public class PuzzleSolverOverlay extends Overlay System.arraycopy(items, 0, cachedItems, 0, cachedItems.length); } - private Deque solve(int[] items) + private void solve(int[] items) { - Deque steps = new ArrayDeque<>(); - - solver = new PuzzleSolver(convertToSolverFormat(items)); - - while (solver.hasNext()) + if (solverFuture != null) { - solver.next(); - steps.add(solver.getBlankX() + solver.getBlankY() * PuzzleSolver.DIMENSION); + solverFuture.cancel(true); } - return steps; + int[] puzzleItems = convertToSolverFormat(items); + PuzzleState puzzleState = new PuzzleState(puzzleItems); + + solver = new PuzzleSolver(new IDAStar(new ManhattanDistance()), puzzleState); + solverFuture = executorService.submit(solver); } /** 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 index d246a3fde9..84750dcd9c 100644 --- 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 @@ -26,6 +26,7 @@ package net.runelite.client.plugins.puzzlesolver; import com.google.inject.Provides; +import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import net.runelite.client.config.ConfigManager; import net.runelite.client.plugins.Plugin; @@ -39,6 +40,9 @@ public class PuzzleSolverPlugin extends Plugin @Inject private PuzzleSolverOverlay puzzleSolverOverlay; + @Inject + private ScheduledExecutorService executorService; + @Provides PuzzleSolverConfig provideConfig(ConfigManager configManager) { From f6a0f4860161374db9c61e35794fb3a23f3f3a61 Mon Sep 17 00:00:00 2001 From: Lotto Date: Fri, 23 Feb 2018 23:25:30 +0100 Subject: [PATCH 4/7] puzzlesolver: update the test to use the new solver --- .../puzzlesolver/PuzzleSolverTest.java | 96 ++++++++++++++++--- 1 file changed, 83 insertions(+), 13 deletions(-) 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 index 4420735dca..12cdc00103 100644 --- 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 @@ -25,29 +25,99 @@ */ package net.runelite.client.plugins.puzzlesolver; -import static org.junit.Assert.assertEquals; +import java.util.List; +import net.runelite.client.plugins.puzzlesolver.solver.PuzzleSolver; +import net.runelite.client.plugins.puzzlesolver.solver.PuzzleState; +import net.runelite.client.plugins.puzzlesolver.solver.heuristics.ManhattanDistance; +import net.runelite.client.plugins.puzzlesolver.solver.pathfinding.IDAStar; import org.junit.Test; +import static net.runelite.client.plugins.puzzlesolver.solver.PuzzleSolver.DIMENSION; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; 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}; + private static final PuzzleState[] START_STATES = + { + new PuzzleState(new int[]{0, 11, 1, 3, 4, 5, 12, 2, 7, 9, 6, 20, 18, 16, 8, 15, 22, 10, 14, 13, 21, -1, 17, 23, 19}), + new PuzzleState(new int[]{0, 2, 7, 3, 4, 10, 5, 12, 1, 9, 6, 17, 8, 14, 19, -1, 16, 21, 11, 13, 15, 20, 22, 18, 23}), + new PuzzleState(new int[]{0, 1, 11, 3, 4, 12, 2, 7, 13, 9, 5, 21, 15, 17, 14, -1, 10, 6, 8, 19, 16, 20, 22, 18, 23}), + new PuzzleState(new int[]{0, 1, 2, 3, 4, 10, 5, 6, 9, 14, 15, -1, 7, 13, 17, 21, 11, 20, 23, 8, 16, 22, 12, 19, 18}), + new PuzzleState(new int[]{0, 1, 2, 3, 4, 5, 6, 8, 22, 18, 10, -1, 7, 17, 9, 20, 11, 12, 21, 14, 16, 15, 23, 13, 19}), + new PuzzleState(new int[]{0, 1, 2, 3, 4, 5, 6, 8, 12, 9, 16, 11, 17, 7, 14, 10, 20, -1, 22, 13, 15, 18, 21, 23, 19}), + new PuzzleState(new int[]{1, 6, 16, 8, 4, 0, 7, 11, 2, 9, 5, 21, 18, 3, 14, 10, 20, -1, 13, 22, 15, 23, 12, 17, 19}), + new PuzzleState(new int[]{0, 1, 2, 4, 8, 6, 16, 11, 7, 3, 5, -1, 12, 14, 9, 15, 10, 17, 13, 18, 20, 21, 22, 23, 19}), + new PuzzleState(new int[]{0, 2, 9, 14, 6, 5, 7, 11, 3, 4, 15, 10, 1, 12, 18, 16, 17, -1, 8, 13, 20, 21, 22, 23, 19}), + new PuzzleState(new int[]{0, 1, 2, 3, 4, 11, 5, 12, 7, 8, 10, 6, 15, 13, 9, 16, 21, -1, 17, 14, 20, 22, 23, 18, 19}), + new PuzzleState(new int[]{5, 0, 1, 2, 4, 10, 6, 3, 8, 9, 12, 13, 7, 14, 19, 15, 11, 16, 17, -1, 20, 21, 22, 18, 23}), + new PuzzleState(new int[]{0, 6, 1, 3, 4, 5, 8, -1, 2, 9, 16, 11, 12, 7, 14, 10, 15, 17, 13, 19, 20, 21, 22, 18, 23}), + new PuzzleState(new int[]{0, 6, 1, 2, 4, 11, 15, 8, 3, 14, 5, 7, 9, 12, 18, 16, 10, 17, 23, 13, 20, 21, 22, -1, 19}), + new PuzzleState(new int[]{0, 1, 7, 2, 4, 5, 3, 12, 8, 9, 15, 6, 18, -1, 13, 11, 10, 22, 17, 23, 16, 21, 20, 19, 14}), + new PuzzleState(new int[]{0, 1, 2, 7, 3, 5, 11, 6, 14, 4, 10, -1, 16, 12, 9, 15, 17, 18, 8, 19, 20, 21, 13, 22, 23}), + new PuzzleState(new int[]{2, 10, 5, 3, 4, -1, 0, 1, 8, 9, 15, 11, 7, 13, 23, 17, 6, 20, 14, 19, 16, 12, 18, 21, 22}), + new PuzzleState(new int[]{0, 1, 2, 8, 9, 5, 6, 7, 3, 4, 10, -1, 14, 23, 18, 21, 11, 16, 12, 19, 15, 20, 17, 13, 22}), + new PuzzleState(new int[]{0, 6, 1, 3, 4, 11, 2, 13, 9, 12, 5, 16, 7, 18, 8, 20, 15, -1, 14, 19, 21, 10, 22, 23, 17}), + new PuzzleState(new int[]{12, 1, 2, 3, 4, 0, 7, 6, 8, 9, 5, 10, 22, 13, 19, 15, 11, 21, 14, 17, 20, 16, 18, -1, 23}), + new PuzzleState(new int[]{0, 2, 11, 3, 4, 5, 1, 6, 8, 9, 15, 10, 13, 14, 19, 7, 12, -1, 17, 18, 20, 21, 16, 22, 23}), + new PuzzleState(new int[]{5, 0, 4, 2, 9, 10, 7, 3, 19, 8, 6, 1, 18, -1, 14, 15, 11, 16, 12, 13, 20, 21, 17, 22, 23}), + new PuzzleState(new int[]{0, 3, 2, 7, 4, 6, 10, 1, 8, 9, 15, 5, 12, 18, 13, -1, 20, 11, 22, 14, 16, 21, 23, 17, 19}), + new PuzzleState(new int[]{1, 2, 4, -1, 9, 0, 5, 7, 3, 14, 10, 6, 8, 13, 19, 15, 11, 18, 12, 22, 20, 16, 21, 23, 17}), + new PuzzleState(new int[]{0, 1, 2, 4, 9, 5, 11, -1, 7, 14, 10, 17, 6, 13, 8, 15, 16, 20, 3, 18, 22, 21, 12, 23, 19}), + new PuzzleState(new int[]{0, 1, 8, 2, 4, 5, 11, 17, 3, 9, 6, 16, 7, 12, 18, 15, 21, -1, 14, 13, 20, 22, 10, 23, 19}), + new PuzzleState(new int[]{5, 0, 2, 3, 4, 1, 8, 6, 7, 9, 11, 12, 16, 13, 14, -1, 22, 20, 17, 19, 21, 10, 15, 18, 23}), + new PuzzleState(new int[]{0, -1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 16, 11, 13, 14, 10, 15, 19, 22, 23, 20, 21, 17, 12, 18}), + new PuzzleState(new int[]{0, 2, 7, -1, 4, 6, 1, 9, 3, 14, 5, 12, 8, 13, 19, 15, 16, 10, 22, 23, 20, 11, 18, 21, 17}), + new PuzzleState(new int[]{7, 5, 13, 1, 12, 0, 2, 4, 3, 9, 10, 6, 8, 17, 14, 15, 11, 16, 18, -1, 20, 21, 22, 23, 19}), + new PuzzleState(new int[]{5, 0, 3, 8, 4, 10, 6, -1, 7, 9, 11, 1, 12, 2, 19, 15, 16, 17, 14, 13, 20, 21, 22, 18, 23}), + new PuzzleState(new int[]{5, 2, 3, 7, 4, 0, 6, 14, 9, 19, 1, 11, 22, 17, 12, 10, 15, -1, 13, 8, 20, 16, 21, 18, 23}), + new PuzzleState(new int[]{0, 1, 3, 4, 9, 5, 6, 2, 7, 14, 10, 13, 17, -1, 8, 15, 11, 16, 12, 18, 20, 21, 22, 23, 19}), + new PuzzleState(new int[]{0, 3, 11, 7, 4, 5, 2, 6, 12, 8, 10, 1, 17, -1, 9, 15, 16, 23, 13, 14, 20, 21, 18, 22, 19}), + new PuzzleState(new int[]{1, 5, 8, 2, 4, -1, 0, 7, 3, 9, 11, 22, 15, 12, 14, 6, 10, 18, 16, 19, 20, 21, 17, 13, 23}), + new PuzzleState(new int[]{7, 12, 11, 4, 9, -1, 0, 8, 10, 2, 6, 1, 16, 3, 14, 5, 15, 17, 13, 19, 20, 21, 22, 18, 23}), + new PuzzleState(new int[]{11, 3, 2, 12, 4, 6, 0, 7, 13, 8, 1, 5, 17, 16, 9, -1, 10, 15, 18, 14, 20, 21, 22, 23, 19}), + new PuzzleState(new int[]{0, 6, 1, 3, 4, 5, 11, 2, 10, 9, 15, 12, 8, 14, 19, 16, 21, -1, 7, 13, 20, 22, 17, 18, 23}), + new PuzzleState(new int[]{0, 1, 2, 3, 4, 6, 10, 7, 8, 9, 5, 16, 11, 14, 17, 20, 13, 18, 12, 22, 21, 15, 23, -1, 19}), + new PuzzleState(new int[]{0, 1, 2, 4, 8, 5, 6, 7, 3, -1, 10, 16, 18, 17, 9, 15, 12, 11, 14, 13, 20, 21, 22, 23, 19}), + new PuzzleState(new int[]{0, 11, 6, 1, 4, 5, 21, 8, 2, 9, 10, 3, 16, -1, 14, 15, 12, 17, 13, 19, 20, 22, 7, 18, 23}), + new PuzzleState(new int[]{0, 6, 1, 2, 4, 11, 10, 3, 13, 9, 5, 7, 8, -1, 23, 15, 16, 22, 18, 14, 20, 21, 12, 17, 19}), + new PuzzleState(new int[]{0, 6, 1, 2, 3, 10, 11, 12, 5, 18, 15, 7, 4, -1, 14, 21, 17, 13, 8, 9, 16, 20, 22, 23, 19}), + new PuzzleState(new int[]{0, 1, 3, 11, 4, 6, 10, 14, 2, 8, 5, -1, 12, 7, 9, 15, 16, 18, 13, 19, 20, 21, 22, 17, 23}), + new PuzzleState(new int[]{1, 5, 2, 3, 4, -1, 0, 7, 14, 8, 11, 6, 13, 9, 23, 10, 12, 15, 19, 17, 20, 21, 16, 22, 18}), + }; + + 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, -1}; @Test public void testSolver() { - PuzzleSolver solver = new PuzzleSolver(TEST_STATE); - - assertEquals(solver.hasNext(), true); - - while (solver.hasNext()) + for (PuzzleState state : START_STATES) { - solver.next(); - } + PuzzleSolver solver = new PuzzleSolver(new IDAStar(new ManhattanDistance()), state); + solver.run(); - for (int i = 0; i < FINISHED_STATE.length; i++) - { - assertEquals(solver.getTiles()[i], FINISHED_STATE[i]); + assertTrue(solver.hasSolution()); + + for (int i = 0; i < solver.getStepCount(); i++) + { + int futureMove = solver.getStep(i); + + List moves = state.computeMoves(); + + for (PuzzleState move : moves) + { + if (move.getEmptyPiece() == futureMove) + { + state = move; + break; + } + } + } + + for (int i = 0; i < FINISHED_STATE.length; i++) + { + assertEquals(state.getPiece(i % DIMENSION, i / DIMENSION), FINISHED_STATE[i]); + } } } } From 31af43e9be1c7eaa7ea10817d4c7563d47375e67 Mon Sep 17 00:00:00 2001 From: Lotto Date: Fri, 23 Feb 2018 23:47:03 +0100 Subject: [PATCH 5/7] puzzlesolver: add an option to draw dots instead of arrows --- .../puzzlesolver/PuzzleSolverConfig.java | 10 ++ .../puzzlesolver/PuzzleSolverOverlay.java | 111 ++++++++++++------ 2 files changed, 82 insertions(+), 39 deletions(-) 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 index eb924b10f1..902afd0d3d 100644 --- 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 @@ -55,4 +55,14 @@ public interface PuzzleSolverConfig extends Config { return true; } + + @ConfigItem( + keyName = "drawDots", + name = "Draw dots instead of arrows", + description = "Draw dots increasing in size instead of arrows for the solution" + ) + default boolean drawDots() + { + return false; + } } 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 index 12d559c07b..fffb774e8a 100644 --- 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 @@ -26,6 +26,7 @@ */ package net.runelite.client.plugins.puzzlesolver; +import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; @@ -70,6 +71,7 @@ public class PuzzleSolverOverlay extends Overlay private static final int INFO_BOX_BOTTOM_BORDER = 2; private static final int PUZZLE_TILE_SIZE = 39; + private static final int DOT_MARKER_SIZE = 16; private static final int BLANK_TILE_VALUE = -1; private static final int DIMENSION = 5; @@ -209,56 +211,87 @@ public class PuzzleSolverOverlay extends Overlay if (config.displaySolution()) { - // Find the current blank tile position - Integer currentMove = solver.getStep(solver.getPosition()); - - int lastBlankX = currentMove % DIMENSION; - int lastBlankY = currentMove / DIMENSION; - - // Display the next 3 steps - for (int j = 1; j < 4; j++) + if (config.drawDots()) { - Integer futureMove = solver.getStep(solver.getPosition() + j); + graphics.setColor(Color.YELLOW); - if (futureMove == null) + // Display the next 4 steps + for (int j = 1; j < 5; j++) { - break; + Integer futureMove = solver.getStep(solver.getPosition() + j); + + if (futureMove == null) + { + break; + } + + int blankX = futureMove % DIMENSION; + int blankY = futureMove / DIMENSION; + + int markerSize = DOT_MARKER_SIZE - j * 3; + + int x = puzzleBoxLocation.getX() + blankX * PUZZLE_TILE_SIZE + + PUZZLE_TILE_SIZE / 2 - markerSize / 2; + + int y = puzzleBoxLocation.getY() + blankY * PUZZLE_TILE_SIZE + + PUZZLE_TILE_SIZE / 2 - markerSize / 2; + + graphics.fillOval(x, y, markerSize, markerSize); } + } + else + { + // Find the current blank tile position + Integer currentMove = solver.getStep(solver.getPosition()); - int blankX = futureMove % DIMENSION; - int blankY = futureMove / DIMENSION; + int lastBlankX = currentMove % DIMENSION; + int lastBlankY = currentMove / DIMENSION; - int xDelta = blankX - lastBlankX; - int yDelta = blankY - lastBlankY; - - BufferedImage arrow; - if (xDelta > 0) + // Display the next 3 steps + for (int j = 1; j < 4; j++) { - arrow = getRightArrow(); - } - else if (xDelta < 0) - { - arrow = getLeftArrow(); - } - else if (yDelta > 0) - { - arrow = getDownArrow(); - } - else - { - arrow = getUpArrow(); - } + Integer futureMove = solver.getStep(solver.getPosition() + j); - int x = puzzleBoxLocation.getX() + blankX * PUZZLE_TILE_SIZE - + PUZZLE_TILE_SIZE / 2 - arrow.getWidth() / 2; + if (futureMove == null) + { + break; + } - int y = puzzleBoxLocation.getY() + blankY * PUZZLE_TILE_SIZE - + PUZZLE_TILE_SIZE / 2 - arrow.getHeight() / 2; + int blankX = futureMove % DIMENSION; + int blankY = futureMove / DIMENSION; - OverlayUtil.renderImageLocation(graphics, new net.runelite.api.Point(x, y), arrow); + int xDelta = blankX - lastBlankX; + int yDelta = blankY - lastBlankY; - lastBlankX = blankX; - lastBlankY = blankY; + 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; + } } } } From 3396c7ac45009fdb1c8ca45f9d6c226b43cc5be0 Mon Sep 17 00:00:00 2001 From: Lotto Date: Sat, 24 Feb 2018 00:38:41 +0100 Subject: [PATCH 6/7] puzzlesolver: fix solution being reset when clicking the puzzle quickly --- .../puzzlesolver/PuzzleSolverOverlay.java | 75 +++++++++---------- .../puzzlesolver/solver/PuzzleSolver.java | 19 +---- .../puzzlesolver/solver/PuzzleState.java | 5 ++ .../puzzlesolver/PuzzleSolverTest.java | 28 +------ 4 files changed, 49 insertions(+), 78 deletions(-) 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 index fffb774e8a..b28ae9917c 100644 --- 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 @@ -142,8 +142,8 @@ public class PuzzleSolverOverlay extends Overlay { boolean foundPosition = false; - // Find the current state by looking at the current step and then the next 3 steps - for (int i = 0; i < 4; i++) + // Find the current state by looking at the current step and then the next 5 steps + for (int i = 0; i < 6; i++) { int j = solver.getPosition() + i; @@ -152,10 +152,10 @@ public class PuzzleSolverOverlay extends Overlay break; } - Integer currentState = solver.getStep(j); + PuzzleState currentState = solver.getStep(j); // If this is false, player has moved the empty tile - if (currentState != null && itemIds[currentState] == BLANK_TILE_VALUE) + if (currentState != null && currentState.hasPieces(itemIds)) { foundPosition = true; solver.setPosition(j); @@ -168,10 +168,10 @@ public class PuzzleSolverOverlay extends Overlay } // If looking at the next steps didn't find the current state, - // see if we can find the current state in the 3 previous steps + // see if we can find the current state in the 5 previous steps if (!foundPosition) { - for (int i = 1; i < 4; i++) + for (int i = 1; i < 6; i++) { int j = solver.getPosition() - i; @@ -180,9 +180,9 @@ public class PuzzleSolverOverlay extends Overlay break; } - Integer currentState = solver.getStep(j); + PuzzleState currentState = solver.getStep(j); - if (currentState != null && itemIds[currentState] == BLANK_TILE_VALUE) + if (currentState != null && currentState.hasPieces(itemIds)) { foundPosition = true; shouldCache = true; @@ -218,15 +218,15 @@ public class PuzzleSolverOverlay extends Overlay // Display the next 4 steps for (int j = 1; j < 5; j++) { - Integer futureMove = solver.getStep(solver.getPosition() + j); + PuzzleState futureMove = solver.getStep(solver.getPosition() + j); if (futureMove == null) { break; } - int blankX = futureMove % DIMENSION; - int blankY = futureMove / DIMENSION; + int blankX = futureMove.getEmptyPiece() % DIMENSION; + int blankY = futureMove.getEmptyPiece() / DIMENSION; int markerSize = DOT_MARKER_SIZE - j * 3; @@ -242,23 +242,23 @@ public class PuzzleSolverOverlay extends Overlay else { // Find the current blank tile position - Integer currentMove = solver.getStep(solver.getPosition()); + PuzzleState currentMove = solver.getStep(solver.getPosition()); - int lastBlankX = currentMove % DIMENSION; - int lastBlankY = currentMove / DIMENSION; + int lastBlankX = currentMove.getEmptyPiece() % DIMENSION; + int lastBlankY = currentMove.getEmptyPiece() / DIMENSION; // Display the next 3 steps for (int j = 1; j < 4; j++) { - Integer futureMove = solver.getStep(solver.getPosition() + j); + PuzzleState futureMove = solver.getStep(solver.getPosition() + j); if (futureMove == null) { break; } - int blankX = futureMove % DIMENSION; - int blankY = futureMove / DIMENSION; + int blankX = futureMove.getEmptyPiece() % DIMENSION; + int blankY = futureMove.getEmptyPiece() / DIMENSION; int xDelta = blankX - lastBlankX; int yDelta = blankY - lastBlankY; @@ -353,27 +353,7 @@ public class PuzzleSolverOverlay extends Overlay itemIds[items.length] = BLANK_TILE_VALUE; } - return itemIds; - } - - private void cacheItems(int[] items) - { - cachedItems = new int[items.length]; - System.arraycopy(items, 0, cachedItems, 0, cachedItems.length); - } - - private void solve(int[] items) - { - if (solverFuture != null) - { - solverFuture.cancel(true); - } - - int[] puzzleItems = convertToSolverFormat(items); - PuzzleState puzzleState = new PuzzleState(puzzleItems); - - solver = new PuzzleSolver(new IDAStar(new ManhattanDistance()), puzzleState); - solverFuture = executorService.submit(solver); + return convertToSolverFormat(itemIds); } /** @@ -413,6 +393,25 @@ public class PuzzleSolverOverlay extends Overlay return convertedItems; } + private void cacheItems(int[] items) + { + cachedItems = new int[items.length]; + System.arraycopy(items, 0, cachedItems, 0, cachedItems.length); + } + + private void solve(int[] items) + { + if (solverFuture != null) + { + solverFuture.cancel(true); + } + + PuzzleState puzzleState = new PuzzleState(items); + + solver = new PuzzleSolver(new IDAStar(new ManhattanDistance()), puzzleState); + solverFuture = executorService.submit(solver); + } + private BufferedImage getDownArrow() { if (downArrow == null) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java index 2b0bbfcee8..cf4b0cc1c2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java @@ -26,7 +26,6 @@ package net.runelite.client.plugins.puzzlesolver.solver; import java.util.List; -import java.util.stream.Collectors; import net.runelite.client.plugins.puzzlesolver.solver.pathfinding.Pathfinder; public class PuzzleSolver implements Runnable @@ -36,7 +35,7 @@ public class PuzzleSolver implements Runnable private Pathfinder pathfinder; private PuzzleState startState; - private List solution; + private List solution; private int position; private boolean failed = false; @@ -46,7 +45,7 @@ public class PuzzleSolver implements Runnable this.startState = startState; } - public Integer getStep(int stepIdx) + public PuzzleState getStep(int stepIdx) { if (stepIdx < 0 || stepIdx >= solution.size()) { @@ -84,17 +83,7 @@ public class PuzzleSolver implements Runnable @Override public void run() { - List solution = pathfinder.computePath(startState); - - if (solution != null) - { - this.solution = solution.stream() - .map(PuzzleState::getEmptyPiece) - .collect(Collectors.toList()); - } - else - { - failed = true; - } + solution = pathfinder.computePath(startState); + failed = solution == null; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java index 80f0415626..36db17893f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleState.java @@ -153,6 +153,11 @@ public class PuzzleState return parent; } + public boolean hasPieces(int[] pieces) + { + return Arrays.equals(pieces, this.pieces); + } + public int getPiece(int x, int y) { return pieces[y * DIMENSION + x]; 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 index 12cdc00103..af14ee8ebb 100644 --- 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 @@ -25,15 +25,12 @@ */ package net.runelite.client.plugins.puzzlesolver; -import java.util.List; import net.runelite.client.plugins.puzzlesolver.solver.PuzzleSolver; import net.runelite.client.plugins.puzzlesolver.solver.PuzzleState; import net.runelite.client.plugins.puzzlesolver.solver.heuristics.ManhattanDistance; import net.runelite.client.plugins.puzzlesolver.solver.pathfinding.IDAStar; import org.junit.Test; -import static net.runelite.client.plugins.puzzlesolver.solver.PuzzleSolver.DIMENSION; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class PuzzleSolverTest @@ -97,27 +94,8 @@ public class PuzzleSolverTest solver.run(); assertTrue(solver.hasSolution()); - - for (int i = 0; i < solver.getStepCount(); i++) - { - int futureMove = solver.getStep(i); - - List moves = state.computeMoves(); - - for (PuzzleState move : moves) - { - if (move.getEmptyPiece() == futureMove) - { - state = move; - break; - } - } - } - - for (int i = 0; i < FINISHED_STATE.length; i++) - { - assertEquals(state.getPiece(i % DIMENSION, i / DIMENSION), FINISHED_STATE[i]); - } + assertFalse(solver.hasFailed()); + assertTrue(solver.getStep(solver.getStepCount() - 1).hasPieces(FINISHED_STATE)); } } } From f210bbc4fb266ed1dd5cc21cf4f5690b9845e40d Mon Sep 17 00:00:00 2001 From: Lotto Date: Sun, 25 Feb 2018 20:48:16 +0100 Subject: [PATCH 7/7] puzzlesolver: remove bounds check from PuzzleSolver#getStep --- .../puzzlesolver/PuzzleSolverOverlay.java | 24 +++++++++++++++---- .../puzzlesolver/solver/PuzzleSolver.java | 5 ---- 2 files changed, 19 insertions(+), 10 deletions(-) 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 index b28ae9917c..c498c2e0ea 100644 --- 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 @@ -216,9 +216,16 @@ public class PuzzleSolverOverlay extends Overlay graphics.setColor(Color.YELLOW); // Display the next 4 steps - for (int j = 1; j < 5; j++) + for (int i = 1; i < 5; i++) { - PuzzleState futureMove = solver.getStep(solver.getPosition() + j); + int j = solver.getPosition() + i; + + if (j >= solver.getStepCount()) + { + break; + } + + PuzzleState futureMove = solver.getStep(j); if (futureMove == null) { @@ -228,7 +235,7 @@ public class PuzzleSolverOverlay extends Overlay int blankX = futureMove.getEmptyPiece() % DIMENSION; int blankY = futureMove.getEmptyPiece() / DIMENSION; - int markerSize = DOT_MARKER_SIZE - j * 3; + int markerSize = DOT_MARKER_SIZE - i * 3; int x = puzzleBoxLocation.getX() + blankX * PUZZLE_TILE_SIZE + PUZZLE_TILE_SIZE / 2 - markerSize / 2; @@ -248,9 +255,16 @@ public class PuzzleSolverOverlay extends Overlay int lastBlankY = currentMove.getEmptyPiece() / DIMENSION; // Display the next 3 steps - for (int j = 1; j < 4; j++) + for (int i = 1; i < 4; i++) { - PuzzleState futureMove = solver.getStep(solver.getPosition() + j); + int j = solver.getPosition() + i; + + if (j >= solver.getStepCount()) + { + break; + } + + PuzzleState futureMove = solver.getStep(j); if (futureMove == null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java index cf4b0cc1c2..9749a7769e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/solver/PuzzleSolver.java @@ -47,11 +47,6 @@ public class PuzzleSolver implements Runnable public PuzzleState getStep(int stepIdx) { - if (stepIdx < 0 || stepIdx >= solution.size()) - { - return null; - } - return solution.get(stepIdx); }