Search code examples
javarobocode

Can you explain what this code from robocode means?


I do not understand what this method exactly is doing. Could someone explain this trig to me?

public void onScannedRobot(ScannedRobotEvent e)
  {
    setTurnRadarLeftRadians(getRadarTurnRemaining());

    setTurnRight(e.getBearing() + 90.0D - dir / 33.0D);
    if (((Guppy.var = var - e.getEnergy()) <= 3) && (var > 0.0D))
    {
      setMaxVelocity(Math.random() * 9.0D + 3);
      setAhead(Guppy.dir = dir * type);
    }
    setTurnGunRightRadians(Math.sin((Guppy.var = e.getBearingRadians() + getHeadingRadians()) - getGunHeadingRadians() + Math.asin((vel * 0.4D + e.getVelocity() * 0.6D) / 14.0D * Math.sin(e.getHeadingRadians() - var))));
    if (getGunHeat() == 0.0D)
    {
      setFire(2.9D);
      vel = e.getVelocity();
    }
    var = e.getEnergy();
  }

Solution

  • Of note to those familiar with trigonometry but not Robocode: Robocode's angular values are nonnegative and increase clockwise, rather than zero-centered and increasing counterclockwise as in trigonometric convention.

    Holy cow, that's a hairball. Painful style, too, assigning a variable and then using it within the same expression and randomly reusing static variables for different purposes. (EDIT: I've been out of robocode too long; obfuscated-looking messes are likely the result of hand-optimizing for code size.) Let's try to unpick that one messy line, starting here:

    setTurnGunRightRadians(Math.sin((Guppy.var = e.getBearingRadians() + getHeadingRadians()) - getGunHeadingRadians() + Math.asin((vel * 0.4D + e.getVelocity() * 0.6D) / 14.0D * Math.sin(e.getHeadingRadians() - var))));
    

    Looks like var is being used to hold the result of the subexpression e.getBearingRadians() + getHeadingRadians(). Since e.getBearingRadians() returns the target's bearing relative to your heading, var is the absolute target's bearing. Refactored:

    double targetBearing = e.getBearingRadians() + getHeadingRadians();
    setTurnGunRightRadians(Math.sin(targetBearing - getGunHeadingRadians() + Math.asin((vel * 0.4D + e.getVelocity() * 0.6D) / 14.0D * Math.sin(e.getHeadingRadians() - targetBearing))));
    

    Ugh, still a mess. We also have the variable vel unaccounted for, but based on the rest of the code, it seems to be the target's velocity when last scanned (and under the rash assumption that only one-on-one matches will be played); it's combined into a weighted average with the current velocity to give a rough predicted velocity, so there's probably some target-leading logic going on here.

    double targetBearing = e.getBearingRadians() + getHeadingRadians();
    double predictedVelocity = vel * 0.4D + e.getVelocity() * 0.6D;
    setTurnGunRightRadians(Math.sin(targetBearing - getGunHeadingRadians() + Math.asin(predictedVelocity / 14.0D * Math.sin(e.getHeadingRadians() - targetBearing))));
    

    Since robocode appears to neglect the difference between course and heading, the innermost sine Math.sin(e.getHeadingRadians() - targetBearing) gives a signed coefficient indicating what component of the target's velocity is perpendicular to its bearing and therefore requires firing-angle adjustment.

    double targetBearing = e.getBearingRadians() + getHeadingRadians();
    double predictedVelocity = vel * 0.4D + e.getVelocity() * 0.6D;
    double perpendicularMotionCoefficient = Math.sin(e.getHeadingRadians() - targetBearing);
    setTurnGunRightRadians(Math.sin(targetBearing - getGunHeadingRadians() + Math.asin(predictedVelocity / 14.0D * perpendicularMotionCoefficient)));
    

    The arc-sine expression seems to be intended to turn the perpendicular component of the target's motion back into an angular adjustment. This bit of math is outright wrong because arc-sine doesn't work that way: it turns a coefficient in the interval [-1, 1] back into an angle in the interval [-pi/2, pi/2], and dividing predicted velocity by 14 is probably just an attempt to bound the argument to arc-sine to within its domain. Then again, neither distance to the target nor projectile speed enter the computation either, so the best I can say about this adjustment is that it's vaguely in the right direction.

    double targetBearing = e.getBearingRadians() + getHeadingRadians();
    double predictedVelocity = vel * 0.4D + e.getVelocity() * 0.6D;
    double perpendicularMotionCoefficient = Math.sin(e.getHeadingRadians() - targetBearing);
    double firingAngleAdjustment = Math.asin(predictedVelocity / 14.0D * perpendicularMotionCoefficient);
    setTurnGunRightRadians(Math.sin(targetBearing - getGunHeadingRadians() + firingAngleAdjustment));
    

    That last sine makes no sense to me at all. The argument is already the angle in radians the gun needs to be moved by: the target's absolute bearing minus the current gun heading (which is how far the gun would need to turn to point exactly at the target), plus the firing angle adjustment for the target's movement. I'll just remove the sine function there entirely.

    double targetBearing = e.getBearingRadians() + getHeadingRadians();
    double predictedVelocity = vel * 0.4D + e.getVelocity() * 0.6D;
    double perpendicularMotionCoefficient = Math.sin(e.getHeadingRadians() - targetBearing);
    double firingAngleAdjustment = Math.asin(predictedVelocity / 14.0D * perpendicularMotionCoefficient);
    setTurnGunRightRadians(targetBearing - getGunHeadingRadians() + firingAngleAdjustment);
    

    Of course, that also depends on whether setting the gun to turn more than a half-turn (pi radians) is automatically understood to turn it less than a half-turn the other direction instead... you may end up doing lots of pointless gun-spinning otherwise.