[Home]EnemyWave

Robo Home | Changes | Preferences | AllPages

Related concepts: /MovementAdaption - Wave - IntelligenceManagement
If you want to collect data on how your enemy might see your movement profile, one way is to fire Wave's from your enemy. I call these EnemyWaves. In GloomyDark I have tried, and failed, to implement a self-adjusting movement with this technique. Here's an outline of how it is set up.

Firstly. When Gloomy has detected that it is being fired upon it registers an EnemyWave custom event. Like so:

        currentMoveFactorVisits = moveFactorVisits[movePowerSegment()][moveAccelSegment()];
        currentMoveFactor = moveFactors[movePowerSegment()][moveAccelSegment()];
        robotMaxBearing = maxBearing(enemyFirePower);
        if (enemyHasFired) {
            EnemyWave ew = new EnemyWave(this, enemyFirePower, enemyLocation, robotLocation,
                robotDeltaBearing, robotMaxBearing, currentMoveFactorVisits, currentMoveFactor);
            addCustomEvent(ew);
            enemyWaves.add(ew);
        }
As you can see I am segmenting the enemy's gun much the same way as I might segment my own.

With these enemy waves I am currently just interested in when the enemy hits me. (This is modeled after how I have understood Paul's description of his adaptive movement system.) Which means I am looking for the following event:

    public void onHitByBullet(HitByBulletEvent e) {
        updateMoveFactor(e.getPower());
    }
Since two enemy waves (with different velocity) can reach me at the same time I am using the bullet power of the bullet that hit me to select the right wave to update my factors from:
    void updateMoveFactor(double power) {
        double shortestDistance = 70;
        EnemyWave selectedWave = null;
        Iterator iterator = enemyWaves.iterator();
        for (int i = 0, n = enemyWaves.size(); i < n; i++) {
            EnemyWave ew = (EnemyWave)enemyWaves.get(i);
            if (ew.powerIsEqual(power) && ew.shortestDistance() < shortestDistance) {
                selectedWave = ew;
                shortestDistance = ew.shortestDistance();
            }
        }
        if (selectedWave != null) {
            selectedWave.updateStats();
            selectedWave.updateMoveFactor();
        }
    }
What happens here is that I iterate among the currently active enemy waves to find the closest one, with the correct bullet power, that possibly could have hit me. Then I use this wave to update my movement profile (or rather my view of where the enemy is firing to try to hit me) and my magic flattener factor.

A Wave is a Wave is a Wave. Thus GloomyDark uses the same abstract Wave class as a superclass to both the waves it fires itself and the enemy's waves:

package pez.gloom.intel;
import pez.gloom.Bot;
import robocode.util.Utils;
import robocode.*;
import java.awt.geom.*;

// This code is released under the RoboWiki Public Code Licence (RWPCL), datailed on:
// http://robowiki.dyndns.org/?RWPCL
//
// $Id: Wave.java,v 1.1 2003/10/15 21:13:18 peter Exp $
public abstract class Wave extends Condition {
    Bot robot;
    double time;
    double bulletPower;
    double bulletVelocity;
    double deltaBearing;
    double maxBearing;
    Point2D gunLocation = new Point2D.Double();
    Point2D targetLocation = new Point2D.Double();
    Point2D currentTargetLocation;
    int[] factorVisits;
    double triggerDistanceDelta;
    int visitedIndex = -1;

    public Wave(Bot robot, double bulletPower, Point2D gunLocation, Point2D targetLocation,
                double targetDeltaBearing, double targetMaxBearing, int[] currentFactorVisits) {

        this.robot = robot;
        this.time = robot.getTime();
        this.bulletPower = bulletPower;
        this.bulletVelocity = Bot.bulletVelocity(bulletPower);
        this.deltaBearing = targetDeltaBearing;
        this.gunLocation.setLocation(gunLocation);
        this.currentTargetLocation = targetLocation;
        this.targetLocation.setLocation(targetLocation);
        this.maxBearing = targetMaxBearing;
        this.factorVisits = currentFactorVisits;
    }

    public boolean test() {
        if (triggerTest(triggerDistanceDelta)) {
            robot.removeCustomEvent(this);
            return true;
        }
        return false;
    }

    public void updateStats() {
        double bearingDiff = Utils.normalRelativeAngle(Bot.absoluteBearing(gunLocation, currentTargetLocation) -
            Bot.absoluteBearing(gunLocation, targetLocation));
        setVisitedIndex(bearingDiff);
        robot.registerFactorVisit(factorVisits, visitedIndex);
    }

    double distance() {
        return bulletVelocity * (double)(robot.getTime() - time);
    }

    boolean triggerTest(double distanceDelta) {
        return distance() > gunLocation.distance(currentTargetLocation) + distanceDelta;
    }

    void setVisitedIndex(double bearingDiff) {
        int numFactors = factorVisits.length;
        visitedIndex = (int)Math.round((((double)Bot.sign(deltaBearing) * bearingDiff) / maxBearing) * (numFactors - 1) / 2D + (numFactors - 1) / 2D);
        visitedIndex = Math.max(visitedIndex, 0);
        visitedIndex = Math.min(visitedIndex, (numFactors - 1));
    }
}
And the concrete EnemyWave class looks like this:
package pez.gloom.intel;
import pez.gloom.Bot;
import java.awt.geom.*;

// This code is released under the RoboWiki Public Code Licence (RWPCL), datailed on:
// http://robowiki.dyndns.org/?RWPCL
//
// $Id: EnemyWave.java,v 1.1 2003/10/15 21:13:17 peter Exp $

public class EnemyWave extends Wave {
    float[] moveFactor;

    public EnemyWave(Bot robot, double bulletPower, Point2D gunLocation, Point2D targetLocation,
                double targetDeltaBearing, double targetMaxBearing, int[] currentFactorVisits, float[] moveFactor) {

        super(robot, bulletPower, gunLocation, targetLocation, targetDeltaBearing, targetMaxBearing, currentFactorVisits);
        this.moveFactor = moveFactor;
        this.triggerDistanceDelta = 80;
    }

    public boolean powerIsEqual(double power) {
        return (int)Math.round(power * 1000) == (int)Math.round(bulletPower * 1000);
    }

    public double shortestDistance() {
        return Math.abs(gunLocation.distance(currentTargetLocation) - distance());
    }

    public void updateMoveFactor() {
        double visitedFactor = Math.abs(Bot.visitIndexToFactor(visitedIndex, factorVisits.length));
        int mostVisitedIndex = Bot.mostVisitedIndex(factorVisits);
        double mostVisitedFactor = Math.abs(Bot.visitIndexToFactor(mostVisitedIndex, factorVisits.length));
        if (visitedFactor >= 0.9) {
            moveFactor[0] *= (float)(1.01);
        }
        else if (Math.random() < 0.05) {
            moveFactor[0] *= (float)(0.99);
        }
    }
}
There. The only problem with this is that it doesn't really work. I often fail to find the corresponding wave when I am hit. So this is both a way for me to share this technique together with some implementation details and request some help in finding whatever bug is lurking there causing the missed waves or maybe spot where I am thinking wrongly with this.

Since I have not got the infrastructure quite right yet, I am not sure if I am on the right track with my movement adjustment either. Please share your views on that too. -- PEZ


Can't you do everything exactly the same as you do with your own waves? Whenever a wave has travelled the required distance to have possibly hit you (it doesn't matter if it hit or missed) you update the curve exactly the same way you would with your profile of the enemy, and then whenever you find peaks and troughs you try to level them out. -- Tango

