commit cae9af4df65c55c2b5861af001eba98ee255b84e Author: Paul Mathieu Date: Sun Jan 22 10:04:06 2023 -0800 Add polbot diff --git a/pol/PolBot.java b/pol/PolBot.java new file mode 100644 index 0000000..3ee8597 --- /dev/null +++ b/pol/PolBot.java @@ -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 seenBots = new HashMap<>(); + private PotentialField field; + private boolean forward = true; + private Map 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(); + } +} diff --git a/pol/PotentialField.java b/pol/PotentialField.java new file mode 100644 index 0000000..f135023 --- /dev/null +++ b/pol/PotentialField.java @@ -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); + } + } + } +} \ No newline at end of file diff --git a/pol/PotentialFieldTest.java b/pol/PotentialFieldTest.java new file mode 100644 index 0000000..62e9d6b --- /dev/null +++ b/pol/PotentialFieldTest.java @@ -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); + } +}