CassiusClay/Butterfly

From Robowiki
Jump to navigation Jump to search

Butterfly by PEZ - The CassiusClay (pluggable) movement

Butterfly is all about WaveSurfing. It's the result of letting the Pugilist movement grow out of its MiniBot constraints. The design is pretty simple:

  1. Every scan create an EnemyWave
    1. If the enemy fired the tick before then mark the wave as "surfable"
    2. If a bullet hits my bot then
      1. Find the wave that is in the passing
      2. Find out what GuessFactor we were hit on
      3. Update the stat buffers with this knowledge
  2. Every scan:
    1. check where (what guess factor) we would impact with the closest surfable wave were we to:
      1. move fully forward
      2. move fully reverse
      3. hit the brakes
    2. For each wave in the air
      1. Examine how often we've been hit in each of these destination options compared to all other guess factors
      2. Weigh together the results of all surfable waves with the closest one being the most important
    3. Go for the option where we've been hit the least (or where it seems the least dangerous to move)

Simple enough, huh? One of those steps is a bit complicated though - step 2.1. It involves predicting our own movement some ticks in the future. This can be done in many ways. Pugilist does it in a pretty sloppy manner. CassiusClay is a bit more precise. In fact as of version 1.9.9.00g (g for gamma, my first test version) not only considers how fast CC can accelerate/deccelerate. It also tries to take into account the maximum turn angle it can do at a given velocity. Here's the scheme for the predictor:

Inputs

  • The closest "surfable" EnemyWave
  • The desired orbit direction
  • The desired target velocity (between 0 and 8 inclusive)

Action

  1. Grab the current heading
  2. Grab the current velocity (relative to the desired orbit direction)
  3. Do
    1. Calculate a WallSmoothed position in the desired orbit direction
    2. Calculate absolute bearing to this position
    3. Adjust the velocity according to Robocode physics (aiming for the target velocity)
    4. Calculate how much we would need to turn the bot to get the right heading
    5. Adjust our virtual heading while considering BackAsFront and the maximum turn rate at the current virtual velocity
    6. Calculate the position of the bot 1 tick into the future using the new virtual heading and velocity
    7. Advance the wave 1 tick into the future
  4. Until the wave impacts with the predicted bot position

Here's the implementing code (RWPCL):

    Move waveImpactLocation(MovementWave closest, double direction, double maxVelocity) {
	double currentDirection = robotOrbitDirection(closest.gunBearing(robotLocation));
	double v = Math.abs(robot.getVelocity()) * PUtils.sign(direction);
	double h = robot.getHeadingRadians();
	Point2D orbitCenter = orbitCenter(closest);
	Point2D impactLocation = new Point2D.Double(robot.getX(), robot.getY());
	Move smoothed;
	int time = 0;
	do {
	    smoothed = wallSmoothedDestination(impactLocation, orbitCenter, currentDirection * direction);
	    double wantedHeading = PUtils.absoluteBearing(impactLocation, smoothed.location);
	    h += PUtils.backAsFrontDirection(wantedHeading, h) < 0 ? Math.PI : 0.0;
	    if (v < maxVelocity) {
		v = Math.min(maxVelocity, v + (v < 0 ? 2 : 1));
	    }
	    else {
		v = Math.max(maxVelocity, v - 2);
	    }
	    double maxTurn = Math.toRadians(MAX_TURN_RATE - 0.75 * Math.abs(v));
	    h += PUtils.minMax(PUtils.backAsFrontTurn(wantedHeading, h), -maxTurn, maxTurn);
	    impactLocation = PUtils.project(impactLocation, h, v);
	} while (closest.distanceFromTarget(impactLocation, time++) > 18);
	return new Move(impactLocation, smoothed.smoothing, smoothed.wantedEvasion, smoothed.oldDistance, impactLocation.distance(enemyLocation));
    }

The BackAsFront functions there looks like so:

    public static double backAsFrontTurn(double newHeading, double oldHeading) {
	return Math.tan(newHeading - oldHeading);
    }

    public static double backAsFrontDirection(double newHeading, double oldHeading) {
	return sign(Math.cos(newHeading - oldHeading));
    }

It's the same functions I use to move my bot around Jamougha style:

	double newHeading = PUtils.absoluteBearing(robotLocation, destination);
	double oldHeading = robot.getHeadingRadians();
	robot.setAhead(PUtils.backAsFrontDirection(newHeading, oldHeading) * 50);
	robot.setTurnRightRadians(PUtils.backAsFrontTurn(newHeading, oldHeading));