// Matchup by Stelokim // // History // version 1.3: forward/reverse pattern-matcher // version 1.4: statistical pattern-matcher // // Credit: ABC for the idea package stelo; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import robocode.*; import robocode.util.Utils; public class Matchup extends AdvancedRobot { private static final int NUM_LOGS = 2; // [0]: velocity, [1]: angleChange, [2]: distance, [3]: bearingChange, [4]: ticksSinceMyFire private static final int PATTERN_LENGTH = 9000; private static int searchLength = 20; // increasing this will slow down the game private static int pattern_size = 0; private static int bulletTravelTime = 0; private static double[][] theLog = new double[PATTERN_LENGTH][NUM_LOGS]; private static int cursor = 0; private double movementLateralAngle = 0.2; private Point2D enemyLocation; private Point2D robotLocation; private double enemyDistance; private double enemyAbsoluteBearing; // private static double maxVelocity = 8; private static final double WALL_MARGIN = 25; private static double lastEnemyHeading = 0; private static double lastEnemyVelocity = 0; private static double lastEnemyBearing = 0; private static double lastEnemyEnergy; // private static int ticksSinceMyFire = 0; // private static double smallestDiff; // private static double smallestDiffReverse; // private static double matchedSign = 1.0; private static double enemyBulletPower = 0.1; private static double energyDrop = 0; // private static int ticksSinceDirection = 0; private static int CLOSEST_SIZE = 50; private static double MAX_ESCAPE_ANGLE = 0.9; // private static int hitRobotCount = 0; public void run() { setAdjustGunForRobotTurn(true); setAdjustRadarForGunTurn(true); do { turnRadarRightRadians(Double.POSITIVE_INFINITY); } while (true); } public void onScannedRobot(ScannedRobotEvent e) { double absbearing = e.getBearingRadians() + getHeadingRadians(); setTurnRadarRightRadians(Utils.normalRelativeAngle(absbearing - getRadarHeadingRadians()) * 2); // 2 original robotLocation = new Point2D.Double(getX(), getY()); enemyAbsoluteBearing = absbearing; enemyDistance = e.getDistance(); enemyLocation = vectorToLocation(enemyAbsoluteBearing, enemyDistance, robotLocation); double bulletPower; energyDrop = lastEnemyEnergy - e.getEnergy(); if (energyDrop >= 0.1 && energyDrop <= 3) enemyBulletPower = energyDrop; bulletPower = enemyBulletPower; //bulletPower = limit(0.1, bulletPower, (enemyDistance < 100 || getOthers() > 5 || smallestDiff < 1) ? 3.0 : 2.0); bulletPower = Math.min(bulletPower, e.getEnergy() / 5.0 ); bulletPower = limit(0.1, bulletPower, 3.0); if (getEnergy() > bulletPower && getGunHeat() / getGunCoolingRate() < 3 && getGunTurnRemainingRadians() == 0) { // if (getEnergy() > bulletPower && getGunHeat() / getGunCoolingRate() < 3) { double eX = e.getDistance() * Math.sin(absbearing); double eY = e.getDistance() * Math.cos(absbearing); // if (e.getEnergy() == 0.0) { // maxVelocity = 8.0; // goTo(new Point2D.Double(getX() + eX, getY() + eY)); // return; // } // if (getGunHeat() == 0) { //out.println("matched index: " + index + "\tdiff: " + smallestDiff + "\tsearchLength: " + searchLength); // ticksSinceMyFire = 0; // } double bearingOffset = bestBearing(theLog, e, bulletPower, absbearing); if (Math.abs(bearingOffset) < MAX_ESCAPE_ANGLE) { setTurnGunRightRadians(Utils.normalRelativeAngle(absbearing + bearingOffset - getGunHeadingRadians())); setFire(bulletPower); } } // else { // setTurnGunRightRadians(Utils.normalRelativeAngle( absbearing - getGunHeadingRadians() ) ); // } // ticksSinceMyFire++; if (Math.abs(e.getVelocity()) > 0 && getOthers() <= 1 && enemyDistance >= 200 && Math.abs(getEnergy() - e.getEnergy()) < 20) { // setMaxVelocity(maxVelocity = 8); mirrorMove(); } else { randomMove(); } //out.println("cursor: " + cursor); { /* //velocityChange[cursor] = e.getVelocity() - lastEnemyVelocity; velocityChange[cursor] = e.getVelocity(); angleChange[cursor] = e.getHeadingRadians() - lastEnemyHeading; //angleChange[cursor] = (int) ((e.getHeadingRadians() - lastEnemyHeading) * 10000) / 10000.0; logDistance[cursor] = e.getDistance() / 1000; logEnergy[cursor] = e.getEnergy() / 1000; */ theLog[cursor][0] = e.getVelocity(); theLog[cursor][1] = e.getHeadingRadians() - lastEnemyHeading; //theLog[cursor][1] = e.getHeadingRadians(); //theLog[cursor][2] = e.getDistance() / 10000; //theLog[cursor][3] = (absbearing - lastEnemyBearing) / 100; //theLog[cursor][4] = ticksSinceMyFire / 100; cursor = (cursor + 1) % PATTERN_LENGTH; // out.println(cursor); if (pattern_size < PATTERN_LENGTH) pattern_size++; } lastEnemyHeading = e.getHeadingRadians(); lastEnemyVelocity = e.getVelocity(); lastEnemyBearing = absbearing; lastEnemyEnergy = e.getEnergy(); } // public void onHitRobot(HitRobotEvent e) { // hitRobotCount++; // } // public void onHitByBullet(HitByBulletEvent e) { // if (getOthers() <= 1) // turnRadarRightRadians(Utils.normalRelativeAngle(e.getBearingRadians() + getHeadingRadians() - getRadarHeadingRadians())); // } public void onSkippedTurn(SkippedTurnEvent e) { out.println("SkippedTurn at: " + e.getTime()); } // statistical pattern-matcher private double bestBearing(double[][] series, ScannedRobotEvent e, double bulletPower, double absbearing) { int[] closestIndexes = new int[CLOSEST_SIZE]; double[] closestDiffs = new double[CLOSEST_SIZE]; double[] angles = new double[CLOSEST_SIZE]; int count = 0, i, c; for (c = 0 ; c < CLOSEST_SIZE; c++) { closestDiffs[c] = Double.POSITIVE_INFINITY; } i = (PATTERN_LENGTH + cursor - 1) % PATTERN_LENGTH; while (count < PATTERN_LENGTH) { double diff = 0; for (int j = 0; j < searchLength; j++) { int k = (i + j) % PATTERN_LENGTH; int searchIndex = (PATTERN_LENGTH + cursor - searchLength + j) % PATTERN_LENGTH; if (k == searchIndex) { diff = Double.POSITIVE_INFINITY; break; } diff += Math.abs(series[searchIndex][0] - series[k][0]) / 8.0; // velocity diff += Math.abs(series[searchIndex][1] - series[k][1]) / 0.17453292519943295769236907684886; // maximum turning, angleChange } for (c = 0 ; c < CLOSEST_SIZE; c++) { if (diff < closestDiffs[c] && Math.abs((i + searchLength) % PATTERN_LENGTH - cursor) > bulletTravelTime) { closestDiffs[c] = diff; closestIndexes[c] = (i + searchLength) % PATTERN_LENGTH; break; } } count++; i = (PATTERN_LENGTH + i - 1) % PATTERN_LENGTH; } // reconstruct enemy movement for (c = 0 ; c < CLOSEST_SIZE; c++) { double eX = e.getDistance() * Math.sin(absbearing); double eY = e.getDistance() * Math.cos(absbearing); double ww = e.getHeadingRadians(); double v = e.getVelocity(); double db = 0; int index = closestIndexes[c]; bulletTravelTime = 0; do { db += (20.0 - 3.0 * bulletPower); eX += v * Math.sin(ww); eY += v * Math.cos(ww); ww += theLog[index][1]; v = theLog[index][0]; if (index + 1 != cursor) { index = (index + 1) % PATTERN_LENGTH; } bulletTravelTime++; } while (db < Point2D.distance(0, 0, eX, eY)); angles[c] = Utils.normalRelativeAngle(Math.atan2(eX, eY) - absbearing); } // find the most popular angle double angleThreshold = 36.0 / e.getDistance(); int binSize = (int) (MAX_ESCAPE_ANGLE * 2.0 / angleThreshold) + 1; int[] statBin = new int[binSize]; double[] metaAngle = new double[binSize]; for (c = 0 ; c < CLOSEST_SIZE; c++) { int binIndex = (int) ((angles[c] + MAX_ESCAPE_ANGLE) / angleThreshold); metaAngle[binIndex] = angles[c]; statBin[binIndex]++; } int maxIndex = 0; for (c = 0 ; c < binSize; c++) { if (statBin[c] > statBin[maxIndex]) { maxIndex = c; } // out.print(statBin[c] + " "); } // out.println(); return metaAngle[maxIndex]; } private void mirrorMove() { goTo(new Point2D.Double(getBattleFieldWidth() - enemyLocation.getX(), getBattleFieldHeight() - enemyLocation.getY())); } // Always try to move a bit further away from the enemy. // Only when the walls forces us we will close in on the enemy. We never // bounce of walls. private void randomMove() { // if (energyDrop >= 0.1 && energyDrop <= 3) { // maxVelocity = Math.min(6.0, Math.random() * 20.0) + 2.0; // } considerChangingDirection(); Point2D robotDestination; double tries = 0; double awayFactor = (getOthers() > 1) ? 2.0 : 1.0; // if (enemyDistance < 200) awayFactor = 5; do { // robotDestination = // vectorToLocation(absoluteBearing(enemyLocation, robotLocation) + // movementLateralAngle, // enemyDistance * (1.1 - tries / 100.0), enemyLocation); robotDestination = vectorToLocation(absoluteBearing(enemyLocation, robotLocation) + movementLateralAngle, enemyDistance * awayFactor * (1.1 - tries / 100.0), enemyLocation); tries++; } while (tries < 100 && !fieldRectangle(WALL_MARGIN).contains(robotDestination)); // } while (tries < 100 && // !fieldRectangle(WALL_MARGIN).contains(robotDestination)); goTo(robotDestination); } private void considerChangingDirection() { // Change lateral direction at random // Tweak this to go for flat movement //if (Math.random() < 0.05 || ticksSinceDirection > 35) { //if (Math.random() < 0.08 && ticksSinceDirection > (enemyDistance - 36) / (20 - 3 * enemyBulletPower) ) { if (Math.random() < 0.05) { movementLateralAngle = -movementLateralAngle; //maxVelocity = Math.min(6.0, Math.random() * 20.0) + 2.0; //maxVelocity = 8; //ticksSinceDirection = 0; } // ticksSinceDirection++; } /* * RoundRectangle2D fieldRectangle(double margin) { return new * RoundRectangle2D.Double(margin, margin, getBattleFieldWidth() - margin * * 2, getBattleFieldHeight() - margin * 2, 75, 75); // 75 } */ private Rectangle2D fieldRectangle(double margin) { return new Rectangle2D.Double(margin, margin, getBattleFieldWidth() - margin * 2, getBattleFieldHeight() - margin * 2); } private void goTo(Point2D destination) { destination.setLocation(limit(35, destination.getX(), getBattleFieldWidth() - 35), limit(35, destination.getY(), getBattleFieldHeight() - 35)); double angle = Utils.normalRelativeAngle(absoluteBearing(robotLocation, destination) - getHeadingRadians()); double turnAngle = Math.atan(Math.tan(angle)); setTurnRightRadians(turnAngle); setAhead(robotLocation.distance(destination) * (angle == turnAngle ? 1 : -1)); // Hit the brake pedal hard if we need to turn sharply setMaxVelocity(Math.abs(getTurnRemaining()) > 30 ? 0 : 8); // vibrate //if (Math.abs(getTurnRemaining()) < 1) { // setTurnRight(Math.random() < 0.5 ? 10 : -10); //} } private static Point2D vectorToLocation(double angle, double length, Point2D sourceLocation) { return vectorToLocation(angle, length, sourceLocation, new Point2D.Double()); } private static Point2D vectorToLocation(double angle, double length, Point2D sourceLocation, Point2D targetLocation) { targetLocation.setLocation(sourceLocation.getX() + Math.sin(angle) * length, sourceLocation.getY() + Math.cos(angle) * length); return targetLocation; } private static double absoluteBearing(Point2D source, Point2D target) { return Math.atan2(target.getX() - source.getX(), target.getY() - source.getY()); } public static double limit(double min, double value, double max) { return Math.max(min, Math.min(value, max)); } }
// Matchup by Stelokim // // History // version 1.3: forward/reverse pattern-matcher // version 1.4: statistical pattern-matcher // version 1.5: optimization // version 1.6: random movement only (better), targeting bounded in battle field // // Credit: ABC for the idea package stelo; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import robocode.*; import robocode.util.Utils; public class Matchup extends AdvancedRobot { private static final int NUM_LOGS = 2; // [0]: velocity, [1]: angleChange, [2]: distance, [3]: bearingChange, [4]: ticksSinceMyFire private static final int PATTERN_LENGTH = 9000; private static int searchLength = 20; // increasing this will slow down the game. 20 private static int pattern_size = 0; private static double[][] theLog = new double[PATTERN_LENGTH][NUM_LOGS]; private static int cursor = 0; private double movementLateralAngle = 0.4; private Point2D enemyLocation; private Point2D robotLocation; private double enemyDistance; private double enemyAbsoluteBearing; // private static double maxVelocity = 8; private static final double WALL_MARGIN = 25; private static double lastEnemyHeading = 0; private static double lastEnemyVelocity = 0; private static double lastEnemyBearing = 0; private static double lastEnemyEnergy; // private static int ticksSinceMyFire = 0; // private static double smallestDiff; // private static double smallestDiffReverse; // private static double matchedSign = 1.0; private static double enemyBulletPower = 0.1; private static double energyDrop = 0; // private static int ticksSinceDirection = 0; private static int CLOSEST_SIZE = 50; private static double MAX_ESCAPE_ANGLE = 0.9; private static double maxX, maxY; // private static int hitRobotCount = 0; public void run() { setAdjustGunForRobotTurn(true); setAdjustRadarForGunTurn(true); maxX = getBattleFieldWidth() - 18; maxY = getBattleFieldHeight() - 18; do { turnRadarRightRadians(Double.POSITIVE_INFINITY); } while (true); } public void onScannedRobot(ScannedRobotEvent e) { double absbearing = e.getBearingRadians() + getHeadingRadians(); setTurnRadarRightRadians(Utils.normalRelativeAngle(absbearing - getRadarHeadingRadians()) * 2); // 2 original robotLocation = new Point2D.Double(getX(), getY()); enemyAbsoluteBearing = absbearing; enemyDistance = e.getDistance(); enemyLocation = vectorToLocation(enemyAbsoluteBearing, enemyDistance, robotLocation); double bulletPower; energyDrop = lastEnemyEnergy - e.getEnergy(); if (energyDrop >= 0.1 && energyDrop <= 3) enemyBulletPower = energyDrop; bulletPower = enemyBulletPower; //bulletPower = limit(0.1, bulletPower, (enemyDistance < 100 || getOthers() > 5 || smallestDiff < 1) ? 3.0 : 2.0); bulletPower = Math.min(bulletPower, e.getEnergy() / 5.0 ); bulletPower = limit(0.1, bulletPower, 3.0); if (getEnergy() > bulletPower && getGunHeat() / getGunCoolingRate() < 3 && getGunTurnRemainingRadians() == 0) { // if (getEnergy() > bulletPower && getGunHeat() / getGunCoolingRate() < 3) { double eX = e.getDistance() * Math.sin(absbearing); double eY = e.getDistance() * Math.cos(absbearing); // if (e.getEnergy() == 0.0) { // maxVelocity = 8.0; // goTo(new Point2D.Double(getX() + eX, getY() + eY)); // return; // } // if (getGunHeat() == 0) { //out.println("matched index: " + index + "\tdiff: " + smallestDiff + "\tsearchLength: " + searchLength); // ticksSinceMyFire = 0; // } double bearingOffset = bestBearing(theLog, e, bulletPower, absbearing); // if (Math.abs(bearingOffset) < MAX_ESCAPE_ANGLE) { setTurnGunRightRadians(Utils.normalRelativeAngle(absbearing + bearingOffset - getGunHeadingRadians())); setFire(bulletPower); // } } // else { // setTurnGunRightRadians(Utils.normalRelativeAngle( absbearing - getGunHeadingRadians() ) ); // } // ticksSinceMyFire++; // if (Math.abs(e.getVelocity()) > 0 && getOthers() <= 1 && enemyDistance >= 200 && Math.abs(getEnergy() - e.getEnergy()) < 20) { // setMaxVelocity(maxVelocity = 8); // mirrorMove(); // } // else { randomMove(); // } // if (getTime() % 60 < 30) // mirrorMove(); // else // randomMove(); //out.println("cursor: " + cursor); { /* //velocityChange[cursor] = e.getVelocity() - lastEnemyVelocity; velocityChange[cursor] = e.getVelocity(); angleChange[cursor] = e.getHeadingRadians() - lastEnemyHeading; //angleChange[cursor] = (int) ((e.getHeadingRadians() - lastEnemyHeading) * 10000) / 10000.0; logDistance[cursor] = e.getDistance() / 1000; logEnergy[cursor] = e.getEnergy() / 1000; */ theLog[cursor][0] = e.getVelocity(); theLog[cursor][1] = e.getHeadingRadians() - lastEnemyHeading; // theLog[cursor][2] = e.getDistance() / 10000; //theLog[cursor][3] = (absbearing - lastEnemyBearing) / 100; //theLog[cursor][4] = ticksSinceMyFire / 100; cursor = (cursor + 1) % PATTERN_LENGTH; // out.println(cursor); if (pattern_size < PATTERN_LENGTH) pattern_size++; } lastEnemyHeading = e.getHeadingRadians(); lastEnemyVelocity = e.getVelocity(); lastEnemyBearing = absbearing; lastEnemyEnergy = e.getEnergy(); } // public void onHitRobot(HitRobotEvent e) { // hitRobotCount++; // } // public void onHitByBullet(HitByBulletEvent e) { // if (getOthers() <= 1) // turnRadarRightRadians(Utils.normalRelativeAngle(e.getBearingRadians() + getHeadingRadians() - getRadarHeadingRadians())); // } public void onSkippedTurn(SkippedTurnEvent e) { out.println("SkippedTurn at: " + e.getTime()); } // statistical pattern-matcher private double bestBearing(double[][] series, ScannedRobotEvent e, double bulletPower, double absbearing) { int[] closestIndexes = new int[CLOSEST_SIZE]; double[] closestDiffs = new double[CLOSEST_SIZE]; double[] angles = new double[CLOSEST_SIZE]; int count = 0, i, c; final double bulletSpeed = (20.0 - 3.0 * bulletPower); final double bulletTravelTime = enemyDistance / bulletSpeed; for (c = 0 ; c < CLOSEST_SIZE; c++) { closestDiffs[c] = Double.POSITIVE_INFINITY; } i = (PATTERN_LENGTH + cursor - 1) % PATTERN_LENGTH; while (count < PATTERN_LENGTH) { double diff = 0; for (int j = 0; j < searchLength; j++) { int k = (i + j) % PATTERN_LENGTH; int searchIndex = (PATTERN_LENGTH + cursor - searchLength + j) % PATTERN_LENGTH; if (k == searchIndex) { diff = Double.POSITIVE_INFINITY; break; } diff += Math.abs(series[searchIndex][0] - series[k][0]) / 8.0; // velocity diff += Math.abs(series[searchIndex][1] - series[k][1]) / 0.17453292519943295769236907684886; // maximum turning, angleChange //diff += Math.abs(series[searchIndex][2] - series[k][2]); // distance } // find maximum difference index int maxDiffIndex = 0; for (c = 0; c < CLOSEST_SIZE; c++) { if (closestDiffs[c] > closestDiffs[maxDiffIndex]) maxDiffIndex = c; } // insert into the maximum difference index if (diff < closestDiffs[maxDiffIndex] && Math.abs((i + searchLength) % PATTERN_LENGTH - cursor) > bulletTravelTime) { closestDiffs[maxDiffIndex] = diff; closestIndexes[maxDiffIndex] = (i + searchLength) % PATTERN_LENGTH; } count++; i = (PATTERN_LENGTH + i - 1) % PATTERN_LENGTH; } double initialEX = enemyDistance * Math.sin(absbearing); double initialEY = enemyDistance * Math.cos(absbearing); double eV = e.getVelocity(); // reconstruct enemy movement for (c = 0 ; c < CLOSEST_SIZE; c++) { double eX = initialEX; double eY = initialEY; double ww = e.getHeadingRadians(); double v = eV; double db = 0; int index = closestIndexes[c]; do { db += bulletSpeed; eX += v * Math.sin(ww); eY += v * Math.cos(ww); ww += theLog[index][1]; v = theLog[index][0]; // if (index + 1 != cursor) { index = (index + 1) % PATTERN_LENGTH; // } } while (db < Point2D.distance(0, 0, eX, eY)); double absoluteEX = limit(18, eX + robotLocation.getX(), maxX); double absoluteEY = limit(18, eY + robotLocation.getY(), maxY); //angles[c] = Utils.normalRelativeAngle(Math.atan2(eX, eY) - absbearing); angles[c] = Utils.normalRelativeAngle(Math.atan2(absoluteEX - robotLocation.getX(), absoluteEY - robotLocation.getY()) - absbearing); } // find the most popular angle double angleThreshold = 36.0 / e.getDistance(); int binSize = (int) (MAX_ESCAPE_ANGLE * 2.0 / angleThreshold) + 1; int[] statBin = new int[binSize]; //double[] statBin = new double[binSize]; double[] metaAngle = new double[binSize]; for (c = 0 ; c < CLOSEST_SIZE; c++) { int binIndex = (int) ((angles[c] + MAX_ESCAPE_ANGLE) / angleThreshold); metaAngle[binIndex] = angles[c]; statBin[binIndex]++; //statBin[binIndex] += 1.0 / closestDiffs[c]; // weighted } int maxIndex = 0; for (c = 0 ; c < binSize; c++) { if (statBin[c] > statBin[maxIndex]) { maxIndex = c; } // out.print(statBin[c] + " "); } // out.println(); return metaAngle[maxIndex]; } // private void mirrorMove() { // goTo(new Point2D.Double(getBattleFieldWidth() - enemyLocation.getX(), getBattleFieldHeight() - enemyLocation.getY())); // } private static final double REVERSE_TUNER = 0.421075; private static final double WALL_BOUNCE_TUNER = 0.699484; private void randomMove() { // if (energyDrop >= 0.1 && energyDrop <= 3) { // maxVelocity = Math.min(6.0, Math.random() * 20.0) + 2.0; // } Point2D robotDestination; double tries = 0; double awayFactor = (getOthers() > 1) ? 2.0 : 1.0; // if (enemyDistance < 200) awayFactor = 4.0; do { // robotDestination = // vectorToLocation(absoluteBearing(enemyLocation, robotLocation) + // movementLateralAngle, // enemyDistance * (1.1 - tries / 100.0), enemyLocation); robotDestination = vectorToLocation(absoluteBearing(enemyLocation, robotLocation) + movementLateralAngle, enemyDistance * awayFactor * (1.2 - tries / 100.0), enemyLocation); // 1.1 tries++; } while (tries < 125 && !fieldRectangle(WALL_MARGIN).contains(robotDestination)); double bv = 20.0 - 3.0 * enemyBulletPower; if ((Math.random() < (bv / REVERSE_TUNER) / enemyDistance || tries > (enemyDistance / bv / WALL_BOUNCE_TUNER))) { movementLateralAngle = -movementLateralAngle; } // } while (tries < 100 && // !fieldRectangle(WALL_MARGIN).contains(robotDestination)); goTo(robotDestination); } /* * RoundRectangle2D fieldRectangle(double margin) { return new * RoundRectangle2D.Double(margin, margin, getBattleFieldWidth() - margin * * 2, getBattleFieldHeight() - margin * 2, 75, 75); // 75 } */ private Rectangle2D fieldRectangle(double margin) { return new Rectangle2D.Double(margin, margin, getBattleFieldWidth() - margin * 2, getBattleFieldHeight() - margin * 2); } private void goTo(Point2D destination) { // destination.setLocation(limit(35, destination.getX(), // getBattleFieldWidth() - 35), limit(35, destination.getY(), // getBattleFieldHeight() - 35)); double angle = Utils.normalRelativeAngle(absoluteBearing(robotLocation, destination) - getHeadingRadians()); double turnAngle = Math.atan(Math.tan(angle)); setTurnRightRadians(turnAngle); setAhead(robotLocation.distance(destination) * (angle == turnAngle ? 1 : -1)); // Hit the brake pedal hard if we need to turn sharply setMaxVelocity(Math.abs(getTurnRemaining()) > 30 ? 0 : 8); // vibrate //if (Math.abs(getTurnRemaining()) < 1) { // setTurnRight(Math.random() < 0.5 ? 10 : -10); //} } private static Point2D vectorToLocation(double angle, double length, Point2D sourceLocation) { return vectorToLocation(angle, length, sourceLocation, new Point2D.Double()); } private static Point2D vectorToLocation(double angle, double length, Point2D sourceLocation, Point2D targetLocation) { targetLocation.setLocation(sourceLocation.getX() + Math.sin(angle) * length, sourceLocation.getY() + Math.cos(angle) * length); return targetLocation; } private static double absoluteBearing(Point2D source, Point2D target) { return Math.atan2(target.getX() - source.getX(), target.getY() - source.getY()); } public static double limit(double min, double value, double max) { return Math.max(min, Math.min(value, max)); } }