HotColdClue: Add hot-cold solver class
This adds a general hot-cold puzzle solver class and implements it in HotColdClue.
This commit is contained in:
@@ -25,20 +25,15 @@
|
||||
*/
|
||||
package net.runelite.client.plugins.cluescrolls.clues;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
@@ -48,6 +43,7 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin;
|
||||
import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdLocation;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdSolver;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdTemperature;
|
||||
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdTemperatureChange;
|
||||
import net.runelite.client.ui.overlay.OverlayUtil;
|
||||
@@ -58,18 +54,17 @@ import net.runelite.client.ui.overlay.components.TitleComponent;
|
||||
@Getter
|
||||
public class HotColdClue extends ClueScroll implements LocationClueScroll, LocationsClueScroll, TextClueScroll, NpcClueScroll
|
||||
{
|
||||
private static final int HOT_COLD_PANEL_WIDTH = 200;
|
||||
private static final HotColdClue CLUE =
|
||||
new HotColdClue("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Jorral may have a clue.",
|
||||
"Jorral",
|
||||
"Speak to Jorral to receive a strange device.");
|
||||
|
||||
// list of potential places to dig
|
||||
private List<HotColdLocation> digLocations = new ArrayList<>();
|
||||
private final String text;
|
||||
private final String npc;
|
||||
private final String solution;
|
||||
private HotColdSolver hotColdSolver;
|
||||
private WorldPoint location;
|
||||
private WorldPoint lastWorldPoint;
|
||||
|
||||
public static HotColdClue forText(String text)
|
||||
{
|
||||
@@ -87,12 +82,13 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
this.npc = npc;
|
||||
this.solution = solution;
|
||||
setRequiresSpade(true);
|
||||
initializeSolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldPoint[] getLocations()
|
||||
{
|
||||
return Lists.transform(digLocations, HotColdLocation::getWorldPoint).toArray(new WorldPoint[0]);
|
||||
return hotColdSolver.getPossibleLocations().stream().map(HotColdLocation::getWorldPoint).toArray(WorldPoint[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,10 +97,10 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
panelComponent.getChildren().add(TitleComponent.builder()
|
||||
.text("Hot/Cold Clue")
|
||||
.build());
|
||||
panelComponent.setPreferredSize(new Dimension(200, 0));
|
||||
panelComponent.setPreferredSize(new Dimension(HOT_COLD_PANEL_WIDTH, 0));
|
||||
|
||||
// strange device has not been tested yet, show how to get it
|
||||
if (lastWorldPoint == null && location == null)
|
||||
if (hotColdSolver.getLastWorldPoint() == null && location == null)
|
||||
{
|
||||
if (getNpc() != null)
|
||||
{
|
||||
@@ -131,7 +127,9 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
panelComponent.getChildren().add(LineComponent.builder()
|
||||
.left("Possible areas:")
|
||||
.build());
|
||||
Map<HotColdArea, Integer> locationCounts = new HashMap<>();
|
||||
|
||||
final Map<HotColdArea, Integer> locationCounts = new EnumMap<>(HotColdArea.class);
|
||||
final Collection<HotColdLocation> digLocations = hotColdSolver.getPossibleLocations();
|
||||
|
||||
for (HotColdLocation hotColdLocation : digLocations)
|
||||
{
|
||||
@@ -159,17 +157,16 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
}
|
||||
else
|
||||
{
|
||||
for (HotColdArea s : locationCounts.keySet())
|
||||
for (HotColdArea area : locationCounts.keySet())
|
||||
{
|
||||
panelComponent.getChildren().add(LineComponent.builder()
|
||||
.left(s.getName() + ":")
|
||||
.left(area.getName() + ':')
|
||||
.build());
|
||||
|
||||
for (HotColdLocation hotColdLocation : digLocations)
|
||||
{
|
||||
if (hotColdLocation.getHotColdArea() == s)
|
||||
if (hotColdLocation.getHotColdArea() == area)
|
||||
{
|
||||
Rectangle2D r = hotColdLocation.getRect();
|
||||
panelComponent.getChildren().add(LineComponent.builder()
|
||||
.left("- " + hotColdLocation.getArea())
|
||||
.leftColor(Color.LIGHT_GRAY)
|
||||
@@ -185,7 +182,7 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin)
|
||||
{
|
||||
// when final location has been found
|
||||
if (this.location != null)
|
||||
if (location != null)
|
||||
{
|
||||
LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), getLocation());
|
||||
|
||||
@@ -198,19 +195,16 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
}
|
||||
|
||||
// when strange device hasn't been activated yet, show Jorral
|
||||
if (lastWorldPoint == null)
|
||||
if (hotColdSolver.getLastWorldPoint() == null && plugin.getNpcsToMark() != null)
|
||||
{
|
||||
// Mark NPC
|
||||
if (plugin.getNpcsToMark() != null)
|
||||
for (NPC npcToMark : plugin.getNpcsToMark())
|
||||
{
|
||||
for (NPC npc : plugin.getNpcsToMark())
|
||||
{
|
||||
OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET);
|
||||
}
|
||||
OverlayUtil.renderActorOverlayImage(graphics, npcToMark, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
// once the number of possible dig locations is below 10, show the dig spots
|
||||
final Collection<HotColdLocation> digLocations = hotColdSolver.getPossibleLocations();
|
||||
if (digLocations.size() < 10)
|
||||
{
|
||||
// Mark potential dig locations
|
||||
@@ -251,8 +245,10 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
}
|
||||
else
|
||||
{
|
||||
location = null;
|
||||
|
||||
final HotColdTemperatureChange temperatureChange = HotColdTemperatureChange.of(message);
|
||||
updatePossibleArea(localWorld, temperature, temperatureChange);
|
||||
hotColdSolver.signal(localWorld, temperature, temperatureChange);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -261,88 +257,14 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
this.lastWorldPoint = null;
|
||||
digLocations.clear();
|
||||
initializeSolver();
|
||||
}
|
||||
|
||||
private void updatePossibleArea(@Nonnull final WorldPoint worldPoint, @Nonnull final HotColdTemperature temperature, @Nullable final HotColdTemperatureChange temperatureChange)
|
||||
private void initializeSolver()
|
||||
{
|
||||
this.location = null;
|
||||
|
||||
if (digLocations.isEmpty())
|
||||
{
|
||||
digLocations.addAll(Arrays.asList(HotColdLocation.values()));
|
||||
}
|
||||
|
||||
// when the strange device reads a temperature, that means that the center of the final dig location
|
||||
// is a range of squares away from the player's current location (Chebyshev AKA Chess-board distance)
|
||||
int maxSquaresAway = temperature.getMaxDistance();
|
||||
int minSquaresAway = temperature.getMinDistance();
|
||||
|
||||
// rectangle r1 encompasses all of the points that are within the max possible distance from the player
|
||||
Point p1 = new Point(worldPoint.getX() - maxSquaresAway, worldPoint.getY() - maxSquaresAway);
|
||||
Rectangle r1 = new Rectangle((int) p1.getX(), (int) p1.getY(), 2 * maxSquaresAway + 1, 2 * maxSquaresAway + 1);
|
||||
// rectangle r2 encompasses all of the points that are within the min possible distance from the player
|
||||
Point p2 = new Point(worldPoint.getX() - minSquaresAway, worldPoint.getY() - minSquaresAway);
|
||||
Rectangle r2 = new Rectangle((int) p2.getX(), (int) p2.getY(), 2 * minSquaresAway + 1, 2 * minSquaresAway + 1);
|
||||
|
||||
// eliminate from consideration dig spots that lie entirely within the min range or entirely outside of the max range
|
||||
digLocations.removeIf(entry -> r2.contains(entry.getRect()) || !r1.intersects(entry.getRect()));
|
||||
|
||||
// if a previous world point has been recorded, we can consider the warmer/colder result from the strange device
|
||||
if (lastWorldPoint != null && temperatureChange != null)
|
||||
{
|
||||
switch (temperatureChange)
|
||||
{
|
||||
case COLDER:
|
||||
// eliminate spots that are absolutely warmer
|
||||
digLocations.removeIf(entry -> isFirstPointCloserRect(worldPoint, lastWorldPoint, entry.getRect()));
|
||||
break;
|
||||
case WARMER:
|
||||
// eliminate spots that are absolutely colder
|
||||
digLocations.removeIf(entry -> isFirstPointCloserRect(lastWorldPoint, worldPoint, entry.getRect()));
|
||||
break;
|
||||
case SAME:
|
||||
// I couldn't figure out a clean implementation for this case
|
||||
// not necessary for quickly determining final location
|
||||
}
|
||||
}
|
||||
|
||||
lastWorldPoint = worldPoint;
|
||||
}
|
||||
|
||||
private boolean isFirstPointCloserRect(WorldPoint firstWp, WorldPoint secondWp, Rectangle2D r)
|
||||
{
|
||||
WorldPoint p1 = new WorldPoint((int) r.getMaxX(), (int) r.getMaxY(), 0);
|
||||
|
||||
if (!isFirstPointCloser(firstWp, secondWp, p1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPoint p2 = new WorldPoint((int) r.getMaxX(), (int) r.getMinY(), 0);
|
||||
|
||||
if (!isFirstPointCloser(firstWp, secondWp, p2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPoint p3 = new WorldPoint((int) r.getMinX(), (int)r.getMaxY(), 0);
|
||||
|
||||
if (!isFirstPointCloser(firstWp, secondWp, p3))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldPoint p4 = new WorldPoint((int) r.getMinX(), (int) r.getMinY(), 0);
|
||||
return (isFirstPointCloser(firstWp, secondWp, p4));
|
||||
}
|
||||
|
||||
private boolean isFirstPointCloser(WorldPoint firstWp, WorldPoint secondWp, WorldPoint wp)
|
||||
{
|
||||
int firstDistance = firstWp.distanceTo2D(wp);
|
||||
int secondDistance = secondWp.distanceTo2D(wp);
|
||||
return (firstDistance < secondDistance);
|
||||
final Set<HotColdLocation> locations = Arrays.stream(HotColdLocation.values())
|
||||
.collect(Collectors.toSet());
|
||||
hotColdSolver = new HotColdSolver(locations);
|
||||
}
|
||||
|
||||
private void markFinalSpot(WorldPoint wp)
|
||||
@@ -355,4 +277,4 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
|
||||
{
|
||||
return new String[] {npc};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
package net.runelite.client.plugins.cluescrolls.clues.hotcold;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
@@ -180,7 +179,7 @@ public enum HotColdLocation
|
||||
private final HotColdArea hotColdArea;
|
||||
private final String area;
|
||||
|
||||
public Rectangle2D getRect()
|
||||
public Rectangle getRect()
|
||||
{
|
||||
return new Rectangle(worldPoint.getX() - 4, worldPoint.getY() - 4, 9, 9);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Eadgars Ruse <https://github.com/Eadgars-Ruse>
|
||||
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.com>
|
||||
* 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 OWNER 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.cluescrolls.clues.hotcold;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
|
||||
/**
|
||||
* Solution finder for hot-cold style puzzles.
|
||||
* <p>
|
||||
* These puzzles are established by having some way to test the distance from the solution via "warmth", where being
|
||||
* colder means one is farther away from the target, and being warmer means one is closer to it, with the goal being to
|
||||
* reach the most warm value to discover the solution point. Hot-cold puzzles in Old School Runescape are implemented
|
||||
* with specific set of solution points, so this solver will filter from a provided set of possible solutions as new
|
||||
* signals of temperatures and temperature changes are provided.
|
||||
*/
|
||||
@Getter
|
||||
public class HotColdSolver
|
||||
{
|
||||
private final Set<HotColdLocation> possibleLocations;
|
||||
@Nullable
|
||||
private WorldPoint lastWorldPoint;
|
||||
|
||||
public HotColdSolver(Set<HotColdLocation> possibleLocations)
|
||||
{
|
||||
this.possibleLocations = possibleLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a hot-cold update given a {@link WorldPoint} where a check occurred and the resulting temperature and
|
||||
* temperature change discovered at that point. This will filter the set of possible locations which can be the
|
||||
* solution.
|
||||
*
|
||||
* @param worldPoint The point where a hot-cold check occurred
|
||||
* @param temperature The temperature of the checked point
|
||||
* @param temperatureChange The change of temperature of the checked point compared to the previously-checked point
|
||||
* @return A set of {@link HotColdLocation}s which are still possible after the filtering occurs. This return value
|
||||
* is the same as would be returned by {@code getPossibleLocations()}.
|
||||
*/
|
||||
public Set<HotColdLocation> signal(@Nonnull final WorldPoint worldPoint, @Nonnull final HotColdTemperature temperature, @Nullable final HotColdTemperatureChange temperatureChange)
|
||||
{
|
||||
// when the strange device reads a temperature, that means that the center of the final dig location
|
||||
// is a range of squares away from the player's current location (Chebyshev AKA Chess-board distance)
|
||||
int maxSquaresAway = temperature.getMaxDistance();
|
||||
int minSquaresAway = temperature.getMinDistance();
|
||||
|
||||
// maxDistanceArea encompasses all of the points that are within the max possible distance from the player
|
||||
final Rectangle maxDistanceArea = new Rectangle(
|
||||
worldPoint.getX() - maxSquaresAway,
|
||||
worldPoint.getY() - maxSquaresAway,
|
||||
2 * maxSquaresAway + 1,
|
||||
2 * maxSquaresAway + 1);
|
||||
// minDistanceArea encompasses all of the points that are within the min possible distance from the player
|
||||
final Rectangle minDistanceArea = new Rectangle(
|
||||
worldPoint.getX() - minSquaresAway,
|
||||
worldPoint.getY() - minSquaresAway,
|
||||
2 * minSquaresAway + 1,
|
||||
2 * minSquaresAway + 1);
|
||||
|
||||
// eliminate from consideration dig spots that lie entirely within the min range or entirely outside of the max range
|
||||
possibleLocations.removeIf(entry -> minDistanceArea.contains(entry.getRect()) || !maxDistanceArea.intersects(entry.getRect()));
|
||||
|
||||
// if a previous world point has been recorded, we can consider the warmer/colder result from the strange device
|
||||
if (lastWorldPoint != null && temperatureChange != null)
|
||||
{
|
||||
switch (temperatureChange)
|
||||
{
|
||||
case COLDER:
|
||||
// eliminate spots that are absolutely warmer
|
||||
possibleLocations.removeIf(entry -> isFirstPointCloserRect(worldPoint, lastWorldPoint, entry.getRect()));
|
||||
break;
|
||||
case WARMER:
|
||||
// eliminate spots that are absolutely colder
|
||||
possibleLocations.removeIf(entry -> isFirstPointCloserRect(lastWorldPoint, worldPoint, entry.getRect()));
|
||||
break;
|
||||
case SAME:
|
||||
// I couldn't figure out a clean implementation for this case
|
||||
// not necessary for quickly determining final location
|
||||
}
|
||||
}
|
||||
|
||||
lastWorldPoint = worldPoint;
|
||||
return getPossibleLocations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the first point passed is closer to each corner of the given rectangle than the second point.
|
||||
*
|
||||
* @param firstPoint First point to test. Return result will be relating to this point's location.
|
||||
* @param secondPoint Second point to test
|
||||
* @param rect Rectangle, whose corner points will be compared to the first and second points passed
|
||||
* @return {@code true} if {@code firstPoint} is closer to each of {@code rect}'s four corner points than
|
||||
* {@code secondPoint}, {@code false} otherwise.
|
||||
* @see WorldPoint#distanceTo2D
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static boolean isFirstPointCloserRect(final WorldPoint firstPoint, final WorldPoint secondPoint, final Rectangle rect)
|
||||
{
|
||||
final WorldPoint nePoint = new WorldPoint((rect.x + rect.width), (rect.y + rect.height), 0);
|
||||
|
||||
if (!isFirstPointCloser(firstPoint, secondPoint, nePoint))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final WorldPoint sePoint = new WorldPoint((rect.x + rect.width), rect.y, 0);
|
||||
|
||||
if (!isFirstPointCloser(firstPoint, secondPoint, sePoint))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final WorldPoint nwPoint = new WorldPoint(rect.x, (rect.y + rect.height), 0);
|
||||
|
||||
if (!isFirstPointCloser(firstPoint, secondPoint, nwPoint))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final WorldPoint swPoint = new WorldPoint(rect.x, rect.y, 0);
|
||||
return (isFirstPointCloser(firstPoint, secondPoint, swPoint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the first point passed is closer to the given point of comparison than the second point.
|
||||
*
|
||||
* @param firstPoint First point to test. Return result will be relating to this point's location.
|
||||
* @param secondPoint Second point to test
|
||||
* @param worldPoint Point to compare to the first and second points passed
|
||||
* @return {@code true} if {@code firstPoint} is closer to {@code worldPoint} than {@code secondPoint},
|
||||
* {@code false} otherwise.
|
||||
* @see WorldPoint#distanceTo2D
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static boolean isFirstPointCloser(final WorldPoint firstPoint, final WorldPoint secondPoint, final WorldPoint worldPoint)
|
||||
{
|
||||
return firstPoint.distanceTo2D(worldPoint) < secondPoint.distanceTo2D(worldPoint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.com>
|
||||
* 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 OWNER 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.cluescrolls.clues.hotcold;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdSolver.isFirstPointCloser;
|
||||
import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdSolver.isFirstPointCloserRect;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HotColdSolverTest
|
||||
{
|
||||
private static final String RESPONSE_TEXT_ICE_COLD_COLDER = "The device is ice cold, but colder than last time.";
|
||||
private static final String RESPONSE_TEXT_VERY_COLD_WARMER = "The device is very cold, and warmer than last time.";
|
||||
private static final String RESPONSE_TEXT_COLD = "The device is cold.";
|
||||
private static final String RESPONSE_TEXT_COLD_COLDER = "The device is cold, but colder than last time.";
|
||||
private static final String RESPONSE_TEXT_COLD_WARMER = "The device is cold, and warmer than last time.";
|
||||
private static final String RESPONSE_TEXT_COLD_SAME_TEMP = "The device is cold, and the same temperature as last time.";
|
||||
private static final String RESPONSE_TEXT_VERY_HOT = "The device is very hot.";
|
||||
private static final String RESPONSE_TEXT_VERY_HOT_COLDER = "The device is very hot, but colder than last time.";
|
||||
private static final String RESPONSE_TEXT_VERY_HOT_WARMER = "The device is very hot, and warmer than last time.";
|
||||
private static final String RESPONSE_TEXT_VERY_HOT_SAME_TEMP = "The device is very hot, and the same temperature as last time.";
|
||||
|
||||
@Test
|
||||
public void testOneStepSolution()
|
||||
{
|
||||
final Set<HotColdLocation> foundLocation = Sets.immutableEnumSet(HotColdLocation.KARAMJA_KHARAZI_NE);
|
||||
|
||||
testSolver(createHotColdSolver(), new WorldPoint(2852, 2992, 0), RESPONSE_TEXT_VERY_HOT, foundLocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreStartingTemperatureDifference()
|
||||
{
|
||||
final WorldPoint testedPoint = new WorldPoint(2852, 2992, 0);
|
||||
final Set<HotColdLocation> foundLocations = Sets.immutableEnumSet(HotColdLocation.KARAMJA_KHARAZI_NE);
|
||||
|
||||
testSolver(createHotColdSolver(), testedPoint, RESPONSE_TEXT_VERY_HOT, foundLocations);
|
||||
testSolver(createHotColdSolver(), testedPoint, RESPONSE_TEXT_VERY_HOT_COLDER, foundLocations);
|
||||
testSolver(createHotColdSolver(), testedPoint, RESPONSE_TEXT_VERY_HOT_WARMER, foundLocations);
|
||||
testSolver(createHotColdSolver(), testedPoint, RESPONSE_TEXT_VERY_HOT_SAME_TEMP, foundLocations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameTempNoChanges()
|
||||
{
|
||||
final HotColdSolver solver = createHotColdSolver();
|
||||
final WorldPoint testedPoint = new WorldPoint(2851, 2955, 0);
|
||||
final Set<HotColdLocation> foundLocations = Sets.immutableEnumSet(
|
||||
HotColdLocation.KARAMJA_KHARAZI_NE,
|
||||
HotColdLocation.KARAMJA_KHARAZI_SW);
|
||||
|
||||
testSolver(solver, testedPoint, RESPONSE_TEXT_VERY_HOT, foundLocations);
|
||||
testSolver(solver, testedPoint, RESPONSE_TEXT_VERY_HOT_SAME_TEMP, foundLocations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoChangesAfterSolutionFound()
|
||||
{
|
||||
final HotColdSolver solver = createHotColdSolver();
|
||||
final Set<HotColdLocation> intermediateFoundLocations = Sets.immutableEnumSet(
|
||||
HotColdLocation.KARAMJA_KHARAZI_NE,
|
||||
HotColdLocation.KARAMJA_KHARAZI_SW);
|
||||
final Set<HotColdLocation> finalLocation = Sets.immutableEnumSet(HotColdLocation.KARAMJA_KHARAZI_NE);
|
||||
|
||||
testSolver(solver, new WorldPoint(2851, 2955, 0), RESPONSE_TEXT_VERY_HOT, intermediateFoundLocations);
|
||||
testSolver(solver, new WorldPoint(2852, 2955, 0), RESPONSE_TEXT_VERY_HOT_WARMER, finalLocation);
|
||||
testSolver(solver, new WorldPoint(2851, 2955, 0), RESPONSE_TEXT_VERY_HOT_COLDER, finalLocation);
|
||||
testSolver(solver, new WorldPoint(2465, 3495, 0), RESPONSE_TEXT_ICE_COLD_COLDER, finalLocation);
|
||||
testSolver(solver, new WorldPoint(3056, 3291, 0), RESPONSE_TEXT_VERY_COLD_WARMER, finalLocation);
|
||||
testSolver(solver, new WorldPoint(2571, 2956, 0), RESPONSE_TEXT_VERY_COLD_WARMER, finalLocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNarrowToFindSolutions()
|
||||
{
|
||||
final HotColdSolver solver = createHotColdSolver();
|
||||
final Set<HotColdLocation> firstLocationsSet = Sets.immutableEnumSet(
|
||||
HotColdLocation.FELDIP_HILLS_GNOME_GLITER,
|
||||
HotColdLocation.FELDIP_HILLS_RED_CHIN,
|
||||
HotColdLocation.KARAMJA_KHARAZI_NE,
|
||||
HotColdLocation.KARAMJA_CRASH_ISLAND);
|
||||
final Set<HotColdLocation> secondLocationsSet = firstLocationsSet.stream()
|
||||
.filter(location -> location != HotColdLocation.FELDIP_HILLS_RED_CHIN)
|
||||
.collect(Collectors.toSet());
|
||||
final Set<HotColdLocation> thirdLocationSet = secondLocationsSet.stream()
|
||||
.filter(location -> location != HotColdLocation.FELDIP_HILLS_GNOME_GLITER)
|
||||
.collect(Collectors.toSet());
|
||||
final Set<HotColdLocation> finalLocation = thirdLocationSet.stream()
|
||||
.filter(location -> location != HotColdLocation.KARAMJA_CRASH_ISLAND)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
testSolver(solver, new WorldPoint(2711, 2803, 0), RESPONSE_TEXT_COLD, firstLocationsSet);
|
||||
testSolver(solver, new WorldPoint(2711, 2802, 0), RESPONSE_TEXT_COLD_SAME_TEMP, firstLocationsSet);
|
||||
testSolver(solver, new WorldPoint(2716, 2802, 0), RESPONSE_TEXT_COLD_WARMER, secondLocationsSet);
|
||||
testSolver(solver, new WorldPoint(2739, 2808, 0), RESPONSE_TEXT_COLD_WARMER, thirdLocationSet);
|
||||
testSolver(solver, new WorldPoint(2810, 2757, 0), RESPONSE_TEXT_COLD_COLDER, finalLocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSomewhatDistantLocations()
|
||||
{
|
||||
// Activate device on Ape Atoll when solution point is HotColdLocation.KARAMJA_KHARAZI_NE
|
||||
testSolver(createHotColdSolver(), new WorldPoint(2723, 2743, 0), RESPONSE_TEXT_COLD,
|
||||
Sets.immutableEnumSet(
|
||||
HotColdLocation.KARAMJA_KHARAZI_NE,
|
||||
HotColdLocation.KARAMJA_KHARAZI_SW,
|
||||
HotColdLocation.KARAMJA_CRASH_ISLAND,
|
||||
HotColdLocation.FELDIP_HILLS_SW,
|
||||
HotColdLocation.FELDIP_HILLS_RANTZ,
|
||||
HotColdLocation.FELDIP_HILLS_RED_CHIN,
|
||||
HotColdLocation.FELDIP_HILLS_SE));
|
||||
|
||||
// Activate device near fairy ring DKP when solution point is HotColdLocation.KARAMJA_KHARAZI_NE
|
||||
testSolver(createHotColdSolver(), new WorldPoint(2900, 3111, 0), RESPONSE_TEXT_COLD,
|
||||
Sets.immutableEnumSet(
|
||||
HotColdLocation.KARAMJA_WEST_BRIMHAVEN,
|
||||
HotColdLocation.KARAMJA_KHARAZI_NE,
|
||||
HotColdLocation.ASGARNIA_COW,
|
||||
HotColdLocation.ASGARNIA_CRAFT_GUILD,
|
||||
HotColdLocation.KANDARIN_WITCHHAVEN,
|
||||
HotColdLocation.MISTHALIN_DRAYNOR_BANK));
|
||||
|
||||
// Activate device on Mudskipper Point when solution point is HotColdLocation.KARAMJA_KHARAZI_NE
|
||||
testSolver(createHotColdSolver(), new WorldPoint(2985, 3106, 0), RESPONSE_TEXT_COLD,
|
||||
Sets.immutableEnumSet(
|
||||
HotColdLocation.KARAMJA_BRIMHAVEN_FRUIT_TREE,
|
||||
HotColdLocation.KARAMJA_KHARAZI_NE,
|
||||
HotColdLocation.ASGARNIA_COW,
|
||||
HotColdLocation.ASGARNIA_CRAFT_GUILD,
|
||||
HotColdLocation.MISTHALIN_LUMBRIDGE_2,
|
||||
HotColdLocation.DESERT_BEDABIN_CAMP));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsFirstPointCloserRect()
|
||||
{
|
||||
assertFalse(isFirstPointCloserRect(new WorldPoint(0, 0, 0), new WorldPoint(0, 0, 0), new Rectangle(0, 0, 1, 1)));
|
||||
assertFalse(isFirstPointCloserRect(new WorldPoint(1, 0, 0), new WorldPoint(5, 0, 0), new Rectangle(2, 1, 5, 5)));
|
||||
assertFalse(isFirstPointCloserRect(new WorldPoint(1, 0, 0), new WorldPoint(0, 0, 0), new Rectangle(2, 0, 1, 2)));
|
||||
assertFalse(isFirstPointCloserRect(new WorldPoint(0, 0, 0), new WorldPoint(1, 1, 1), new Rectangle(2, 2, 2, 2)));
|
||||
assertFalse(isFirstPointCloserRect(new WorldPoint(0, 0, 0), new WorldPoint(4, 4, 4), new Rectangle(1, 1, 2, 2)));
|
||||
assertFalse(isFirstPointCloserRect(new WorldPoint(3, 2, 0), new WorldPoint(1, 5, 0), new Rectangle(0, 0, 4, 4)));
|
||||
|
||||
assertTrue(isFirstPointCloserRect(new WorldPoint(1, 1, 0), new WorldPoint(0, 1, 0), new Rectangle(2, 0, 3, 2)));
|
||||
assertTrue(isFirstPointCloserRect(new WorldPoint(4, 4, 0), new WorldPoint(1, 1, 0), new Rectangle(3, 3, 2, 2)));
|
||||
assertTrue(isFirstPointCloserRect(new WorldPoint(3, 2, 0), new WorldPoint(7, 0, 0), new Rectangle(1, 3, 4, 2)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsFirstPointCloser()
|
||||
{
|
||||
assertFalse(isFirstPointCloser(new WorldPoint(0, 0, 0), new WorldPoint(0, 0, 0), new WorldPoint(0, 0, 0)));
|
||||
assertFalse(isFirstPointCloser(new WorldPoint(0, 0, 0), new WorldPoint(0, 0, 1), new WorldPoint(0, 0, 0)));
|
||||
assertFalse(isFirstPointCloser(new WorldPoint(1, 0, 0), new WorldPoint(0, 0, 0), new WorldPoint(1, 1, 0)));
|
||||
assertFalse(isFirstPointCloser(new WorldPoint(2, 2, 0), new WorldPoint(0, 0, 0), new WorldPoint(1, 1, 0)));
|
||||
|
||||
assertTrue(isFirstPointCloser(new WorldPoint(1, 0, 0), new WorldPoint(0, 0, 0), new WorldPoint(2, 0, 0)));
|
||||
assertTrue(isFirstPointCloser(new WorldPoint(1, 1, 0), new WorldPoint(1, 0, 0), new WorldPoint(2, 2, 0)));
|
||||
assertTrue(isFirstPointCloser(new WorldPoint(1, 1, 1), new WorldPoint(0, 1, 0), new WorldPoint(1, 1, 0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a hot-cold solver by signalling a test point, temperature, and temperature change to it and asserting the
|
||||
* resulting possible location set is equal to that of a given set of expected locations.
|
||||
*
|
||||
* @param solver The hot-cold solver to signal to.
|
||||
* <br>
|
||||
* Note: This will mutate the passed solver, which is helpful for testing
|
||||
* multiple sequential steps.
|
||||
* @param testPoint The {@link WorldPoint} where the signal occurs.
|
||||
* @param deviceResponse The string containing the temperature and temperature change which is
|
||||
* given when the hot-cold checking device is activated.
|
||||
* @param expectedRemainingPossibleLocations A {@link Set} of {@link HotColdLocation}s which is expected to be
|
||||
* given by {@link HotColdSolver#getPossibleLocations()} after it receives
|
||||
* the signal formed by the other given arguments.
|
||||
*/
|
||||
private static void testSolver(final HotColdSolver solver, final WorldPoint testPoint, final String deviceResponse, final Set<HotColdLocation> expectedRemainingPossibleLocations)
|
||||
{
|
||||
final HotColdTemperature temperature = HotColdTemperature.of(deviceResponse);
|
||||
final HotColdTemperatureChange temperatureChange = HotColdTemperatureChange.of(deviceResponse);
|
||||
|
||||
assertNotNull(temperature);
|
||||
assertEquals(expectedRemainingPossibleLocations, solver.signal(testPoint, temperature, temperatureChange));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A hot-cold solver with a starting set of master hot-cold locations nearby the KARAMJA_KHARAZI_NE
|
||||
* location. {@link HotColdLocation#values()} is not used as it may change with future game updates, and
|
||||
* such changes would break this test suite.
|
||||
*/
|
||||
private static HotColdSolver createHotColdSolver()
|
||||
{
|
||||
final Set<HotColdLocation> hotColdLocations = Sets.immutableEnumSet(
|
||||
HotColdLocation.KARAMJA_KHARAZI_NE,
|
||||
HotColdLocation.KARAMJA_KHARAZI_SW,
|
||||
HotColdLocation.KARAMJA_GLIDER,
|
||||
HotColdLocation.KARAMJA_MUSA_POINT,
|
||||
HotColdLocation.KARAMJA_BRIMHAVEN_FRUIT_TREE,
|
||||
HotColdLocation.KARAMJA_WEST_BRIMHAVEN,
|
||||
HotColdLocation.KARAMJA_CRASH_ISLAND,
|
||||
HotColdLocation.DESERT_BEDABIN_CAMP,
|
||||
HotColdLocation.DESERT_MENAPHOS_GATE,
|
||||
HotColdLocation.DESERT_POLLNIVNEACH,
|
||||
HotColdLocation.DESERT_SHANTY,
|
||||
HotColdLocation.MISTHALIN_LUMBRIDGE,
|
||||
HotColdLocation.MISTHALIN_LUMBRIDGE_2,
|
||||
HotColdLocation.MISTHALIN_DRAYNOR_BANK,
|
||||
HotColdLocation.ASGARNIA_COW,
|
||||
HotColdLocation.ASGARNIA_PARTY_ROOM,
|
||||
HotColdLocation.ASGARNIA_CRAFT_GUILD,
|
||||
HotColdLocation.ASGARNIA_RIMMINGTON,
|
||||
HotColdLocation.ASGARNIA_MUDSKIPPER,
|
||||
HotColdLocation.KANDARIN_WITCHHAVEN,
|
||||
HotColdLocation.KANDARIN_NECRO_TOWER,
|
||||
HotColdLocation.KANDARIN_FIGHT_ARENA,
|
||||
HotColdLocation.KANDARIN_TREE_GNOME_VILLAGE,
|
||||
HotColdLocation.FELDIP_HILLS_GNOME_GLITER,
|
||||
HotColdLocation.FELDIP_HILLS_JIGGIG,
|
||||
HotColdLocation.FELDIP_HILLS_RANTZ,
|
||||
HotColdLocation.FELDIP_HILLS_RED_CHIN,
|
||||
HotColdLocation.FELDIP_HILLS_SE,
|
||||
HotColdLocation.FELDIP_HILLS_SOUTH,
|
||||
HotColdLocation.FELDIP_HILLS_SW
|
||||
);
|
||||
return new HotColdSolver(hotColdLocations);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user