Add polbot

This commit is contained in:
Paul Mathieu 2023-01-22 10:04:06 -08:00
commit cae9af4df6
3 changed files with 409 additions and 0 deletions

239
pol/PolBot.java Normal file
View File

@ -0,0 +1,239 @@
package pol;
import robocode.*;
import robocode.util.Utils;
import java.awt.Color;
import java.util.Map;
import java.util.HashMap;
// API help : https://robocode.sourceforge.io/docs/robocode/robocode/Robot.html
/**
* PolBot - a robot by (your name here)
*/
public class PolBot extends AdvancedRobot
{
private Map<String, ScannedRobotEvent> seenBots = new HashMap<>();
private PotentialField field;
private boolean forward = true;
private Map<String, Double> hits = new HashMap<>();
/**
* run: PolBot's default behavior
*/
@Override
public void run() {
// Initialization of the robot should be put here
// After trying out your robot, try uncommenting the import at the top,
// and the next line:
setColors(Color.red,Color.blue,Color.green); // body,gun,radar
// Robot main loop
// keep the radar still while we turn
setAdjustRadarForRobotTurn(true);
setBodyColor(new Color(128, 128, 50));
setGunColor(new Color(50, 50, 20));
setRadarColor(new Color(200, 200, 70));
setScanColor(Color.white);
setBulletColor(Color.white);
setAhead(1e100);
setTurnLeft(1e40);
// keep the gun still while we turn
setAdjustGunForRobotTurn(true);
// keep spinning that radar
setTurnRadarRightRadians(Double.POSITIVE_INFINITY);
final int width = (int)getBattleFieldWidth();
final int height = (int)getBattleFieldHeight();
field = new PotentialField(width, height);
field.addWalls(1000, 200);
field.addBlob(0, 0, 1000, 200);
field.addBlob(width, 0, 1000, 200);
field.addBlob(width, height, 1000, 200);
field.addBlob(0, height, 1000, 200);
field.addBlob(width / 2, height / 2, 500, 200);
System.out.println("Starting main loop");
while (true) {
tick();
execute();
// waitFor(new Condition() {
// public boolean test() { return true; }
// });
}
}
private String pickATarget() {
if (seenBots.size() < 1) {
return null;
}
double highest = 0;
String target = seenBots.keySet().iterator().next();
for (String bot : seenBots.keySet()) {
if (hits.get(bot) != null && hits.get(bot) > highest) {
highest = hits.get(bot);
target = bot;
}
}
return target;
}
public void tick() {
// main tick
// TO DO:
// [x] track enemy
// [x] shoot
// [x] move a bit
// [ ] do periodic full scans
// [ ] estimate likelihood of hitting
// [ ] estimate enemy trajectory
// [ ] follow enemy
// [x] avoid walls
// [ ] avoid other robots
// [ ] select enemy at not-random
aimAndShoot();
moveAround();
}
private double[] computeVel() {
final double vel = getVelocity();
final double heading = Math.PI / 2 - getHeadingRadians();
return new double[]{Math.cos(heading) * vel, Math.sin(heading) * vel};
}
private void moveAround() {
final double[] v = computeVel();
final double[] newV = field.dv((int)getX(), (int)getY(), v[0], v[1]);
final double newVx = newV[0];
final double newVy = newV[1];
final double vel = Math.sqrt(newVx * newVx + newVy * newVy);
final double heading = Math.atan2(newVy, newVx);
final double turnRight = (Math.PI / 2 - heading) - getHeadingRadians();
if (Math.abs(vel) > 1.0) {
setAhead(vel * 5);
}
setTurnRightRadians(turnRight * 10);
}
/**
*
* @param target
* @return the adjusted relative bearing for the given target (radians)
*/
private double aim(ScannedRobotEvent target, double power) {
final double heading = Math.PI / 2 - (target.getHeadingRadians() - getHeadingRadians());
final double bearing = Math.PI / 2 - target.getBearingRadians();
final double x = Math.cos(bearing) * target.getDistance();
final double y = Math.sin(bearing) * target.getDistance();
final double bulletSpeed = Rules.getBulletSpeed(power);
final double hitTime = target.getDistance() / bulletSpeed;
final double nextX = Math.cos(heading) * 1.0 * hitTime * target.getVelocity() + x;
final double nextY = Math.sin(heading) * 1.0 * hitTime * target.getVelocity() + y;
System.out.println("(" + x + ", " + y + ") -> (" + nextX + ", " + nextY + ")");
return Math.PI / 2 - Math.atan2(nextY, nextX);
}
private void aimAndShoot() {
final String target = pickATarget();
if (target == null) {
return;
}
final ScannedRobotEvent enemy = seenBots.get(target);
// do a full scan if the next target hasn't been seen for a while
if (enemy.getTime() < getTime() - 10) {
setTurnRadarRight(1e10);
return;
}
final double distance = enemy.getDistance();
final double power = 1 + 9 / (distance / 10);
if (power < 1.1) {
return;
}
if (getEnergy() < 15 && distance > 200) {
return;
}
final double seenBearing = enemy.getBearing();
final double aimBearing = 180 / Math.PI * aim(enemy, power);
final double radarBearing = Utils.normalRelativeAngleDegrees(getRadarHeading() - getHeading());
final double gunBearing = Utils.normalRelativeAngleDegrees(getGunHeading() - getHeading());
final double dgh = Utils.normalRelativeAngleDegrees(aimBearing - gunBearing);
final double drh = Utils.normalRelativeAngleDegrees(seenBearing - radarBearing);
setTurnGunRight(dgh);
setTurnRadarRight(drh);
if (Math.abs(dgh) < 5) {
setFire(power);
}
}
@Override
public void onRobotDeath(RobotDeathEvent event) {
seenBots.remove(event.getName());
}
/**
* onScannedRobot: What to do when you see another robot
*/
@Override
public void onScannedRobot(ScannedRobotEvent e) {
// Replace the next line with any behavior you would like
seenBots.put(e.getName(), e);
System.out.println("seen " + e.getName());
}
private void reverse() {
if (forward) {
forward = false;
setAhead(-1e100);
} else {
forward = true;
setAhead(1e100);
}
}
/**
* onHitByBullet: What to do when you're hit by a bullet
*/
@Override
public void onHitByBullet(HitByBulletEvent e) {
// Replace the next line with any behavior you would like
reverse();
// keep a grudge
if (hits.get(e.getName()) == null) {
hits.put(e.getName(), 0.0);
}
hits.put(e.getName(), hits.get(e.getName()) + e.getPower());
}
/**
* onHitWall: What to do when you hit a wall
*/
@Override
public void onHitWall(HitWallEvent e) {
// Replace the next line with any behavior you would like
reverse();
}
}

