1
0
mirror of https://github.com/davpapp/PowerMiner synced 2024-12-21 23:48:49 -05:00

Major refactoring work

This commit is contained in:
davpapp 2018-02-04 15:34:27 -05:00
parent c24f8588de
commit e6ab945729
17 changed files with 787 additions and 139 deletions

Binary file not shown.

Binary file not shown.

BIN
bin/CursorPathTest.class Normal file

Binary file not shown.

Binary file not shown.

BIN
bin/CursorPointTest.class Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -27,7 +27,7 @@ public class Cursor {
private Robot robot;
private Random random = new Random();
private Randomizer randomizer;
private ArrayList<ArrayList<CursorPath>> cursorPathsByDistance;
@ -36,7 +36,7 @@ public class Cursor {
initializeCursorPathsByDistanceFromFile("/home/dpapp/GhostMouse/coordinates.txt");
robot = new Robot();
random = new Random();
randomizer = new Randomizer();
}
private void initializeCursorPathsByDistanceFromFile(String path) {
@ -72,7 +72,7 @@ public class Cursor {
// TODO: make sure these are reasonable
private int getRandomClickLength() {
return random.nextInt(MAXIMUM_CLICK_LENGTH - MINIMUM_CLICK_LENGTH) + MINIMUM_CLICK_LENGTH;
return randomizer.nextGaussianWithinRange(MINIMUM_CLICK_LENGTH, MAXIMUM_CLICK_LENGTH);
}
public void leftClickCursor() throws InterruptedException {
@ -114,12 +114,12 @@ public class Cursor {
}
public void moveCursorToCoordinates(Point goalPoint) throws InterruptedException {
Point startingCursorPoint = getCurrentCursorPoint();
int distanceToMoveCursor = calculateDistanceBetweenPoints(startingCursorPoint, goalPoint);
Point startingPoint = getCurrentCursorPoint();
int distanceToMoveCursor = (int) startingPoint.distance(goalPoint);
if (distanceToMoveCursor == 0) {
return;
}
double angleToMoveCursor = calculateThetaBetweenPoints(startingCursorPoint, goalPoint);
double angleToMoveCursor = getThetaBetweenPoints(startingPoint, goalPoint);
// TODO: check if exists
CursorPath cursorPathToFollow = chooseCursorPathToFollowBasedOnDistance(distanceToMoveCursor);
@ -127,20 +127,20 @@ public class Cursor {
followCursorPath(startingCursorPoint, angleToTranslatePathBy, cursorPathToFollow);
}
//TODO: fix
private double getThetaBetweenPoints(Point startingPoint, Point goalPoint) {
return Math.atan2(startingPoint.x * goalPoint.y, 1.0 * goalPoint.x);
}
private void followCursorPath(Point startingCursorPoint, double angleToTranslatePathBy, CursorPath cursorPathToFollow) throws InterruptedException {
for (CursorPoint untranslatedCursorPoint : cursorPathToFollow.getCursorPathPoints()) {
Point translatedPointToClick = translatePoint(startingCursorPoint, angleToTranslatePathBy, untranslatedCursorPoint);
robotMouseMove(translatedPointToClick);
Thread.sleep(untranslatedCursorPoint.postMillisecondDelay);
Thread.sleep(untranslatedCursorPoint.delay);
}
}
private Point translatePoint(Point startingCursorPoint, double angleToTranslateBy, CursorPoint untranslatedCursorPoint) {
int x = (int) (startingCursorPoint.x + Math.cos(angleToTranslateBy) * untranslatedCursorPoint.x - Math.sin(angleToTranslateBy) * untranslatedCursorPoint.y);
int y = (int) (startingCursorPoint.y + Math.sin(angleToTranslateBy) * untranslatedCursorPoint.x + Math.cos(angleToTranslateBy) * untranslatedCursorPoint.y);
return new Point(x, y);
}
public void robotMouseMove(Point pointToMoveCursorTo) {
robot.mouseMove(pointToMoveCursorTo.x, pointToMoveCursorTo.y);
}
@ -154,7 +154,7 @@ public class Cursor {
double scaleFactor = getScaleFactor(newDistanceToMoveCursor, distanceToMoveCursor);
ArrayList<CursorPath> cursorPathsWithSameDistance = cursorPathsByDistance.get(newDistanceToMoveCursor);
CursorPath scaledCursorPath = cursorPathsWithSameDistance.get(random.nextInt(cursorPathsWithSameDistance.size())).getScaledCopyOfCursorPath(1.0);
CursorPath scaledCursorPath = cursorPathsWithSameDistance.get(new Random().nextInt(cursorPathsWithSameDistance.size())).getScaledCopyOfCursorPath(1.0);
return scaledCursorPath;
}
@ -175,24 +175,14 @@ public class Cursor {
return (1.0 * newDistanceToMoveCursor / distanceToMoveCursor);
}
private int calculateDistanceBetweenPoints(Point a, Point b) {
return (int) (Math.hypot(a.x - b.x, a.y - b.y));
}
public double calculateThetaBetweenPoints(Point a, Point b) {
return Math.atan2(1.0 * (b.y - a.y), 1.0 * (b.x - a.x));
}
public Point getCurrentCursorPoint() {
return MouseInfo.getPointerInfo().getLocation();
}
private int getRandomIntSigned(int tolerance) {
return random.nextInt(tolerance) - tolerance / 2;
}
private Point randomizePoint(Point goalPoint, int xTolerance, int yTolerance) {
return new Point(goalPoint.x + getRandomIntSigned(xTolerance), goalPoint.y + getRandomIntSigned(yTolerance));
Randomizer randomizer = new Randomizer();
return new Point(goalPoint.x + randomizer.nextGaussianWithinRange(-xTolerance, xTolerance), goalPoint.y + randomizer.nextGaussianWithinRange(-yTolerance, yTolerance));
}
public void displayCursorPaths() {

View File

@ -6,70 +6,86 @@ import java.util.ArrayList;
public class CursorPath {
private ArrayList<CursorPoint> pathCursorPoints;
private int pathNumPoints;
private int pathDistance;
private double pathTheta;
private int pathTimespanMilliseconds;
private ArrayList<CursorPoint> cursorPoints;
private double theta;
private int distance;
private int timespan;
public CursorPath(ArrayList<CursorPoint> cursorPoints)
{
this.pathCursorPoints = copyCursorPointsWithOffset(cursorPoints);
this.pathNumPoints = cursorPoints.size();
this.pathDistance = calculateCursorPathDistance();
this.pathTheta = calculateCursorPathTheta();
this.pathTimespanMilliseconds = calculateCursorPathTimespan();
this.cursorPoints = initializePathOfCursorPoints(cursorPoints);
this.distance = calculateCursorPathDistance();
this.theta = calculateCursorPathTheta();
this.timespan = calculateCursorPathTimespan();
}
private ArrayList<CursorPoint> copyCursorPointsWithOffset(ArrayList<CursorPoint> cursorPoints) {
ArrayList<CursorPoint> cursorPointsCopy = new ArrayList<CursorPoint>(cursorPoints.size());
private ArrayList<CursorPoint> initializePathOfCursorPoints(ArrayList<CursorPoint> cursorPoints) {
CursorPoint startingCursorPoint = cursorPoints.get(0);
ArrayList<CursorPoint> translatedCursorPoints = getTranslatedCopyOfCursorPath(cursorPoints, startingCursorPoint);
ArrayList<CursorPoint> normalizedDelayCursorPoints = getNormalizedDelayCopyOfCursorPath(translatedCursorPoints);
return normalizedDelayCursorPoints;
}
private ArrayList<CursorPoint> getTranslatedCopyOfCursorPath(ArrayList<CursorPoint> cursorPoints, CursorPoint cursorPointToTranslateBy) {
ArrayList<CursorPoint> offsetCursorPath = new ArrayList<CursorPoint>();
for (CursorPoint cursorPoint : cursorPoints) {
cursorPointsCopy.add(getOffsetCursorPoint(cursorPoint, startingCursorPoint));
offsetCursorPath.add(cursorPoint.getCursorPointTranslatedBy(cursorPointToTranslateBy));
}
calculatePostMillisecondDelays(cursorPointsCopy);
return cursorPointsCopy;
return offsetCursorPath;
}
private void calculatePostMillisecondDelays(ArrayList<CursorPoint> cursorPoints) {
for (int i = 1; i < cursorPoints.size(); i++) {
cursorPoints.get(i - 1).setPostMillisecondDelay(cursorPoints.get(i).postMillisecondDelay - cursorPoints.get(i - 1).postMillisecondDelay);
private ArrayList<CursorPoint> getNormalizedDelayCopyOfCursorPath(ArrayList<CursorPoint> cursorPoints) {
ArrayList<CursorPoint> normalizedDelayCursorPoints = new ArrayList<CursorPoint>();
for (int i = 0; i < cursorPoints.size() - 1; i++) {
CursorPoint cursorPoint = cursorPoints.get(i);
CursorPoint nextCursorPoint = cursorPoints.get(i + 1);
normalizedDelayCursorPoints.add(cursorPoint.getCursorPointWithNewDelay(nextCursorPoint.delay - cursorPoint.delay));
}
cursorPoints.get(cursorPoints.size() - 1).setPostMillisecondDelay(0);
normalizedDelayCursorPoints.add(cursorPoints.get(cursorPoints.size() - 1).getCursorPointWithNewDelay(0));
return normalizedDelayCursorPoints;
}
public ArrayList<CursorPoint> getScaledCopyOfCursorPath(double factorToScaleBy) {
ArrayList<CursorPoint> scaledCursorPath = new ArrayList<CursorPoint>();
for (CursorPoint cursorPoint : this.cursorPoints) {
scaledCursorPath.add(cursorPoint.getCursorPointScaledBy(factorToScaleBy));
}
return scaledCursorPath;
}
private CursorPoint getOffsetCursorPoint(CursorPoint cursorPoint, CursorPoint offsetPoint) {
return new CursorPoint(cursorPoint.x - offsetPoint.x, cursorPoint.y - offsetPoint.y, cursorPoint.postMillisecondDelay);
public ArrayList<CursorPoint> getRotatedCopyOfCursorPath(double angleToRotateBy) {
ArrayList<CursorPoint> rotatedCursorPath = new ArrayList<CursorPoint>();
for (CursorPoint cursorPoint : this.cursorPoints) {
rotatedCursorPath.add(cursorPoint.getCursorPointRotatedBy(angleToRotateBy));
}
return rotatedCursorPath;
}
private int calculateCursorPathTimespan() {
int sumPathTimespanMilliseconds = 0;
for (CursorPoint cursorPoint : this.pathCursorPoints) {
sumPathTimespanMilliseconds += cursorPoint.postMillisecondDelay;
for (CursorPoint cursorPoint : this.cursorPoints) {
sumPathTimespanMilliseconds += cursorPoint.delay;
}
return sumPathTimespanMilliseconds;
}
private int calculateCursorPathDistance() {
return (int) calculateDistanceBetweenCursorPoints(getStartingCursorPoint(), getEndingCursorPoint());
return (int) (getStartingCursorPoint().getDistanceFrom(getEndingCursorPoint()));
}
private double calculateCursorPathTheta() {
CursorPoint endingCursorPoint = getEndingCursorPoint();
return Math.atan2(1.0 * endingCursorPoint.y, 1.0 * endingCursorPoint.x);
return getStartingCursorPoint().getThetaFrom(getEndingCursorPoint());
}
private CursorPoint getStartingCursorPoint() {
return pathCursorPoints.get(0);
public CursorPoint getStartingCursorPoint() {
return this.cursorPoints.get(0);
}
private CursorPoint getEndingCursorPoint() {
return pathCursorPoints.get(pathNumPoints - 1);
public CursorPoint getEndingCursorPoint() {
return this.cursorPoints.get(this.cursorPoints.size() - 1);
}
private double calculateDistanceBetweenCursorPoints(CursorPoint a, CursorPoint b) {
return Math.hypot(a.x - b.x, a.y - b.y);
}
public boolean isCursorPathReasonable() {
return isCursorPathTimespanReasonable() && isCursorPathDistanceReasonable() &&
@ -77,42 +93,35 @@ public class CursorPath {
}
private boolean isCursorPathTimespanReasonable() {
return (this.pathTimespanMilliseconds > 50 && this.pathTimespanMilliseconds < 400);
return (this.timespan > 50 && this.timespan < 400);
}
private boolean isCursorPathDistanceReasonable() {
return (this.pathDistance > 0 && this.pathDistance < 1000);
return (this.distance > 0 && this.distance < 1000);
}
private boolean isCursorPathNumPointsReasonable() {
return (this.pathNumPoints > 0 && this.pathNumPoints < 50);
return (this.cursorPoints.size() > 0 && this.cursorPoints.size() < 50);
}
public ArrayList<CursorPoint> getCursorPathPoints() {
return pathCursorPoints;
return cursorPoints;
}
public int getCursorPathDistance() {
return pathDistance;
return distance;
}
public double getCursorPathTheta() {
return pathTheta;
}
public CursorPath getScaledCopyOfCursorPath(double scaleFactor) {
ArrayList<CursorPoint> scaledCursorPath = new ArrayList<CursorPoint>();
for (CursorPoint cursorPoint : this.pathCursorPoints) {
scaledCursorPath.add(new CursorPoint((int) (cursorPoint.x * scaleFactor), (int) (cursorPoint.y * scaleFactor), (int) (cursorPoint.postMillisecondDelay * scaleFactor)));
}
return new CursorPath(scaledCursorPath);
return theta;
}
public void displayCursorPoints() {
for (CursorPoint p : pathCursorPoints) {
for (CursorPoint p : this.cursorPoints) {
p.display();
}
System.out.println("Length:" + pathNumPoints + ", Timespan:" + pathTimespanMilliseconds);
System.out.println("Number of points:" + this.cursorPoints.size() + ", Timespan:" + this.timespan);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~ End of Path ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
}

78
src/CursorPathTest.java Normal file
View File

@ -0,0 +1,78 @@
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
class CursorPathTest {
ArrayList<CursorPath> cursorPaths;
@Test
void testCursorPathOffset() {
initializeCursorPath();
for (CursorPath cursorPath : cursorPaths) {
testCursorPathStartsAtOrigin(cursorPath);
testEndingCursorPointInCursorPathHasZeroDelay(cursorPath);
//testCursorPathLocations(cursorPath);
testCursorPathRotation(cursorPath, 15);
testCursorPathScaling(cursorPath, 1.15);
cursorPath.displayCursorPoints();
}
}
private void testCursorPathStartsAtOrigin(CursorPath cursorPath) {
CursorPoint startingCursorPoint = cursorPath.getStartingCursorPoint();
assertTrue(startingCursorPoint.x == 0 && startingCursorPoint.y == 0);
}
private void testEndingCursorPointInCursorPathHasZeroDelay(CursorPath cursorPath) {
CursorPoint endingCursorPoint = cursorPath.getEndingCursorPoint();
assertEquals(endingCursorPoint.delay, 0);
}
/*private void testCursorPathLocations(CursorPath cursorPath) {
}*/
void testCursorPathRotation(CursorPath cursorPath, double angleToRotateBy) {
ArrayList<CursorPoint> cursorPoints = cursorPath.getCursorPathPoints();
ArrayList<CursorPoint> rotatedCursorPoints = cursorPath.getRotatedCopyOfCursorPath(angleToRotateBy);
assertEquals(cursorPoints.size(), rotatedCursorPoints.size());
CursorPoint startingPoint = cursorPoints.get(0);
for (int i = 1; i < cursorPoints.size(); i++) {
double originalThetaOfCursorPoint = cursorPoints.get(i).getThetaFrom(startingPoint);
double rotatedThetaOfCursorPoint = rotatedCursorPoints.get(i).getThetaFrom(startingPoint);
System.out.println(originalThetaOfCursorPoint + ", " + rotatedThetaOfCursorPoint);
assertInRangeByAbsoluteValue(originalThetaOfCursorPoint + angleToRotateBy, rotatedThetaOfCursorPoint, 3);
}
}
void testCursorPathScaling(CursorPath cursorPath, double factorToScaleBy) {
ArrayList<CursorPoint> cursorPoints = cursorPath.getCursorPathPoints();
ArrayList<CursorPoint> scaledCursorPoints = cursorPath.getScaledCopyOfCursorPath(factorToScaleBy);
assertEquals(cursorPoints.size(), scaledCursorPoints.size());
CursorPoint startingPoint = cursorPoints.get(0);
for (int i = 0; i < cursorPoints.size(); i++) {
double originalDistanceOfCursorPoint = cursorPoints.get(i).getDistanceFrom(startingPoint);
double scaledDistanceOfCursorPoint = scaledCursorPoints.get(i).getDistanceFrom(startingPoint);
assertInRangeByAbsoluteValue(originalDistanceOfCursorPoint * factorToScaleBy, scaledDistanceOfCursorPoint, 3);
}
}
void initializeCursorPath() {
CursorDataFileParser cursorDataFileParser = new CursorDataFileParser("/home/dpapp/eclipse-workspace/RunescapeAI/testfiles/cursorPathTest.txt");
this.cursorPaths = cursorDataFileParser.getArrayListOfCursorPathsFromFile();
}
void assertInRangeByRatio(double valueToTest, double expectation, double toleranceRatio) {
assertTrue((valueToTest <= (expectation * (1 + toleranceRatio))) && (valueToTest >= (expectation * (1 - toleranceRatio))));
}
void assertInRangeByAbsoluteValue(double valueToTest, double expectation, double toleranceAbsolute) {
assertTrue((valueToTest <= (expectation + toleranceAbsolute)) && (valueToTest >= (expectation * (1 - toleranceAbsolute))));
}
}

View File

@ -1,20 +1,44 @@
import java.awt.Point;
public class CursorPoint {
public int x;
public int y;
public int postMillisecondDelay; // how much to sleep for after moving here
public int delay;
public CursorPoint(int x, int y, int postMillisecondDelay) {
public CursorPoint(int x, int y, int delay) {
this.x = x;
this.y = y;
this.postMillisecondDelay = postMillisecondDelay;
this.delay = delay;
}
public void setPostMillisecondDelay(int postMillisecondDelay) {
this.postMillisecondDelay = postMillisecondDelay;
public double getDistanceFrom(CursorPoint b) {
return Math.hypot(this.x - b.x, this.y - b.y);
}
public double getThetaFrom(CursorPoint b) {
return Math.atan2(1.0 * b.y, 1.0 * b.x);
}
public CursorPoint getCursorPointTranslatedBy(CursorPoint startingCursorPoint) {
return new CursorPoint(this.x - startingCursorPoint.x, this.y - startingCursorPoint.y, this.delay - startingCursorPoint.delay);
}
public CursorPoint getCursorPointScaledBy(double scaleFactor) {
return (new CursorPoint((int) (this.x * scaleFactor), (int) (this.y * scaleFactor), (int) (this.delay * scaleFactor)));
}
public CursorPoint getCursorPointRotatedBy(double angleOfRotation) {
int rotatedX = (int) (this.x + Math.cos(angleOfRotation) * this.x - Math.sin(angleOfRotation) * this.y);
int rotatedY = (int) (this.y + Math.sin(angleOfRotation) * this.x + Math.cos(angleOfRotation) * this.y);
return (new CursorPoint(rotatedX, rotatedY, this.delay));
}
public CursorPoint getCursorPointWithNewDelay(int delay) {
return (new CursorPoint(this.x, this.y, delay));
}
public void display() {
System.out.println("Point: (" + x + "," + y + "), delay: " + postMillisecondDelay);
System.out.println("Point: (" + x + "," + y + "), delay: " + delay);
}
}

43
src/CursorPointTest.java Normal file
View File

@ -0,0 +1,43 @@
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class CursorPointTest {
@Test
void testDistanceFrom() {
CursorPoint a = new CursorPoint(0, 0, 0);
CursorPoint b = new CursorPoint(3, 4, 0);
CursorPoint c = new CursorPoint(500, 750, 0);
CursorPoint d = new CursorPoint(284, 848, 0);
assertTrue(withinRangeByRatio(a.getDistanceFrom(b), 5, 0.0001));
assertTrue(withinRangeByRatio(a.getDistanceFrom(c), 901.387818866, 0.0001));
assertTrue(withinRangeByRatio(a.getDistanceFrom(d), 894.293016857, 0.0001));
assertTrue(withinRangeByRatio(b.getDistanceFrom(c), 896.395560007, 0.0001));
assertTrue(withinRangeByRatio(c.getDistanceFrom(d), 237.191905427, 0.0001));
}
@Test
void testCursorPointRotation() {
CursorPoint a = new CursorPoint(0, 0, 0);
CursorPoint b = new CursorPoint(10, 0, 0);
//CursorPoint c = new CursorPoint(10, 20, 0);
CursorPoint d = a.getCursorPointRotatedBy(Math.PI / 4);
CursorPoint e = b.getCursorPointRotatedBy(Math.PI / 2);
//CursorPoint f = c.getCursorPointRotatedBy(90);
//System.out.println(a.getThetaFrom(a));
//System.out.println(b.getThetaFrom(a));
assertTrue(d.x == 0 && d.y == 0);
System.out.println(e.x + ", " + e.y);
//assertTrue(e.x == 5);
//assertTrue(withinRangeByRatio())
}
boolean withinRangeByRatio(double actual, double expectation, double toleranceRatio) {
return ((actual <= (expectation * (1 + toleranceRatio))) && (actual >= (expectation * (1 - toleranceRatio))));
}
}

View File

@ -1,30 +0,0 @@
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
class MousePathTest {
@Test
void mousePathLengthTest() {
}
@Test
void mousePathTimespanTest() {
}
@Test
void mousePathStartingPointTest() {
}
@Test
void mousePathEndingPointTest() {
}
}

View File

@ -1,21 +0,0 @@
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
/*
* Things to test:
* - Invalid regex expression
* - Empty paths (mouse jumps)
* - Short paths
* - No input file
* - Long paths
*/
class MouseTest {
@Test
void testMouseLengths() {
}
}

View File

@ -10,18 +10,20 @@ public class Randomizer {
random = new Random();
}
public int nextGaussianWithinThreeSTDs(int mean, int STD) {
int result = (int) (random.nextGaussian() * STD + mean);
while (result > (mean + 3 * STD) || result < (mean - 3 * STD)) {
result = (int) random.nextGaussian() * STD + mean;
public int nextGaussianWithinRange(double rangeBegin, double rangeEnd) {
double rangeMean = (rangeEnd - rangeBegin) / 2.0;
double rangeSTD = (rangeEnd - rangeMean) / 3.0;
double result = random.nextGaussian() * rangeSTD + rangeMean;
while (result > rangeEnd || result < rangeBegin) {
result = random.nextGaussian() * rangeSTD + rangeMean;
}
return result;
return (int) result;
}
public Point generatePeakForTransformationParabola(int pathDistance) {
double maxTransformationScale = 0.2;
int peakX = nextGaussianWithinThreeSTDs(pathDistance / 2, pathDistance / 6);
int peakY = (int) (random.nextGaussian() * maxTransformationScale);
int peakX = nextGaussianWithinRange(0, pathDistance);
int peakY = nextGaussianWithinRange(0, pathDistance * maxTransformationScale);
return new Point(peakX, peakY);
}

View File

@ -0,0 +1,553 @@
1527,568,2
1527,568,10
1527,568,15
1527,568,21
1527,568,28
1527,568,34
1527,568,41
1527,568,47
1527,568,54
1527,568,60
1527,568,67
1527,568,74
1527,568,81
1527,568,89
1527,568,95
1527,568,101
1527,568,106
1527,568,112
1527,568,118
1527,568,125
1527,568,131
1527,568,138
1527,568,145
1527,568,152
1527,568,158
1527,568,164
1527,568,170
1527,568,177
1527,568,184
1527,568,189
1527,568,194
1527,568,200
1527,568,206
1527,568,213
1527,568,220
1527,568,227
1527,568,232
1527,568,238
1527,568,245
1527,568,251
1527,568,257
1527,568,264
1527,568,271
1527,568,278
1527,568,285
1527,568,292
1527,568,299
1527,568,304
1527,568,310
1527,568,316
1527,568,321
1527,568,328
1527,568,334
1527,568,342
1527,568,350
1527,568,357
1527,568,364
1527,568,371
1527,568,376
1527,568,381
1527,568,387
1527,568,393
1527,568,398
1527,568,404
1527,568,409
1527,568,416
1527,568,422
1527,568,429
1527,568,435
1527,568,441
1527,568,447
1527,568,452
1527,568,458
1527,568,463
1527,568,469
1527,568,476
1527,568,483
1527,568,490
1527,568,497
1527,568,504
1527,568,512
1527,568,518
1527,568,524
1527,568,531
1527,568,538
1527,568,545
1527,568,552
1527,568,558
1527,568,565
1527,568,572
1527,568,579
1527,568,585
1527,568,592
1527,568,600
1527,568,606
1527,568,614
1527,568,621
1527,568,627
1527,568,633
1527,568,639
1527,568,646
1527,568,651
1527,568,656
1527,568,662
1527,568,669
1527,568,676
1527,568,683
1527,568,689
1527,568,695
1527,568,701
1527,568,707
1527,568,713
1527,568,719
1527,568,724
1527,568,730
1527,568,736
1527,568,742
1527,568,748
1527,568,754
1527,568,760
1527,568,768
1527,568,774
1527,568,781
1527,568,788
1527,568,794
1527,568,800
1527,568,806
1527,568,813
1527,568,820
1527,568,828
1527,568,834
1527,568,840
1527,568,848
1527,568,855
1527,568,861
1527,568,868
1527,568,874
1527,568,882
1527,568,888
1527,568,894
1527,568,899
1527,568,906
1527,568,912
1527,568,920
1527,568,927
1527,568,933
1527,568,939
1527,568,945
1527,568,952
1527,568,960
1527,568,966
1527,568,973
1527,568,980
1527,568,986
1527,568,994
1527,568,1002
1527,568,1008
1527,568,1014
1527,568,1020
1527,568,1026
1527,568,1032
1527,568,1039
1527,568,1045
1527,568,1052
1527,568,1059
1527,568,1066
1527,568,1073
1527,568,1080
1527,568,1086
1527,568,1095
1527,568,1102
1527,568,1108
1527,568,1114
1527,568,1121
1527,568,1128
1353,641,1135
1193,719,1157
1193,719,1165
1051,799,1173
1051,799,1180
931,872,1187
830,928,1193
830,928,1199
735,976,1206
735,976,1213
650,1021,1218
650,1021,1225
590,1056,1231
590,1056,1238
566,1079,1244
566,1079,1248
559,1079,1253
559,1079,1259
558,1079,1264
558,1079,1270
558,1079,1277
558,1079,1283
559,1079,1289
559,1079,1296
605,1076,1302
605,1076,1308
706,1022,1315
706,1022,1322
848,934,1329
848,934,1335
982,850,1343
1079,774,1349
1079,774,1357
1141,707,1364
1172,653,1371
1172,653,1379
1187,613,1385
1187,613,1392
1190,577,1398
1190,577,1404
1190,563,1411
1190,563,1417
1190,563,1422
1190,563,1428
1190,563,1434
1190,563,1440
1190,563,1446
1190,563,1451
1190,563,1457
1190,563,1462
1190,563,1468
1190,563,1474
1190,563,1481
1190,563,1488
1190,563,1495
1190,563,1502
1190,563,1509
1190,563,1516
1190,563,1524
1190,563,1530
1190,563,1536
1190,563,1543
1190,563,1548
1190,563,1554
1190,563,1561
1190,563,1568
1190,563,1575
1190,541,1581
1190,526,1589
1190,526,1595
1186,497,1601
1186,497,1607
1140,449,1614
1140,449,1622
1083,407,1629
1083,407,1635
1005,386,1641
936,377,1648
936,377,1656
866,377,1662
866,377,1668
805,377,1674
805,377,1681
746,383,1688
746,383,1695
701,397,1702
701,397,1708
681,406,1713
681,406,1719
672,415,1726
671,421,1733
671,421,1740
671,428,1745
671,428,1751
671,437,1757
671,437,1764
676,445,1770
676,445,1776
689,455,1782
689,455,1789
713,473,1795
713,473,1802
743,495,1808
743,495,1814
772,514,1820
772,514,1826
808,523,1832
808,523,1838
844,528,1843
844,528,1849
886,528,1856
886,528,1862
953,512,1870
1053,455,1877
1053,455,1884
1149,386,1891
1149,386,1897
1210,321,1903
1210,321,1910
1224,278,1916
1224,278,1922
1224,235,1928
1224,235,1934
1224,191,1941
1224,148,1948
1224,148,1955
1188,104,1961
1188,104,1966
1188,104,1972
1133,80,1978
1133,80,1984
1051,73,1990
953,73,1997
953,73,2005
850,99,2010
745,160,2021
745,160,2028
669,229,2032
669,229,2040
640,291,2046
640,291,2052
640,333,2059
640,333,2065
640,374,2071
640,374,2078
648,411,2085
669,447,2093
669,447,2098
697,494,2104
697,494,2114
731,531,2119
731,531,2125
790,559,2131
790,559,2138
855,571,2146
855,571,2152
943,571,2158
943,571,2162
1049,555,2170
1162,513,2178
1162,513,2183
1162,513,2191
1248,472,2197
1281,445,2202
1281,445,2208
1286,432,2214
1286,432,2223
1297,415,2229
1305,401,2235
1305,401,2244
1317,382,2250
1317,382,2259
1328,364,2265
1332,355,2271
1332,355,2277
1332,352,2283
1332,352,2293
1332,350,2297
1332,350,2303
1332,350,2309
1332,350,2315
1332,348,2321
1332,348,2327
1324,348,2333
1324,348,2339
1308,348,2345
1308,348,2350
1287,348,2357
1287,348,2364
1261,354,2372
1261,354,2379
1233,387,2386
1204,430,2394
1204,430,2399
1185,481,2404
1185,481,2411
1179,536,2417
1179,536,2423
1179,579,2429
1179,579,2435
1179,621,2441
1179,621,2448
1179,659,2454
1179,659,2461
1199,682,2466
1199,682,2472
1233,696,2480
1233,696,2486
1266,705,2492
1266,705,2498
1300,709,2505
1345,709,2511
1345,709,2518
1384,709,2525
1384,709,2532
1384,709,2538
1384,709,2543
1384,709,2550
1384,709,2556
1384,709,2563
1384,709,2569
1384,709,2576
1384,709,2583
1384,709,2589
1384,709,2595
1384,709,2600
1384,709,2605
1384,709,2611
1384,709,2617
1384,709,2624
1384,709,2632
1384,709,2640
1384,709,2646
1384,709,2651
1384,709,2657
1384,709,2662
1384,709,2669
1384,709,2675
1384,709,2682
1384,709,2689
1384,709,2696
1384,709,2702
1384,709,2709
1384,709,2717
1384,709,2723
1384,709,2729
1384,709,2736
1384,709,2744
1384,709,2750
1384,709,2757
1384,709,2764
1384,709,2772
1380,709,2780
1365,710,2787
1365,710,2793
1336,715,2801
1336,715,2808
1290,720,2814
1290,720,2820
1228,722,2827
1228,722,2833
1140,727,2838
1140,727,2844
1030,729,2850
1030,729,2857
904,729,2864
804,729,2871
804,729,2877
742,729,2883
742,729,2890
693,723,2896
693,723,2903
629,707,2910
629,707,2917
561,694,2925
499,678,2931
499,678,2937
499,678,2944
458,661,2950
428,644,2957
428,644,2964
400,621,2969
400,621,2974
378,597,2980
378,597,2987
365,578,2994
365,578,3002
358,564,3008
358,564,3013
356,559,3020
356,556,3026
356,556,3033
356,556,3039
356,556,3046
356,556,3053
356,556,3058
356,556,3065
356,556,3071
356,556,3078
356,556,3084
356,556,3091
356,556,3096
356,556,3102
356,556,3109
356,556,3116
356,556,3123
356,556,3130
356,556,3136
356,556,3143
356,556,3150
356,556,3157
356,556,3164
356,556,3170
356,556,3177
356,556,3184
356,556,3191
356,547,3197
356,547,3205
353,536,3212
353,536,3218
344,500,3224
344,500,3228
320,439,3235
320,439,3240
299,376,3246
299,376,3253
274,324,3259
274,324,3264
251,284,3269
251,284,3274
231,254,3279
231,254,3286
214,234,3292
214,234,3298
204,226,3305
204,226,3313
200,223,3319
200,223,3326
199,223,3333
199,223,3340
199,223,3346
199,223,3352
199,223,3358
199,223,3364
199,223,3369
199,223,3374
199,223,3380
199,223,3385
202,224,3391
202,224,3398
212,230,3405
235,242,3412
235,242,3419
268,256,3425
268,256,3431
331,271,3438
331,271,3444
439,293,3450
439,293,3456
590,326,3463
750,359,3470
750,359,3478
905,391,3485
905,391,3491
1051,425,3499
1051,425,3505
1178,460,3510
1178,460,3515
1282,492,3522
1282,492,3527
1363,517,3533
1363,517,3540
1406,526,3546
1406,526,3553
1417,528,3560
1420,528,3566
1420,528,3572
1420,528,3578