Yes, that's one way to do it. But I'm trying to avoid resorting to that since I think that what's really important is avoiding where the enemy is firing, rather than where it should be firing. Consider a bot firing at the same guess factor all the time (quite a few bots do this, guess factor 0.0, i.e. head-on). Keeping a flat curve almost rewards this, while avoiding that guess factor successfully would make it miss almost all the time. Paul mentioned "keeping a flat profile regardless of enemy firing strategy" (or something along those lines). This is about that rather than being flat against guess factor targeters alone. -- PEZ

Prehaps you could get a profile of your own movement in the way i described, and also do some pattern matching on yourself, and then sacrifice yourself a little and go to exactly the points the curve, the PM, head-on, linear, circular, etc. predict, and if you get hit at one of them, avoid it in the future. To get accruate results, however, you may well have to get hit by enough bullets that you will be dead... as long as the enemy doesn't use virtual guns, you can use the info in future battles without the sacrifice, so that will be ok. Virtual guns are a bit of a problem... I guess once you have decided what to avoid, you switch to going to all the other places, and wait until you start getting hit in that one, and then you know the gun has changed, so you start avoiding that one. Might be a little complicated... -- Tango

I try to avoid complicated stuff and that surely sounds a bit on the complicated side of things. =) Though I don't worry too much about VirtualGuns enemies. No one but Paul and myself seem to think they are a very good idea anyway. -- PEZ

