puzzlesolver: add IDA* + manhattan distance solver

This commit is contained in:
Lotto
2018-02-23 22:58:42 +01:00
parent 6580060f38
commit 90d7f9c56e
6 changed files with 588 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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<Integer> 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<PuzzleState> solution = pathfinder.computePath(startState);
if (solution != null)
{
this.solution = solution.stream()
.map(PuzzleState::getEmptyPiece)
.collect(Collectors.toList());
}
else
{
failed = true;
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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<PuzzleState> computeMoves()
{
List<PuzzleState> 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;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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);
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* Copyright (c) 2018, Henke <https://github.com/henke96>
* 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;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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<PuzzleState> computePath(PuzzleState root)
{
PuzzleState goalNode = path(root);
if (goalNode == null)
{
return null;
}
List<PuzzleState> 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;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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<PuzzleState> computePath(PuzzleState start);
}