106
pol/PotentialField.java Normal file
View File

@ -0,0 +1,106 @@
package pol;
public class PotentialField {
private final double[] potential;
private final int width;
private final int height;
public PotentialField(final int width, final int height) {
this.width = width;
this.height = height;
potential = new double[width * height];
}
public double value(final int x, final int y) {
return potential[x * height + y];
}
public double[] gradient(final int x, final int y) {
double gx;
double gy;
if (x < 1) {
gx = value(x, y);
} else {
gx = value(x, y) - value(x - 1, y);
}
if (y < 1) {
gy = value(x, y);
} else {
gy = value(x, y) - value(x, y - 1);
}
return new double[]{gx, gy};
}
/**
* Compute the force from the potential field
* @param x
* @param y
* @param vx
* @param vy
* @return the new speed, assuming unit mass, time, and everything.
*/
public double[] dv(final int x, final int y, final double vx, final double vy) {
final double[] grad = gradient(x, y);
return new double[]{vx - grad[0], vy - grad[1]};
}
/**
* Add a bell-shaped blob somewhere
* @param x
* @param y
* @param value
* @param size the standard deviation of the blob
*/
public void addBlob(final int x, final int y, final double value, final double size) {
for (int i = 0; i < width; i++) {
final int mx = i - x;
for (int j = 0; j < height; j++) {
final int my = j - y;
final double v = value * Math.exp(-0.5 * (mx*mx + my*my) / (size*size));
potential[i * height + j] += v;
}
}
}
public void clear() {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
potential[i * height + j] = 0.0;
}
}
}
private double wallPotential(double x, double value, double size) {
return value / ((x / size + 1) * (x / size + 1));
}
private double wallValue(int x, int y, double value, double size) {
// 4 regions: top, right, bottom, left; defined by two intersecting diagonals
final double a = height * x - width * y;
final double b = height * x + width * y - width * height;
if (a > 0 && b > 0) { // right
return wallPotential(width - x, value, size);
} else if (a > 0 && b <= 0) { // bottom
return wallPotential(y, value, size);
} else if (a <= 0 && b <= 0) { // left
return wallPotential(x, value, size);
} else {
return wallPotential(height - y, value, size);
}
}
/**
* Add half-bells ish lines along walls
*/
public void addWalls(final double value, final double size) {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
potential[i * height + j] += wallValue(i, j, value, size);
}
}
}
}

View File

@ -0,0 +1,64 @@
package pol;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class PotentialFieldTest {
@Test
public void testAddBlob() {
final PotentialField pf = new PotentialField(4, 5);
pf.addBlob(2, 3, 1, 1);
final double v = pf.value(2, 3);
assertEquals(1.0, v, 1e-10);
final double[] dv = pf.dv(3, 4, 0, 0);
assertTrue("dv[0] goes down gradient", dv[0] > 0);
assertTrue("dv[1] goes down gradient", dv[1] > 0);
}
@Test
public void testAddWalls() {
}
@Test
public void testClear() {
final PotentialField pf = new PotentialField(4, 5);
pf.addBlob(2, 3, 1, 1);
assertEquals(1.0, pf.value(2, 3), 1e-10);
pf.clear();
assertEquals(0, pf.value(2, 3), 1e-10);
}
@Test
public void testDv() {
final PotentialField pf = new PotentialField(4, 5);
final double[] dv = pf.dv(2, 3, 50, 40);
assertEquals(50.0, dv[0], 1e-10);
assertEquals(40.0, dv[1], 1e-10);
}
@Test
public void testGradient() {
final PotentialField pf = new PotentialField(4, 5);
final double[] g = pf.gradient(2, 3);
assertEquals(0.0, g[0], 1e-10);
assertEquals(0.0, g[1], 1e-10);
}
@Test
public void testValue() {
final PotentialField pf = new PotentialField(4, 5);
assertEquals(0, pf.value(0, 0), 1e-10);
}
}