Remind me, who's bot is no. 1 at the moment? ;-) -- Tango

I think it's one using the guess factors where it is hit to adapt it's movement. =) -- PEZ

I've been tinkering around with a bot called FloodAGF? for the last week or so which does this and attempts to dodge himself. If he DOES get hit, he adds enough to that bucket for it to be the new highest guess factor. The cool thing about this is that once it works, it will theoretically be able to 'learn' to dodge head-on aim perfectly, as well as some other simple aiming algorithms. -- Kawigi

Yeah! That's maybe the way I should go about this. Do you use the enemy's bullets for the waves or do you just fire power three waves from the enemy constantly or some such? -- PEZ

Well, I'm already detecting when they fire, so I'm using the enemy's bullets at the powers I'm seeing them fire at me. I actually took FloodMini's gun and 'modularized' it to do this, and just feed the enemy's 'gun' with a re-polarized ScannedRobotEvent I create. -- Kawigi


Hey hey! [Timmy_The_Cheerful_Monkey] uses EnemyWaves for dodging! :-) --Dummy

And can you outline how it Timmy does it here please? Or if you can see where things go wrong in Gloomy... -- PEZ

Timmy only tracks one enemy bullet at a time. Timmy tries to dodge a couple of ticks (either 8 or 14 IIRC) before impact of that Wave. Timmy didn't always dodge, though. There was either a 80% or 30% (IIRC) chance that Timmy reverses direction every time a Wave approaches. Every time Timmy is hit by a bullet, Timmy switches between 80% and 30%, and between 8 and 14 ticks before impact. Didn't really work that well... (It was a JustForFun Robot ;-) ) --Dummy


OK Vuen now please tell me how you accurately catch those enemy waves in Fractal when you are hit. I really need a break on that area... -- PEZ

Um, I never actually made that part of Fractal either; it does make the waves line up exactly with the bullets, but it doesn't yet actually make the connection between getting hit by a bullet and its associated wave. However, this is the way I would do it: when a bullet hits you, you now have the XY coordinates of the bullet. You'll have to look through each wave and convert the bullet's position to polar relative to the center of that wave, then ignore the angle and compare the radial component. In other words, for each wave, calculate the distance from the bullet to the center of the wave. If the difference between this distance and the wave's radius is less than, say, 0.1, you've found the wave associated with that bullet. -- Vuen

My tests with this unfortunately seem to have a bug here... -- Kawigi

Print out the results of the differences for each wave when you get hit; I bet you'll turn up with a wave having either 1 or 2 bullet speeds off every time. Remember that when creating a wave upon detecting an enemy bullet, you have to iterate it ahead 2 ticks; when a bullet is launched, it is iterated a tick immediately upon being launched, and then is iterated an extra tick when the next frame advances. Thus when firing your own bullets, you set the launched waves as having already travelled a tick. -- Vuen

Good! Now we are getting somewhere with this page. =) Does this mean I should also use the enemy's location the tick before I see the drop in energy? -- PEZ

I have chased this bug for two months or so. Iterating the EnemyWave two ticks and registering the gun and target locations of the last tick immediately fixed the bug. Now I can accurately catch the right waves. I don't know how to thank you Vuen. I hope it is reward enough for you to know you have saved my sanity (whatever I have left anyway). =) -- PEZ

