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); +}