package tango; import robocode.*; import java.awt.Color; import java.awt.geom.*; import java.util.*; /** * Recrimpo 2.0 - a robot by Tango - my first attempt at Waves */ public class Recrimpo extends AdvancedRobot { Vector Waves=new Vector(0); Point2D EnemyCurrent, PostdictedEnemyFuture, CurrentLocation; Wave CurrentWave; int i,j, GuessFactor, dir=1; double PostdictedMoveDistance,a,b,c,AngleOffset, lastBearing, angleToTurn, velocity; static int[][] GuessFactors=new int[51][5]; //50 is angles, 5 is distances (0-100,101-300,300-500,500-700,700+ boolean clockwise; public void run() { setColors(Color.blue,Color.blue,Color.red); setAdjustGunForRobotTurn(true); setAdjustRadarForRobotTurn(true); setTurnRadarRight(Double.POSITIVE_INFINITY); if (getRoundNum()==0) { for (i=0; i<51; i++) { for (j=0; j<5; j++) { GuessFactors[i][j]=0; } } } while (true) { if (getDistanceRemaining()==0) { if (Math.random()>0.5) {dir*=-1;} setMaxVelocity(velocity=Math.random()*4+4); //setAhead(1000/velocity*dir); } execute(); } } public void onHitWall(HitWallEvent e) { dir*=-1; } public void onHitRobot(HitRobotEvent e) { dir*=-1; } public void onScannedRobot(ScannedRobotEvent e) { double power; int DistanceIndex=0; angleToTurn=90-e.getBearing(); if (e.getDistance()<150) {angleToTurn+=20;} if (e.getDistance()>500) {angleToTurn-=20;} angleToTurn=robocode.util.Utils.normalRelativeAngle(angleToTurn); //setTurnLeft(angleToTurn); if (robocode.util.Utils.normalAbsoluteAngle(Math.toRadians(e.getBearing()+getHeading()))-lastBearing>=0) {clockwise=true; out.println("CLOCKWISE");} else {clockwise=false; out.println("ANTICLOCKWISE");} EnemyCurrent=new Point2D.Double(getX() + Math.sin(Math.toRadians(e.getBearing() + getHeading())) * e.getDistance(),getY() + Math.cos(Math.toRadians(e.getBearing() + getHeading())) * e.getDistance()); //if (EnemyCurrent.getX()<50 || EnemyCurrent.getX()>getBattleFieldWidth()-50) {out.println("-----NEAR WALL-----");} else {out.println("---NOT NEAR WALL---");} CurrentLocation=new Point2D.Double(getX(),getY()); if (Waves.size()>0) { for (i=0;i<Waves.size();i++) { CurrentWave=(Wave) Waves.elementAt(i); if (CurrentWave.Hit(EnemyCurrent, i)) { //calulate guess factor double EnemyNewBearing = Math.toDegrees(robocode.util.Utils.normalAbsoluteAngle(Math.atan2(EnemyCurrent.getX()-CurrentWave.FiredPosition.getX(),EnemyCurrent.getY()-CurrentWave.FiredPosition.getY()))); //out.println("New="+EnemyNewBearing+" Old="+CurrentWave.EnemyOriginalBearing); //out.println("Raw offset="+(EnemyNewBearing-CurrentWave.EnemyOriginalBearing)); AngleOffset=Math.toDegrees(robocode.util.Utils.normalRelativeAngle(Math.toRadians(EnemyNewBearing-CurrentWave.EnemyOriginalBearing)))*(CurrentWave.clockwise?1:-1); //out.println("Offset="+AngleOffset); GuessFactor=(int) Math.round(AngleOffset/46.66*25)+25; if (GuessFactor<0) {GuessFactor=0;} if (GuessFactor>50) {GuessFactor=50;} if (CurrentWave.EnemyOriginalDistance<101) {DistanceIndex=0;} else { if (CurrentWave.EnemyOriginalDistance>100 && CurrentWave.EnemyOriginalDistance<301) {DistanceIndex=1;} else { if (CurrentWave.EnemyOriginalDistance>300 && CurrentWave.EnemyOriginalDistance<501) {DistanceIndex=2;} else { if (CurrentWave.EnemyOriginalDistance>500 && CurrentWave.EnemyOriginalDistance<701) {DistanceIndex=3;} else { if (CurrentWave.EnemyOriginalDistance>700) {DistanceIndex=4;}}}}} GuessFactors[GuessFactor][DistanceIndex]++; } } } if (getGunHeat()==0) { int BestGuess=25; if (e.getDistance()<101) {DistanceIndex=0;} else { if (e.getDistance()>100 && e.getDistance()<301) {DistanceIndex=1;} else { if (e.getDistance()>300 && e.getDistance()<501) {DistanceIndex=2;} else { if (e.getDistance()>500 && e.getDistance()<701) {DistanceIndex=3;} else { if (e.getDistance()>700) {DistanceIndex=4;}}}}} for (i=0;i<51;i++) {if (GuessFactors[i][DistanceIndex]>GuessFactors[BestGuess][DistanceIndex]) {BestGuess=i;}} //out.println("Best Guess is "+BestGuess); turnGunRight(Math.toDegrees(robocode.util.Utils.normalRelativeAngle(Math.toRadians(getHeading()-getGunHeading()+e.getBearing()+((BestGuess-25)/25*46.66*(clockwise?1:-1)))))); power=3; setFire(power); Waves.addElement(new Wave(getTime(), CurrentLocation, power, CurrentLocation.distance(EnemyCurrent), Math.toDegrees(robocode.util.Utils.normalAbsoluteAngle(Math.toRadians(e.getBearing()+getHeading()))), e.getVelocity(), EnemyCurrent, clockwise)); } lastBearing=robocode.util.Utils.normalAbsoluteAngle(Math.toRadians(e.getBearing()+getHeading())); } class Wave { long FiredTime; Point2D FiredPosition; Point2D EnemyOriginalPosition; double Power; double EnemyOriginalDistance; double EnemyOriginalBearing; double EnemyOriginalVelocity; boolean clockwise; Wave(long FiredTime, Point2D FiredPosition, double Power, double EnemyOriginalDistance, double EnemyOriginalBearing, double EnemyOriginalVelocity, Point2D EnemyOriginalPosition, boolean clockwise) { this.FiredTime=FiredTime; this.FiredPosition=FiredPosition; this.Power=Power; this.EnemyOriginalDistance=EnemyOriginalDistance; this.EnemyOriginalBearing=EnemyOriginalBearing; this.EnemyOriginalVelocity=EnemyOriginalVelocity; this.EnemyOriginalPosition=EnemyOriginalPosition; this.clockwise=clockwise; } public boolean Hit(Point2D EnemyCurrent, int Index) { if (FiredPosition.distance(EnemyCurrent)<=getBulletTime()*(20-3*Power)) { Waves.removeElementAt(Index); return true; } else { return false; } } public double getBulletTime() { return getTime()-FiredTime; } } public void onDeath(DeathEvent e) { for (i=0; i<51; i++) { //out.println("Final VC for GF "+i+" was "+GuessFactors[i][0]+","+GuessFactors[i][1]+","+GuessFactors[i][2]+","+GuessFactors[i][3]+","+GuessFactors[i][4]); } } public void onWin(WinEvent e) { for (i=0; i<51; i++) { //out.println("Final VC for GF "+i+" was "+GuessFactors[i][0]+","+GuessFactors[i][1]+","+GuessFactors[i][2]+","+GuessFactors[i][3]+","+GuessFactors[i][4]); } } }

Ok, first question what is this:

AngleOffset?=Math.toDegrees(Math.acos((Math.pow(b,2)+Math.pow(c,2)-Math.pow(a,2))/(2*b*c)));

It looks like you're trying to use the law of cosines to figure out the angle, which is intuitive and original, but may have a symantic problem - I think arc cosines only return positive numbers.

- You're right, it always returns postive numbers, if they are meant to be -ve I multiply them by -1 in the next bit of code you questioned.

You want it to be negative sometimes. The way I would do this calculation is:

AngleOffset? = Math.toDegrees(robocode.util.Utils.normalRelativeAngle(Math.atan2(EnemyCurrent?.getX()-CurrentWave?.FiredPosition?.getX(), EnemeyCurrent?.getY()-CurrentWave?.FiredPosition?.getY())-EnemyOriginalBearing?));

Basically, take the current bearing of the enemy from the original spot the wave was fired from and subtract their original bearing, then normalize it to range (to account for going over 0 degrees).

- I'm doing almost exactly that later on for a different purpose, and never thought of combining them. I'll certainly do that, it would be much simpler. (I wrote them at different times, and didn't notice they did the same thing).

if (EnemyNewBearing-CurrentWave.EnemyOriginalBearing>=0 && !CurrentWave.clockwise || EnemyNewBearing-CurrentWave.EnemyOriginalBearing<0 && CurrentWave.clockwise) { AngleOffset*=-1; out.println("It's turned around!"); }

I think you just always want to multiply by -1 if !CurentWave?.clockwise. The idea here is that EnemyNewBearing?-CurrentWave?.EnemeyOriginalBearing? will be positive if they were going clockwise and are still going clockwise, or negative if they were going clockwise and ended up counterclockwise, and that's right. However, if he was going counter-clockwise at fire time and he ended up counter-clockwise, AngleOffset? will be negative but should be positive.

- I don't think so. It should work. There are 2 parts to the if statement, on for clockwise to anticlockwise, and one for anticlockwise to clockwise. It should work. However, it's not needed if i use the code you suggested above.

On the side, using their old bearing and their current bearing is unstable, as it depends as much on your movement as your opponent's movement.

- Indeed, but i don't think i use the current bearing. EnemyNewBearing? is calculated from FiredPosition? to EnemyCurrent?.

So there are a couple of stabs at what might be problems. No time to test and see if I'm close... -- Kawigi

Thanks. I can certainly simplify things using your suggestions, so even if that doesn't fix it, it should make it easier to fix. -- Tango

OK, i lied about not using current bearing, i do in one place, but as i've commented out my movement, i'll fix that later. I also realised that i had EnemyNewBearing? in radians, which didn't help much... so with that fixed, things are slowing making more sense. I've got a problem with getting -ve offsets when i'm sure they should be +ve, though, so i need to check something there. -- Tango

I've fixed the -ve/+ve problem, there were some angles i hadn't normalised. It's still not hitting the target though. It looks like the GFs are correct, so i think the problem must be in the predicting section, rather than the data gathering section. The line in question is:

turnGunRight?(Math.toDegrees(robocode.util.Utils.normalRelativeAngle(Math.toRadians(getHeading()-getGunHeading?()+e.getBearing()+((BestGuess?-25)/25*46.66*(clockwise?1:-1))))));

I have updated the code with the changes i've made. Does anything look wrong in the above line of code? -- Tango

Not wrong maybe, but it's too crowded I think. Split it up in a few lines and it should make more sense. And you don't need those conversions between radians and degrees I think. It only hides what's going on.

double predictedBearing = Math.toRadians(((BestGuess - 25) / 25) * 46.66 * (clockwise ? 1 : -1)); turnGunRightRadians(robocode.util.Utils.normalRelativeAngle(getHeadingRadians() - getGunHeadingRadians() + predictedBearing);-- PEZ

I agree, it's very untidy, so is the rest of the code. The 46.66 is correct, then? In other bots i've seen an formula to get the number, but as i'm always using power 3 bullets (at the moment, at least), it seemed easier to make it constant. -- Tango

You could make a function named double maxEscapeAngle?(double firePower) or something that just returns this magic number 46.66 (which is correct, yes). That will both communicate what the number is about and provide for flexibility once you want to test other bullet power ranges. -- PEZ

I ought to modularise a lot of the code, it would make it so much more readable. At the moment, i've stopped working on my GF gun, because it was driving me mad, and am working on a PM gun, if i can eventually get them both to work, i intend to test a VirtualGuns system. My PM gun is 2/3 done, it makes the patterns and finds them, i now just have to do the forcasting bit, which hopefully won't be too hard. -- Tango

Yeah, when things get to messy, add VirtualGuns =) I think you can get your GF gun together. Just start with the modularization and you'll dig that bug up quite soon. It's hard to write a PM gun that's better than even the most basic GF one. -- PEZ

That depends on the enemy. If your enemy is very deterministic you can get a 100% hit rate with PM, getting 100% with GF is unusual. -- Tango

Though, in real combat, bots deterministic against GF guns are more common than bots deterministic from a PM point of view. A quick glance at the TargetingChallenge results table says that at least one bot in the RR@H roster can be hit 100% with a GF gun. Again I suspect you are argumenting from theoretical grounds. What I meant with better is "performs best against the competition". Walls and SpinBot are not the competition. You do as you like of course, but if the goal is higher ranking in the RR@H I would say that a GF gun is a better choice than a PM one. Unless, of course, you produce a spectacular PM gun. If you are going to put that GF gun into a VG array together with a PM gun (which I think is a good idea) you'll need to fix the GF gun first anyway, right? -- PEZ

Indeed, in real life, a good PM gun isn't as good as a good GF gun, but of course, any PM gun is better than a GF gun that doesn't work. The PM gun seems to be working, my first test got 49% in the PMC, and that's without any saving between rounds, and no tweaking. I'll probably release a version with just PM, while i fix the GF, because i don't know how long that will take. -- Tango

Up to 60%, but it's running very slowly because the string is too long. Next step is to handle missed scans better. -- Tango

Ah, progress is sweet! Are you checking the gun heat before running the matcher? That can speed things up considerably. Also doing a binary search gains speed. Is it a symbolic PM? Handling missed scans is quite easy I think. Check how LeachPMC/Code does it if you like. -- PEZ

I do check heat yes, but at the moment it checks each length increasing by 1 tick each time until it fails, a binary search is on my mental todo list. Yes, it is symbolic. I'm pretty sure of how to do missed scans, i just have to actually do it. -- Tango