From 295251a4f096661e791556bb949546f54947a6fc Mon Sep 17 00:00:00 2001 From: davpapp Date: Mon, 12 Mar 2018 05:54:47 -0400 Subject: [PATCH] added random detection, not enough testing --- src/CameraCalibrator.java | 8 +- src/Cursor.java | 5 ++ src/HumanBehavior.java | 1 + src/ImageCollector.java | 8 +- src/IronMiner.java | 27 +++++- src/ObjectDetector.java | 11 ++- src/RandomDetector.java | 106 +++++++++++++++++++----- src/RandomDetectorTest.java | 46 ++++++++++ src/WorldHopper.java | 62 ++++++++++++++ target/classes/CameraCalibrator.class | Bin 1850 -> 1850 bytes target/classes/Cursor.class | Bin 9107 -> 9296 bytes target/classes/HumanBehavior.class | Bin 1429 -> 1429 bytes target/classes/ImageCollector$1.class | Bin 843 -> 843 bytes target/classes/ImageCollector.class | Bin 4170 -> 4335 bytes target/classes/IronMiner.class | Bin 5203 -> 5836 bytes target/classes/ObjectDetector.class | Bin 6998 -> 7259 bytes target/classes/RandomDetector.class | Bin 1753 -> 4063 bytes target/classes/RandomDetectorTest.class | Bin 0 -> 2328 bytes target/classes/WorldHopper.class | Bin 0 -> 2132 bytes 19 files changed, 240 insertions(+), 34 deletions(-) create mode 100644 src/RandomDetectorTest.java create mode 100644 src/WorldHopper.java create mode 100644 target/classes/RandomDetectorTest.class create mode 100644 target/classes/WorldHopper.class diff --git a/src/CameraCalibrator.java b/src/CameraCalibrator.java index fca9164..187b266 100644 --- a/src/CameraCalibrator.java +++ b/src/CameraCalibrator.java @@ -16,21 +16,21 @@ public class CameraCalibrator { public void rotateUntilObjectFound(ObjectDetector objectDetector, String objectNameToLookFor) throws Exception { BufferedImage screenCapture = objectDetector.captureScreenshotGameWindow(); - ArrayList detectedObjects = objectDetector.getObjectsInImage(screenCapture, 0.30); + ArrayList detectedObjects = objectDetector.getObjectsInImage(screenCapture, 0.40); ArrayList detectedObjectsToLookFor = objectDetector.getObjectsOfClassInList(detectedObjects, objectNameToLookFor); while (detectedObjectsToLookFor.size() == 0) { randomlyRotateKeyboard(); screenCapture = objectDetector.captureScreenshotGameWindow(); - detectedObjects = objectDetector.getObjectsInImage(screenCapture, 0.30); + detectedObjects = objectDetector.getObjectsInImage(screenCapture, 0.40); detectedObjectsToLookFor = objectDetector.getObjectsOfClassInList(detectedObjects, objectNameToLookFor); } } private void randomlyRotateKeyboard() throws InterruptedException { - int keyPressLength = Randomizer.nextGaussianWithinRange(150, 505); + int keyPressLength = Randomizer.nextGaussianWithinRange(50, 160); robot.keyPress(KeyEvent.VK_LEFT); Thread.sleep(keyPressLength); robot.keyRelease(KeyEvent.VK_LEFT); - Thread.sleep(Randomizer.nextGaussianWithinRange(120, 250)); + Thread.sleep(Randomizer.nextGaussianWithinRange(80, 118)); } } diff --git a/src/Cursor.java b/src/Cursor.java index 6bc2ad4..307c316 100644 --- a/src/Cursor.java +++ b/src/Cursor.java @@ -88,6 +88,7 @@ public class Cursor { } public void rightClickCursor() throws InterruptedException { + Thread.sleep(50, 80); robot.mousePress(InputEvent.BUTTON3_DOWN_MASK); Thread.sleep(getRandomClickLength() + 20); robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK); @@ -255,4 +256,8 @@ public class Cursor { } System.out.println("--------------"); } + + public Point getOffsetPoint(Point point) { + return new Point(point.x + Constants.GAME_WINDOW_OFFSET_X, point.y + Constants.GAME_WINDOW_OFFSET_Y); + } } \ No newline at end of file diff --git a/src/HumanBehavior.java b/src/HumanBehavior.java index 97afa03..18a9057 100644 --- a/src/HumanBehavior.java +++ b/src/HumanBehavior.java @@ -1,3 +1,4 @@ +import java.util.ArrayList; public class HumanBehavior { diff --git a/src/ImageCollector.java b/src/ImageCollector.java index b1e4b02..36dc467 100644 --- a/src/ImageCollector.java +++ b/src/ImageCollector.java @@ -25,6 +25,10 @@ public class ImageCollector { * detect last file name */ + public BufferedImage captureScreenshotGameWindow() throws IOException, AWTException { + return robot.createScreenCapture(gameWindowRectangle); + } + public ImageCollector(String screenshotOutputDirectory) throws AWTException { initializeGameWindowRectangle(); initializeFullWindowRectangle(); @@ -98,8 +102,8 @@ public class ImageCollector { public static void main(String[] args) throws Exception { ImageCollector imageCollector = new ImageCollector("/home/dpapp/Desktop/RunescapeAI/Images/"); - //imageCollector.collectImages("ore"); + imageCollector.collectImages("chatDialogue"); //imageCollector.generateInventoryImages(); - imageCollector.captureAndSaveFullWindow(); + //imageCollector.captureAndSaveFullWindow(); } } diff --git a/src/IronMiner.java b/src/IronMiner.java index 4405811..f161106 100644 --- a/src/IronMiner.java +++ b/src/IronMiner.java @@ -33,6 +33,8 @@ public class IronMiner { Robot robot; HumanBehavior humanBehavior; CameraCalibrator cameraCalibrator; + RandomDetector randomDetector; + WorldHopper worldHopper; public IronMiner() throws AWTException, IOException { @@ -43,17 +45,31 @@ public class IronMiner { robot = new Robot(); humanBehavior = new HumanBehavior(); cameraCalibrator = new CameraCalibrator(); + randomDetector = new RandomDetector(); + worldHopper = new WorldHopper(); } public void run() throws Exception { long startTime = System.currentTimeMillis(); + int noIronOresDetected = 0; - while (((System.currentTimeMillis() - startTime) / 1000.0 / 60) < 85) { + int count = 0; + while (((System.currentTimeMillis() - startTime) / 1000.0 / 75) < 75) { + count++; BufferedImage screenCapture = objectDetector.captureScreenshotGameWindow(); - ArrayList detectedObjects = objectDetector.getObjectsInImage(screenCapture, 0.30); - ArrayList ironOres = objectDetector.getObjectsOfClassInList(detectedObjects, "ironOre"); + ArrayList detectedObjects = objectDetector.getObjectsInImage(screenCapture, 0.20); + ArrayList ironOres = objectDetector.getIronOres(detectedObjects); + if (ironOres.size() == 0) { + noIronOresDetected++; + } + else { + noIronOresDetected = 0; + } + if (noIronOresDetected > 80) { + cameraCalibrator.rotateUntilObjectFound(objectDetector, "ironOre"); + } DetectedObject closestIronOre = getClosestObjectToCharacter(ironOres); if (closestIronOre != null) { @@ -87,10 +103,15 @@ public class IronMiner { } humanBehavior.randomlyCheckMiningXP(cursor); + randomDetector.dealWithRandoms(screenCapture, cursor); dropInventoryIfFull(); + if (count % 100 == 0) { + System.out.println((System.currentTimeMillis() - startTime) / 1000); + } } } + private void dropInventoryIfFull() throws Exception { inventory.updateLastSlot(); if (inventory.isLastSlotInInventoryFull()) { diff --git a/src/ObjectDetector.java b/src/ObjectDetector.java index b8161a4..c513264 100644 --- a/src/ObjectDetector.java +++ b/src/ObjectDetector.java @@ -85,7 +85,6 @@ public class ObjectDetector { } } } - outputs = null; return detectedObjectsInImage; } @@ -112,6 +111,10 @@ public class ObjectDetector { } return detectedObjectsOfType; } + + public ArrayList getIronOres(ArrayList detectedObjects) { + return getObjectsOfClassInList(detectedObjects, "ironOre"); + } private Tensor makeImageTensor(BufferedImage image) throws IOException { BufferedImage formattedImage = convertBufferedImage(image, BufferedImage.TYPE_3BYTE_BGR); @@ -140,7 +143,7 @@ public class ObjectDetector { } public BufferedImage captureScreenshotGameWindow() throws IOException, AWTException { - Rectangle area = new Rectangle(Constants.GAME_WINDOW_OFFSET_X, Constants.GAME_WINDOW_OFFSET_Y, Constants.GAME_WINDOW_WIDTH, Constants.GAME_WINDOW_HEIGHT); - return robot.createScreenCapture(area); - } + Rectangle area = new Rectangle(Constants.GAME_WINDOW_OFFSET_X, Constants.GAME_WINDOW_OFFSET_Y, Constants.GAME_WINDOW_WIDTH, Constants.GAME_WINDOW_HEIGHT); + return robot.createScreenCapture(area); + } } \ No newline at end of file diff --git a/src/RandomDetector.java b/src/RandomDetector.java index bcdcdeb..9f3cc15 100644 --- a/src/RandomDetector.java +++ b/src/RandomDetector.java @@ -1,7 +1,14 @@ import java.awt.AWTException; +import java.awt.Color; +import java.awt.Point; import java.awt.Rectangle; import java.awt.Robot; import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +import javax.imageio.ImageIO; public class RandomDetector { Robot robot; @@ -10,41 +17,98 @@ public class RandomDetector { robot = new Robot(); } - public void dealWithRandoms(BufferedImage screenCapture) throws AWTException, InterruptedException { - if (isRandomEventPresent(screenCapture)) { - System.out.println("Deal with random here"); + public void dealWithRandoms(BufferedImage screenCapture, Cursor cursor) throws Exception { + Point chatDialogueCornerPoint = findChatDialogueCornerPoint(screenCapture); + Point speakerPoint = findSpeakerPointFromCornerPoint(screenCapture, chatDialogueCornerPoint); + + if (speakerPoint != null && isSpeakerPointCloseToCharacter(speakerPoint)) { + cursor.moveAndRightlickAtCoordinatesWithRandomness(cursor.getOffsetPoint(speakerPoint), 3, 3); + Thread.sleep(Randomizer.nextGaussianWithinRange(1332, 1921)); + + if (dialogueHasDismissOption(speakerPoint)) { + cursor.moveAndLeftClickAtCoordinatesWithRandomness(cursor.getOffsetPoint(getDismissOptionClickLocation(speakerPoint)), 40, 10, 6); + Thread.sleep(1743, 2313); + } } } - public boolean isRandomEventPresent(BufferedImage screenCapture) throws AWTException, InterruptedException - { - // only do this if it's close to center - boolean foundDialogue = false; - for (int x = 0; x < Constants.GAME_WINDOW_WIDTH; x += 5) { - for (int y = 0; y < Constants.GAME_WINDOW_HEIGHT; y += 1) { - int color = screenCapture.getRGB(x, y); - if (pixelsAreWithinRGBTolerance(color, 10)) { + private boolean dialogueHasDismissOption(Point speakerPoint) throws IOException, AWTException { + Rectangle dialogueRectangle = new Rectangle(Constants.GAME_WINDOW_OFFSET_X + speakerPoint.x - 25, Constants.GAME_WINDOW_OFFSET_Y + speakerPoint.y, 95, 55); + BufferedImage dialogueCapture = robot.createScreenCapture(dialogueRectangle); + for (int x = 0; x < 95; x++) { + for (int y = 0; y < 55; y++) { + int pixelColor = dialogueCapture.getRGB(x, y); + if (isPixelChatColor(pixelColor)) { return true; } - } } return false; } - - private boolean pixelsAreWithinRGBTolerance(int rgb1, int rgb2) { - int[] colors1 = getRGBValuesFromPixel(rgb1); - int[] colors2 = getRGBValuesFromPixel(rgb2); - for (int i = 0; i < 3; i++) { - if (Math.abs(colors1[i] - colors2[i]) > 3) { - return false; + + public Point findChatDialogueCornerPoint(BufferedImage screenCapture) throws AWTException, InterruptedException { + for (int x = 30; x < Constants.GAME_WINDOW_WIDTH - 30; x++) { + for (int y = 20; y < Constants.GAME_WINDOW_HEIGHT - 20; y++) { + int pixelColor = screenCapture.getRGB(x, y); + if (isPixelChatColor(pixelColor)) { + return new Point(x, y); + } } } - return true; + return null; } - private int[] getRGBValuesFromPixel(int pixel) { + private boolean isPixelChatColor(int color) { + int[] colorChannels = getRGBChannelsFromPixel(color); + return (isColorWithinTolerance(colorChannels[0], 0, 3) && + isColorWithinTolerance(colorChannels[1], 255, 3) && + isColorWithinTolerance(colorChannels[2], 255, 3)); + } + + public Point findSpeakerPointFromCornerPoint(BufferedImage screenCapture, Point chatDialogueStart) { + if (chatDialogueStart == null) { + return null; + } + + int rightMostChatColorPixel = chatDialogueStart.x; + int countSinceLastChatColorPixel = 0; + for (int x = chatDialogueStart.x; x < Constants.GAME_WINDOW_WIDTH && countSinceLastChatColorPixel < 30; x++) { + for (int y = chatDialogueStart.y; y < chatDialogueStart.y + 20; y++) { + int pixelColor = screenCapture.getRGB(x, y); + if (isPixelChatColor(pixelColor)) { + rightMostChatColorPixel = x; + countSinceLastChatColorPixel = 0; + } + } + countSinceLastChatColorPixel++; + } + + int chatDialogueBoxWidth = rightMostChatColorPixel - chatDialogueStart.x; + if (chatDialogueBoxWidth > 60 && chatDialogueBoxWidth < 400) { + return new Point(chatDialogueStart.x + chatDialogueBoxWidth / 2, chatDialogueStart.y + 25); + } + return null; + } + + private boolean isSpeakerPointCloseToCharacter(Point speakerPoint) { + return (Math.abs(speakerPoint.x + Constants.GAME_WINDOW_OFFSET_X - Constants.CHARACTER_CENTER_X) < 90 && Math.abs(speakerPoint.y + Constants.GAME_WINDOW_OFFSET_Y - Constants.CHARACTER_CENTER_Y) < 80); + } + + private Point getDismissOptionClickLocation(Point speakerLocation) { + return new Point(speakerLocation.x, speakerLocation.y + 46); + } + + private boolean isColorWithinTolerance(int color1, int color2, int tolerance) { + return (Math.abs(color1 - color2) < tolerance); + } + + private int[] getRGBChannelsFromPixel(int pixel) { int[] colors = {(pixel)&0xFF, (pixel>>8)&0xFF, (pixel>>16)&0xFF, (pixel>>24)&0xFF}; return colors; } + + public BufferedImage captureScreenshotGameWindow() throws IOException, AWTException { + Rectangle area = new Rectangle(Constants.GAME_WINDOW_OFFSET_X, Constants.GAME_WINDOW_OFFSET_Y, Constants.GAME_WINDOW_WIDTH, Constants.GAME_WINDOW_HEIGHT); + return robot.createScreenCapture(area); + } } diff --git a/src/RandomDetectorTest.java b/src/RandomDetectorTest.java new file mode 100644 index 0000000..8632445 --- /dev/null +++ b/src/RandomDetectorTest.java @@ -0,0 +1,46 @@ +import static org.junit.jupiter.api.Assertions.*; + +import java.awt.AWTException; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +import javax.imageio.ImageIO; + +import org.junit.jupiter.api.Test; + +class RandomDetectorTest { + + RandomDetector randomDetector; + + void initialize() throws AWTException { + randomDetector = new RandomDetector(); + } + + @Test + void testChatDialogueFound() throws IOException, AWTException, InterruptedException { + initialize(); + for (File file : getListOfFilesFromItemDirectory("/home/dpapp/Desktop/RunescapeAI/Images/")) { + if (file.isFile()) { + BufferedImage screenCapture = ImageIO.read(file); + Point chatDialogueCornerPoint = randomDetector.findChatDialogueCornerPoint(screenCapture); + Point speakerPoint = randomDetector.findSpeakerPointFromCornerPoint(screenCapture, chatDialogueCornerPoint); + + assertNotNull(speakerPoint); + System.out.println(file.getName()); + if (speakerPoint != null) { + System.out.println("----- Random at " + speakerPoint.x + "," + speakerPoint.y + " -----"); + } + } + } + } + + + public File[] getListOfFilesFromItemDirectory(String directoryPath) { + File itemDirectory = new File(directoryPath); + return itemDirectory.listFiles(); + } + +} diff --git a/src/WorldHopper.java b/src/WorldHopper.java new file mode 100644 index 0000000..2021086 --- /dev/null +++ b/src/WorldHopper.java @@ -0,0 +1,62 @@ +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +public class WorldHopper { + + int numberOfOresMined; + int numberOfFramesWithOtherPlayers; + Queue frames; + Queue oresMined; + + public WorldHopper() { + numberOfOresMined = 0; + numberOfFramesWithOtherPlayers = 0; + frames = new LinkedList(); + for (int i = 0; i < 600; i++) { + frames.add(false); + } + oresMined = new LinkedList(); + for (int i = 0; i < 30; i++) { + oresMined.add(true); + } + } + + public void hopWorldsIfOtherPlayersPresent(ArrayList players) { + updateOtherPlayerTracking(players); + if (areOtherPlayersLikelyPresent()) { + hopWorld(); + } + } + + private void updateOtherPlayerTracking(ArrayList players) { + if (players.size() > 1) { + numberOfFramesWithOtherPlayers++; + frames.add(true); + } + else { + frames.add(false); + } + if (frames.poll()) { + numberOfFramesWithOtherPlayers--; + } + } + + public void updateOresMinedSuccessTracking(boolean success) { + oresMined.add(success); + if (success) { + numberOfOresMined++; + } + if (oresMined.poll()) { + numberOfOresMined--; + } + } + + private boolean areOtherPlayersLikelyPresent() { + return numberOfFramesWithOtherPlayers > 360 || numberOfOresMined < 18; + } + + private void hopWorld() { + System.out.println("Hopping worlds!"); + } +} diff --git a/target/classes/CameraCalibrator.class b/target/classes/CameraCalibrator.class index 33a40c5b908deeb22e8143f084788dfaeb564351..ad51fe5bc73f638d62f83d2889aae8efb8e6c0cf 100644 GIT binary patch delta 53 xcmdnRw~KGXE=Km7GeKb1=7Ws+Ox&IfV8G^(!Z6vIWhr+Ej33*u`8A6_69DAX4?_R| delta 53 zcmdnRw~KGXE=Kmt#$d4dAY(ogchUg{5MXntpD@{)Whr+Ym<#0RJ8XW<;?D#CsWT2Z diff --git a/target/classes/Cursor.class b/target/classes/Cursor.class index 701333f08a3b34ddd8cd03b2192b47d2eb9c1bba..003ef51a91e1ac9f2d7a2c4c0d522648c91a1337 100644 GIT binary patch delta 3769 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 Vk>&+S*B3~{Omh*|5ZL6d{{TFT#oqt` delta 3647 zcmb_e3vg7`8UFsw-Mf!m@+4%J-3^O8vzrAH-q8eshzYNv1P~M?kd?$@mLZEgI%R81 zn@+23@y?V^Z5^0Curo|05;rLFQYj*>Agw?j6vVdL+G<;EtEE7{bMLaogjze(o!NWO zIrp6Je|`V|pOf|Gt=<=}JbeJbIJvje+vLs>gv@!KkokSLPo6Ljx(DiA2A;%j^SrxE zy3H%@26@J;_beNH$iQJ75ag|GYHLp1*SaY_FVWhT^g{5Qfya5`sOMAqiw2J41+(6} zK6MhOTzFYfbZ^uBO{1IECr95NUzu!bTeUiF;HS_9vcZFsc*VeJHFK_SZBDkRm9qwZ zZhhaVX1_G>D>c(w;;pM%k}mw3N@OuJN}R(51EqMCFe4ip$`=aE+T4i#8>RF`vn_Xl z{(A#YQ^{kw75X0y{0VQFmvSrhKO6W9{%Tekx9IO0_#6Iie%hF(zh~fm{L?&QOmX2~ z1}@`@ncHuy4nss7W?jDnsdG5x!8x4uiXnL}>32<~)wFN)oTg-pA^B23ZCW<2NhFn4 zb#>)+YIl(##nzW5`l8BtIFRVDW&KU ze4eH2C1^+pyH!*YD-y{$iFNJqIf?t@+$Dny37e1QEzyS?G6Juf?)>3;#E_^|nB($e z(NTtsMu$LaXDGe6Nh%Ggk_v&VExsYyU`1BO88Tibm=ER;)+ZSQ)kF6glsFQ(HmrzOJc>%&P_doZIR0K;bZ1U`{Oz zh879DjVV6fL@MaDir&K35~>}!NSc>@rHwzvPY9;NWdW<;z4Y;bpX~#@fiN2hyh#D4 zdILLD!6Lkh>t|?z#`l3YdAWimd^113u~!%AI>dS9z5UMl+7q+ zkcSgbF)CtX+R-1Q;9)CKjF2jXbk1}8q+ws7PbEPttCo*{hcLBja2+JBG~bBQvB=Jq0HFT+@w;b7iHe) zOPMbb>dU0egNicauL=KX%Dny&7QfNg;;&KPt+e>z|C7ak_#Z6Z)7Rp!)8cKk_#4XN z)W@@!4Yp^DE%F^@^xgDvCF{wX%n_EBw`;A}JCn80H=%V#$hCh|>wewGbiGvViuhd> z-MFfXLylE@h9l+Sg=qj=UUsn>+SO?pJp4qg^_69szb)+J)^yS?&CmPU^eg(o#DV2U^{fCS0otdma&tae$D4h%S2W0 zG>hG37Th6v{xEycb1a(ABgzUkiuHQ}j+s9kRC=SLt7;pQq?%6$_f^P^OoJTm_Q_3j zpvJv!sUex2m``lg@2y=AhyB&O9QC$Cmh!NwecOtqwx(F7)CMLn+-1t166T>Yh?bc1 z!^2XsN8u^aYIh=UizX#nq5F{?S1$y4ZaYdsJh~lu`_Q?#SZ?l?X$S2O&d@IVlQXz0 z5NzHn(*q&({0>~fozOxmJ?!kB%dP^pMUAb{jT5|SoMbw_j3GEh>rPt72vpHYH%X6FzScCIej|*&s7ul9x!)Mt96wB;}u`L_ZZC1wfS_o5SST1zqt8%M( z+u)iUbrsw?ctq;W9?9d*o^)F-SNwEa7%D?~&E`|J)=IwBH`iXNPs`j7x|p5xZ(ES| z-OH7MbTG~bGRGgF#VQEVeVCY9+>OcAn#wUn+hDRIs5ae?0}BHy0~Z4ug8&0JgDeBjWF3}tL4F1fAYXt%5GW$VpvoXTc?nA< ZuNY8;I8amqC@M8siZzZ=c5)@F3;;@T2^9bU delta 75 zcmbQrJ(YWdG>af70}BvxF|aZS07+Q}?#VhV>4JO=91Q$G5dolxAcHD{(Bvg7nY^M5 X+zetsF>#=%!VNJt3= diff --git a/target/classes/ImageCollector$1.class b/target/classes/ImageCollector$1.class index 2a64d34dd0a73faedd09d91cddc433138ba8c350..3e0dcdaf88d257de49f65f8315a32582c8f5a3a9 100644 GIT binary patch delta 19 bcmX@jcA9O&ekMle$p@Gm7+ohbGJ634M}P(t delta 19 bcmX@jcA9O&ekMly$p@Gm7#$}wGJ634M?eM) diff --git a/target/classes/ImageCollector.class b/target/classes/ImageCollector.class index eb7f026b391b6d1c5bf9835a9ba43d6d8912ba78..e8f7c4eda9c96d89977b93e4db7f9fb5ac08e6ad 100644 GIT binary patch literal 4335 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 H;`-FCy4%{;wsxU*RZIH6c@W{VNnYmMd(J!e zEdTlMoUM4J+IZ>wiBkY(%DkgSyENn`g0WD%Yf(5EZcoM&0&mNz%XaDo+1uiYXlQA? zyE{ySoR+qcr5TYjnPBdf(Q>8vluSrXRv3$MIw}ONrrtzPJYir8CVL>5s*nw(qgr6t zsx`r$Ee5J=^;HTv$kj1j;8_;i8jdlV9R{Y^+F1&D@amW?$X#*sX2yCkVgmu&q)s8f z83j7#(PndSYp^!BEm^xNeser&px(AwpfC!BI<96KOMAP5v4!Ez;MRy0WutAeNTCSD zI+mFCW)-bmY;P`8xB?|QuBGS8t7D+qHd(IV!)P5VS*{Q3L%%C*{c43VC^e5}Et>JH#$g?5C@m5!$T z7yGTB16Bn(6(ZPd9(1&hv6fMVF2op0M>x4G5s$4%gnI;4=6FhOT`j!7mI4&V`UoU5Akd|crPe9m-hoAUecq{2ZwMeO!=hnOcl*JrP{3VKN4 z3;3cr;OcU3#FrJG##hWM^fGzIoTHaE9c4!|VKL3JWTY$H(;bY}l^HmU=d2Kq35qKI ze?bl-tuTb+=0m#QeHbScPT~dgMSYUz6i(~-8a2Ab#6uyfW%~p5CxMsYp@CsfP%NX{SyIR92X;xyPki>;b&clJ6`0 z0ONR4C=u@-nZ0GVEa{C#o#YDsINmmJ3O}@>_z|H}(OkLK)p&1XM(1i=bvh$(a(j}& zL~;!)AdqH3K`ed|eis`naJFj%P4c-@?k7b4Zr*52(EdqinJHIsxi#?rR``zgWO|Fm<)33&tkj+YU8N+m%;xT3vOZF%kOKb(z zXkEsu4CmdU7JB&hl}aWtqF2?eEqmZrlbuRtNxxNJ{2tUOtiZz3%r8te^j6uw#7@#<35OXQkQ=C7D28xXo7Y?F{;^K8f zDO@v%rIeew-7<()iYq8yH;7e7c~}6 z);TS~=PX1xjgC53X5~s_OP%i1(}*8{XSYKR!Rcri>s@&eUK%X5j0$?w*yi`?L-=@| z+vh%mGd_13sXEU|^sVzbeV!rgsWWJq@>`0%Uc+movA>OB%+4;vXA1GaaXc8vaRhR; zK%OJuJ%qTW%eS0g7TBYg1ZJK37(+9x6Vez6 zrf_&&!I2?6OYg<`%yKY|=hOIVQSJd07I|F*$n_Uz7v&7$8~ww(=&p&6KaDf|X!Tj; z;o>DC;(JA!H`ILq1@uZ^~ScnB!L}>}G;qMw=j~j6-!FCUPFrb-b&in}1R;%xdQ85GqD?b?nsf4;_8n6cWJ)Xw|ab!{}jtrE!1M78vFv zYPNz$Wm&ht1zH)IK%{PEc`R0`A(UpO@h|)1y?%_RRLjS2@0>4`tU}Q^6@ACWWdX>M z0Z=iE3M83NFZ10>w%SHg*l`I3)Ldwhl`k2rnlbQVMNP7NQ6q@Hzy_hY3gv74~joCv8Q_WWt4pw9&U!WM9=Wshr9uw1-j+rJB&5<1%f( j)fxL%usd`AC7tn&B;k|UGAGkHpUkC{#Y5_>H>Li6)4aRC delta 2595 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 delta 1084 zcmYLI>u(fw6g_8Vc4i;b-JPY|?Y7-6U7(%a?LvV{Hyb`^Lku+(n-C<6sfvipvnr|J zBc_TX(iltfBPKxj;D4aOY>5y9U-c_dlR|x09p@11+@z2~0aZ>GxM zEeU@!^VQb?dRhJ=ywBDxoL0*kwwpiN_z6F&;|;^+SsTCNH8ULu!9)BAu`i;w-{+ z2L=Z6&qyaGZ8kBbmc~nFnynUF)GP63y^R?)9$zM(vNmU{Q}F{Vs1YaCmS?xuqeNI< z55Gy43vBjqq58}@$)H-Fm=hdmkS_gD%+J_z(tco>3>0TOwZk>4W%4U(-Q$3?qr>(1GVs#tV2F z>+mvO#5-7zF$`lbYU*0*`{Wh`u@xC?LmngO5x;`h)O7RCF%65+3mp+*V94+(bc?6q zj7JXNB7l(C!9p=OiRr=aI)16++zDK&Vb;OOIGLd4*Ex;9aDIOox!@loRvLwAatM z|C4p;W#vraGKUGF;P^GWglq~9BXc-c*lPa8?{9s4Y>zbfNHpIoz;S`>!EAi&w=M_{ zGVI}XL5IIHrM4aW{njBwIgg#v`k){!X!j6cBgihg0`rd86gwq?coMAgBcSyd^VN7Q zzS`l;T8B?fvbdt~C33|&=RevyER~Qk3GDOfH2HDsLpllhFBWmJY^t$Wa2g-u60zQI K-H!_U;QR{_SI1QV diff --git a/target/classes/RandomDetector.class b/target/classes/RandomDetector.class index 93141b99a3d73676dcae0542c902293c3570867a..bbbcafd18648b6e2211587a84564d56ce84c889f 100644 GIT binary patch literal 4063 zcmai1TT@(F75+ATInX(Q(}7Mx8giRlx|E zE+=D!;6tqr4b>V922-+C&O@j}Z4CtKbp#L;sOzzF*2TG*2`fKjPGl{C4LwfU%nqA* zTh5D%es|g~$XK7o##rkzmYE%~-D&R$jpDH~B74T1vf56~O-@>QE0dD5&YrHhe8I_g z#)maT8A+L%5|$7nY}T?v-16pog;huU$9xc9E*CR zQ@Y-vV<%n`*d|>M&RXV;qQ5hFXXYvQCtl$GS?_CZw_C>^>=oE*7b-XF$~pyW$RV`6 znRcx_si@e^i#C2agg9C>B&Z6HHyugrV`8T)w{LQ?V7Wymc0T{|v(ZUkZ94YjfI!QP zbJIGR%M934({9#I-#F=ZIZi%f=S3e4%Q=bWcf&tVOo_~0+B10ZIRu$5SC3#c>U+f{Nl9 zn$BBhM#l-9q+KCvS+mrA?0kG!=Cw;lw8wR+^+LrQ<=s2Fr1oT2h6)=E7*G%8&HD^`% zYq(t5NYpzTURy0)+i6Snl}cB`RrZi_*I}f#nP&3 zpsRgtHQk|E!gn-$ zdsUI@Ozkz@X&oQnyF4<@3HB?Qh7=E{qO4b|T2_h#!2ahA+68LdGR}Zve)S>!MAKeU zJlPed9d@i4YlO#!b4y@*>;+0=ASPwY_BZIv<UN2m|AhgQ#Vh45QV^a|P z<;l#y>p12yjw;k}=I1N~ougBgi_V~S%7IygFxW(@HLFh*u%{0F_rit#f>d>R7^(^U-8vWi|lgG(5u?^^U@ zgd;y!6p#PGn1)gQ(2OTU5qOZFS3FXMd|jjQ*dvUM8dEJFU&J2?EMcZ2n1}?IQMikM|D%O~hy;v7i!1+H@QaAw zxVE_RSKmh^>|e9yc_J{k-iu0h~WtFpGJZw)L!D-!|+-a`g2}zCG=wo9}h7e z9Jeq}AbWV!zClKO^w2KbEYY{|n#1-==8jNJw>gp=fgS@-kklk8Iq?_)(NOgXQ^8Jy zZ57;v@L45`-_ezn#ZhDH(fc@HG#tB+9W71G60<)Mp)QDd{UeNyHZ?Ef^A8q$qB$%& z7FYhhP$ine;vb7Ee-aN$*mqLXV2nY$O!o(=gijGi2NA!F16=Rq{h*xjqb1Boiy8Of z6@^dMMNHw7YN}WGyo}c&@R*ldiEN8PRwes2@yYVJLo~9`+7r=byxp5f`dWQV+X8i) zNcvmDSn+1{iDaNWV@fO1w^8wNj1Z2qyiQ;{PBKx)RoL4-@1?NY@f8x!p28%574MN> znSyFCy!{%!#wWasda;Qq`jzC|N&bPcKS)xA$0#-VDW!IpgLFsmKDM<)0uff#Oox_; zXo~-uNI;q{1Vtn$&5VZe#g)HNd`gL7@#G#t)Zx~}mESZ=f0>Q|1$yM?cqtMHi}&v# zDibK5L;`{AxN^}rF4I|&x-)Djpg{dNLp&*p>O70>0$WNCIq0QuFEWXJOlCh`<=Q!x z<^_I_u^25%)+P3@#1wy&mb9;;f4130d^U z)Oc8AG7s^-arquL%j!2Sjfe4XwbXx4)^>#ug7hJKk<{x(&r1nVqFAFc6n5+wAM@f_ z-1ICF$W7}d>vChYQ>!AJ%U&P%yiZhkuP=G8$M;B*M8c!H==2(A^5R_^ZIa3^nmm{ANED`8j!?lpb!gNVW!=2lH|mcX z)wjK2AZi%JVK8cJ_J+Ml*#|syA+^VKQgTB@uBk*JVgfn>_ zRy1#lOwJ%BEStA+iEWJBcz6+cy>{_MXY CH+Q%I literal 1753 zcma)6T~8ZF6g^|_dchd5Atq1*F-=2^3Aj#EnnFU$XX=!%0wp4OTHAx!)ZTU1>xA^7 zPyC5IB30srhqh9nU+NF2`qZD$m;Q&Ug`VAY3`kW~$vbmr=H7G9xpT+Aoc{1LfIHaI z5D@5C*Uh3;nU{_%IF_wJ5r{t3pXr&hZk95u`KR0$P;D!3agHu|XZmv|v+nLp354z$ zrr}Hr1XCkh0^x?DQzT|TXOxoVPCU+?%j1i1#NVAZhjLVS`+`^cER&~M3CP5k{@fv{WhFM}61lnr_ zTS{|IuR3*`wIBTN$}wQW(F>m~>s9Yw!nln)8r~7;Z(j4iR6K%rF~P1XNoW1REITii z%Z=pR0^g0`eM|}@s>Yry*Jf<#-lSpDZPO}C3NDb9=l__=cdWWuoHz8cRjPAea8K;< z%kg_bzk*e^Y!`Pnf;r4nx1VxLFV|&l(Y7jUZXOSDVD%*baf`0;6YBazX909v_HcoIg0)-3&8uW0%N?dc!UxqX7zD|CIsy#TIo z?C_9U8RI^_M$|TCSMYrTqPIt{Yp?*>tyhLaQYBq!3pIM{i6eM z~5~^~vuZXx36Aus0e%}wO12J)WaQ0j9s7Y+tLwBiMW!-wwPK7W*Fzl5Gmf;$sqY+arr5S^-9nVi_x>)6VEs zS_<#&8rGUZ*e<8&*6;~`qRVXnzMh7R&nebz%Z&C@csrPg;q1%RZ6y(H`NB8VL^Rqk uLy2gwZ!X{ZnjwBKx>aE~##l4A50gGzlW+#>9^ovfZLJy|l_Zfv%uMlrp(iXmvM<9_;Fbz#$Xu=%8vu{IL}~ z8Yj3{tjGzk;uZ`X5!kO$9nZ>6H(?$XIM&Eh){SHkRQyO5wqyiSvlTaT%5uR89R@h+ zx?W^!cLn-#UQo1FD-4HE-=V%``;MjKOwzLxCiWtcz$t+<){0k_R>8M@-x`XyP=^2pliUDCdOH^zwvLlHo+)m9vp7k2`@TdtKmEf36Y0 zTogENad_Zv?TpbXwc#WNFlZnnU~XnXotrp|b7b5J)lM^hCkYD|4P2w0B*Sq8G8wdqF>UN-RxUKKd9?6`$ys>Zy) zmBEbXxUAF*b>RO(YBHoXH1z8xhQR7NuAt|9X|L7sRN7kLul)~vBa9mQ4(6`n4FhbG z^v(q^8iq2^MPlL_Mg?};`ee$BrYfZp)7#8jtwDze7CJG88wSQ(+IMa}Bh04Z}F3Ya3Kl|^H>Sl)%$W!sGQe=hS!?M7+=3H-j zU&~-qPBsCQV=E>c?ZkQlYqXr!aVSjk;Hvy4BCm$TW?0tk)3wEkZaPvP<^w6+G24$S z0f{))D65^igQ?6njjoo0-P=&DRU}(EY^rb?n~UuH+NAC40c?QT+q1(MxTR*n$0k0( zrvjZNjuz!CC-uJm?Nfu8h0QrLV@E4$(r@RQ-L{)UuX!LP=e$agm$i}7+d8n-SUj&V zkmit!@hc|yF~62sex#2A*P5J(}?PQn3#PyMh)e~alYxO zI6-R(uD{DvkRLO_)Y1kPe9ymvjtz~bNr)+f(a@#trs)CjIolA-LN_9k7E-lC+7zJ9K;+VKVbQs`f5?XUg; DY}GzF literal 0 HcmV?d00001 diff --git a/target/classes/WorldHopper.class b/target/classes/WorldHopper.class new file mode 100644 index 0000000000000000000000000000000000000000..03dd2e7fc589df0352fd303b04c602f7bf171264 GIT binary patch literal 2132 zcmZuxT~ixn6n@@hVM$nMlJZdqMkp0RP(rEwKoD)ISZesFK`3akOY#C+mL#*;)WK`- z`~|)2=uB^Vqtk-5Gmh8(2>*co2Jv|}8xmS3llSwS^FHS}&)L8J`~439AHviT5NKU1 zI(Gg}u~d?d4ox8P$b4+3ZL_eGUfOyjbFM(NP}$#-&eHahBg+d`LFNU-yg;IMKIfSG zvb<)wyG!n_be3)NKssfC=56nS5i(xXifh^FdlgxcQv#u^wNo(NiX#Pv-n^f#9(XWr z6^phs3sW=7saS{5JZ%*$cSazX99b36W{Y_lMi5a0ZD6wPv$dL*$tx$QDxX`par2Y`jF7^4w2XC@NpP;7ySbIW7Dq4rEP((B@%JRvcoo3OmZ`nt|)MA<$9CP69cSlke?=vf3_`0sHXL1_9Sn^TnEs~k&V%?Z|0gU&+#1Z z{$`W_=grg+BB|g@#MID{dX292BV2fip5XK|?q6{68{)4p5Ww0GLd2ti`%jPmsoM?L z?1r^dc9F!><9~nQ3qg!