Heh, you're welcome. I also spent months bashing my head of the desk because of this bug way back in the day when building SineSweep; this was one of the bugs that caused SineSweep's intercept ratio to drop heavily. You can imagine that this seemingly slight 5 or so pixel difference had devastating effects on its intercept ratio. It took my months to track it down (and I don't think I ever got it right anyway, at least not in SineSweep.) Speaking of which, I seriously need to rebuild that bot... -- Vuen

OK. I have been playing with this whole Wave's as Custom Conditions paradigm. At first I thought they were pretty neat and saved some space. I am now of the opinion that their simplicity and smaller code size are a trap. I placed some System.out.println in each of my Event handlers telling me what order the events were being processed in to see what was going on. What I found is that if you rely on the Event Handler you are most likely to have your enemy position off by one tick because the CustomEvent will happen (be processed) before the onScannedRobot event. This means your Wave will hit the enemy and calculate a GuessFactor from the position of the Enemy last tick. The enemy location used can be off by as many as 8 pixels. This is probably not a hugely significant thing but I have noticed in my own research that it does adversely affect my accuracy (by 2% - 3%). I tried to setEventPriority? of these events but this did not seem to have any effect. It is almost like the Robocode engine is serially creating them, in order of it's defined percedence and sending them to your robot. This leads me to believe that you would have to creat an Event Handler of your own for the setEventPriority? to work. If your match is a close thing this might be the thing that turns it over to your enemy. If code space is tight, this might be the way to go if you can afford the loss in accuracy. Comments? -- jim

I check all my waves manually in the onScannedRobot event, that way i know exactly what order everything is happening in, because i coded it. Admittedly, my GF gun doesn't actually work, but... -- Tango


Lately I've been toying around with EnemyWaves and GuessFactors. Since I'm a practical newbie at this, I decided to do a little brainstorming here to see if someone more experienced can detect any logical error. I came to the conclusion that to simulate a perfect "inverse" segmented GF gun, you have to know your own movement data since up to 4 ticks before you detected the enemy's energy drop:

That is, as I see it, the only way of precisely simulating the enemy's "line of though", is it not? -- ABC

Seems robust. But why do you need to know when the enemy decides to fire? I must be missing something... -- PEZ

You need 1 tick to rotate the gun... -- ABC

Still it's where the enemy fires that's important for the EnemyWave alignement, no? -- PEZ

For alignement yes, but not for segmentation. To classify the bullet exactly like your enemy did, you have to use the info he had when he decided to fire. Maybe I'm just beeing too picky... -- ABC

Not at all. I just didn't know you where talking about segmentation. It's not obvious from the above code snippets, but when that code was active in GloomyDark i was segmenting the enemy waves the same way i segmented my own. I never thought about having to back away two ticks more for that. I think you have to account for the bullet being advanced a tick from the bot also when it is fired. So you need to subtract two from the time you observe the energy drop. (But still use the enemy position of tick -1.) -- PEZ

I said "a perfect inverse segmented gun" ;). I read about the (time - 2) trick, I'm already doing it like that. But, about the segmentation, if you classify the enemy bullet with your current data (let's say distance and speed), you can be very far off from what the enemy used for it's own segmentation. Your speed, for example, could have been 4 when he decided to fire and 0 when you detected it. You do stats[dist][0]++ and he does stats[dist][4]++.-- ABC

Totally agreed. I just didn't think of that back when I was doing enemy waves. And probably wouldn't have thought about it ever if you hadn't shared it. -- PEZ

Isn't your opponent picking his final segment and firing in the same tick? His gun should already be spun in that general direction from previous observations thus finish spin + fire on the same execute()? -- Kuuran

I've always assumed that firing is the very first thing executed by the RC-engine, before gunTurn/turn/accel/move. At least in by bots, I always reserve a tick for gunTurn and assume I'll be firing from my next xy position. I remember noticing a slight precision increase when I started doing it like that... -- ABC

