MovementPredictor { MaxEscapeTarget smoothingPreciseEscapeAngle(int predictDirection, double absBearing, Point2D.Double sourceLocation, long fireTime, double bulletSpeed, RobotState startState, double attackAngle, double wallStick, int iterations) { MaxEscapeTarget bestSmoothingTarget = null; boolean wavePassed = false; bestSmoothingTarget = new MaxEscapeTarget(0, startState.location, startState.time, false); double goAngle = absBearing + (predictDirection * (HALF_PI + attackAngle)); goAngle = _battleField.wallSmoothing( startState.location, goAngle, predictDirection, wallStick); for (int x = 0; x < iterations; x++) { wavePassed = false; RobotState predictedState = startState; do { predictedState = nextLocation(predictedState, FULL_SPEED, goAngle, true); if (wavePassed( sourceLocation, fireTime, bulletSpeed, predictedState)) { wavePassed = true; } else { goAngle = _battleField.wallSmoothing( predictedState.location, goAngle, predictDirection, wallStick); } } while (!wavePassed); Point2D.Double predictedLocation = _battleField.translateToField(predictedState.location); double thisSmoothingEscapeAngle = predictDirection * Utils.normalRelativeAngle( absoluteBearing(sourceLocation, predictedLocation) - absBearing); if (thisSmoothingEscapeAngle > bestSmoothingTarget.angle) { bestSmoothingTarget = new MaxEscapeTarget(thisSmoothingEscapeAngle, predictedLocation, predictedState.time, false); } if (x + 1 < iterations) { goAngle = absoluteBearing(startState.location, predictedLocation); } } return bestSmoothingTarget; } MovementPredictor(BattleField battleField); RobotState predict(RobotState startState, double distance, double turn,
      double maxVelocity, long ticks, boolean ignoreWalls); RobotState nextRobotState(Diamond robot); Point2D.Double nextLocation(Diamond robot); Point2D.Double nextLocation(RobotState robotState); Point2D.Double nextLocation(Point2D.Double botLocation,
      double heading, double velocity); RobotState nextPerpendicularLocation(RobotState robotState,
      double absBearing, int orientation, double attackAngle,
      boolean ignoreWallHits); RobotState nextPerpendicularWallSmoothedLocation(RobotState robotState,
      double absBearing, double maxVelocity, double attackAngle,
      int orientation, double wallStick, boolean ignoreWallHits); RobotState nextLocation(RobotState robotState, double maxVelocity,
      double goAngle, boolean ignoreWallHits); double escapeAngleRange(Point2D.Double sourceLocation,
      long fireTime, double bulletSpeed, RobotState startState,
      double wallStick); MaxEscapeTarget preciseEscapeAngle(int predictDirection,
      Point2D.Double sourceLocation, long fireTime, double bulletSpeed,
      RobotState startState, double wallStick); MaxEscapeTarget preciseEscapeAngle(int predictDirection,
      Point2D.Double sourceLocation, long fireTime, double bulletSpeed,
      RobotState startState, double attackAngle, double wallStick);  }
@Test public void testSmoothingEscapeAngleTheoreticalMax() { MovementPredictor predictor = newPredictor(); RobotState startState = RobotState.newBuilder() .setLocation(newLocation(500, 420)) .setHeading(Math.PI / 2) .setVelocity(8.0) .setTime(1L) .build(); MaxEscapeTarget meaTarget = predictor.smoothingPreciseEscapeAngle(1, 0, newLocation(500, 200), 1L, 11.0, startState, 0, 120, 1); assertTrue(meaTarget.time > 20 && meaTarget.time < 35); assertEquals((meaTarget.time - startState.time) * 8.0, meaTarget.location.distance(startState.location), 0.01); assertEquals(Math.asin(8.0 / 11.0), meaTarget.angle, 0.05); }