When i started write minibots i forgot all of this,now i am hat minibots a bit so i had not write minibots for a long time.;[ These days i was trying to do some PM-State gun like ABC did,but i have not got any success~;[ it did badly and skipped many turn(I just want it can ananyse 20 rounds's data) -- iiley

Since i've got my RobocodeGLV014 working (at last!), I've beeing able to synchronize my EnemyWaves correctly, and my AM bot is finally taking shape. I'm using a code based on above's PEZ's code, and obviously giving him the credits. But i'm curious about the idea: does anybody knows to whom i shall give the credits for the idea? My suspects: PEZ, Vuen, Kawigi, Paul... The list is endless... Axe

I'm probably off the list, since I have yet to take the time to get it working correctly. I believe Vuen would be the person who first figured out the oddities of it all. -- Kawigi

I think I might have been the first to use EnemyWaves to guide movement. This was in an early version of Marshmallow. Some of it found its way into Princess as well, but I don't remember if it's left there or if we finally decided on a simpler version of the movement. However I used it rather clumsily and the AdaptiveMovement was reinvented by Paul later. It was when he described it on the SandboxDT page that I started to implement the EnemyWave code you find on this page. So I'd say we should credit Paul the most, but also myself for exploring the EnemyWave mechanics in an OpenSource way and then of course Vuen for helping me getting them aligned. Without Vuen's successful experiments with aiming at the bullets of the enemy who knows if I had ever gotten those EnemyWaves to work. -- PEZ

So, if everybody agee, i'll credit it to PEZ, Vuen & Paul. Is that ok to u all? -- Axe

While developing HuntedHunter? which i try to make surfing waves i wrote some graphical debugging stuff in order to see what my bot thinks. And i am drawing a circle ( of cause its an ellipse with the form of a circle ) that shows the enemy wave that is nearest to me. There i found a bug that made me shoot enemy waves with the current time when i detect an energy drop. I corrected it to firedTime of the enemy wave being current time-1 , but still the bullet i can see is more far away from the shot-position than the drawn circle that stands for that bullet.Now i dont know if there is something wrong in my implementation or if the time a bullet is shot is really time-of-detection - 2 . Can anyone help me. hope one could understand my poor german-pupil-english????

Indeed, it is time of detection-2 (but the previous position, pos at time-1). -- Pulsar

because pos at time-1 is real position of time-2 right??? --deathcon

This page must be pretty cluttered because, even though it tells about this "time - 2" thingy, people seem to have to find out themselves anyway. This is why: When a bot fires a bullet it is advanced, by Robocode, one tick. And you detect the enemy's drop in energy one tick after it has fired. 1 + 1 = 2 in Robocode. Does that make sense? -- PEZ

OIC sorry, but then the position where he fired the bullet must be the current position you know about him.

 tick  0: energy drop  tick -1: enemy fired
He fired at time -1 and you detect the energy drop at time+-0. The position you think he is at time+-0 is the real position of time-1. Whats wrong??? -deathcon

I don't think I quite follow you here... But what Pugilist does is to use the enemy's position from the last tick as the waves origin. Then I advance the wave 2 ticks. Does this answer your question? -- PEZ

@deathcon Here is the best I can explain it.

tick 1: enemy fires and the bullet is automatically advanced bulletVelocity pixels on its shot bearing. 
Shooter was at point P1, this is also the origin of the bullet, even though the bullet never physically "occupies" this point.

tick 2: enemy has moved to point P2 
bullet has moved another bulletVelocity pixels forward on it's shot bearing. 
You detect the enemy drop in energy and now want to create a wave. 
     To create this wave you need to use the enemies last position (P1) as the shot origin. 
     You have to use 2X bulletVelocity for the distance the wave has traveled to accurately 
         reflect the bullets position.
If you have RobocodeGLV014 working this is all pretty clear if you try to display your waves. Think of the bullets flight as a ray starting from the shooters position at shot time. I think you are getting hung up on the fact that the bullet does not start at "zero" on this ray, but actually "magically" appears bulletVelocity units from the origin and proceeds forward from there. In summary, when you create enemy waves (waves that are meant to represent enemy shots fired at you), use the enemies last position and 2X bulletVelocity when initializing the wave. Hope this helps --jim

Sorry, my misstake. I just thought that when the onScannedRobot funktion of you robot is called by robocode it gives you the values ( ScannedRobotEvent.getHeadingRadians() ... ) of the LAST tick but since this function is called when you scan the enemy and not one tick later at the beginning of the new tick you are right. THX --deathcon


Another thing: Why do most Robots that are EnemyWave surfing use only onHitByBullet? and not additionally onBulletHitBullet? to find out with which guessfactor the enemies gun shot at him??? --deathcon

Because most of us are lazy. =) Axe does that actually. And he might also be the best surfer around. Maybe because of that. Maybe not. I don't think it matters that much. But Axe can actually veryfy that. -- PEZ

Indeed i do this (donīt know if only me, itīs quite obvious actually), i had post it at PulsarMax page (iirc)... I do it because i donīt like the idea of adapting only when beeing hurt (actually i donīt like beeing hurt:). But i canīt tell how much exactly i gain with it, since iīm doing it since almost the beggining... -- Axe

Hmm how would one use it? Find the wave I used to fire and the enemy wave. Then calculate the guess factor by extending that line from enemy fire loc and see what guess factor that was aimed at? Hmm seems easier now when I wrote it down, unlesss I forgot something! I like it a lot, as you say; gather just as useful stats without being hit but as if hit. -- Pulsar

Yeah thats the idea. RaikoMX dont use this, imho. --deathcon

It probably will next version - it's a simple optimization, I just never got around to it. :)

Pulsar - How are you calculating the angles for bullets which hit you at the moment? If you use the position of your bot to do it then the results tend to be a little off; if you use the position of the enemy's bullet then you can, IIRC, get the bullet using getHitBullet? and feed that into your original algorithm. -- Jamougha

Good idea. Thanks for sharing! -- PEZ

I use a methode onSeeEnemyBullet?(Bullet b) that is invoken by onHitByBullet? and onBulletHitBullet?. Cant wait to see the ranking of the new RaikoMX --deathcon


Robo Home | Changes | Preferences | AllPages
Edit text of this page | View other revisions
Last edited June 1, 2004 13:25 EST by PEZ (diff)
Search: