[Home]David Alves/AntiSpinBot

Robo Home | David Alves | Changes | Preferences | AllPages

My entry in the SpinBotChallenge. Attempts to get a perfect score against SpinBot.

Please do not enter modified versions of this bot in the SpinBotChallenge, although you are welcome to use my ideas in your own bot for the challenge.



/*  Created on Oct 13, 2004

David Alves presents
   _____             __   .__   _________        .__         __________           __
  /  _  \    ____  _/  |_ |__| /   _____/______  |__|  ____  \______   \  ____  _/  |_
 /  /_\  \  /    \ \   __\|  | \_____  \ \____ \ |  | /    \  |    |  _/ /  _ \ \   __\  .
/    |    \|   |  \ |  |  |  | /        \|  |_> >|  ||   |  \ |    |   \(  <_> ) |  |
\____|__  /|___|  / |__|  |__|/_______  /|   __/ |__||___|  / |______  / \____/  |__|
        \/      \/                    \/ |__|             \/         \/         
                                                     ____        ______ 
                                                    |_   |      |      |
                                                     _|  |_  __ |  --  |
                                                    |______||__||______|
*/
package davidalves.robot;


import java.awt.Color;
import java.awt.geom.*;

import robocode.*;

/**
 * @author David Alves
 *
 */
public class AntiSpinBot extends Robot{
    
    static final boolean firingEnabled = true;
    static final boolean movingEnabled = true;
    
    static final int ENEMY_FIREPOWER = 3;
    static final int ENEMY_BULLET_SPEED = 20 - 3 * ENEMY_FIREPOWER;
    
    //Time SpinBot takes to drive a complete circle.
    static final int ENEMY_CIRCLE_TIME = 58;
    
    //SpinBot fires once per revolution
    static final int ENEMY_FIRE_FREQUENCY = ENEMY_CIRCLE_TIME;
    
    //Size of the circle that SpinBot drives in
    static final int ENEMY_TURN_CIRCLE_RADIUS = 46;
      
    //We stay at less than this distance to ensure that there will only be one enemy bullet in the air at a time
    static final int MAX_DISTANCE = (ENEMY_FIRE_FREQUENCY - 12) * ENEMY_BULLET_SPEED;
    
    //We need at least 30 ticks to dodge safely
    static final int MIN_DISTANCE = 30 * ENEMY_BULLET_SPEED;
    
    //If we only have 24 ticks or less to dodge each bullet,
    //forget about dodging and just drive far away 
    static final int PANIC_DISTANCE = 19 * ENEMY_BULLET_SPEED;
    
    //try to maintain this distance
    static final int IDEAL_DISTANCE = (MAX_DISTANCE + MIN_DISTANCE) / 2;
    
    static Rectangle2D field;
    
    static double enemyEnergy;
    static long lastScanTime = 0;
    static long lastFireTime = 0;
    static long nextFireTime;
    static double fireRange;
    
    static boolean lastDirectionWasClockwise;
    
    static double enemyHeadingAtMoveStart;
    static double enemyHeadingDegrees;
    static double enemyAngleDegrees;
    Point2D.Double enemy, circleCenter, fire;
    
    //A "death hop" is a dodge made after spinbot dies... in case he fired right before dying and we didn't notice
    //This variable ensures we only do one death hop per round.
    static boolean okToDeathHop = true;
    
    
    //stats
    static int skippedTurns;
    static int damageTaken;
    static int ramCount;
    
    static String state = "";
    
    public void run(){
        //My colors, used on all my bots
        setColors(Color.darkGray, Color.darkGray, Color.cyan);
        
        enemyHeadingAtMoveStart = Double.NaN;
        enemyHeadingDegrees = Double.NaN;
        okToDeathHop = true;
        fireRange = 0;
        enemyEnergy = 100;
        lastFireTime = -100;
        nextFireTime = 30;
        lastScanTime = -100;
        enemy = null;
        circleCenter = null;
        fire = null;
        enemyAngleDegrees = Double.NaN;
        setAdjustGunForRobotTurn(true);
        setAdjustRadarForGunTurn(true);
        setAdjustRadarForRobotTurn(true);
        
        field = new Rectangle2D.Double(18,18, getBattleFieldWidth() - 36, getBattleFieldHeight() - 36);
        
        do{
            
            //First find SpinBot
            state = "find SpinBot";
            findSpinBot();
            
            //Sit still until enemy fires, then we dodge. If moving is disabled we never leave this loop,
            //and therefore never dodge.
            while(movingEnabled == false || getTime() - lastFireTime > 10){
                
                //Sit still and sweep radar back and forth across enemy. Fire if gun is cool.
                state = "sit still";
                sitStill();
                
                //If enemy is very close to us, retreat to bullet-dodging range.
                if(circleCenter != null && circleCenter.distance(getX(), getY()) < PANIC_DISTANCE){
                    print("SpinBot is too close... retreating");
                    break;
                }
                
                Point2D.Double me = new Point2D.Double(getX(), getY());
                if(    normalRelativeAngle(enemyHeadingAtMoveStart - absoluteBearing(enemy, me)) < 0 &&
                    normalRelativeAngle(enemyHeadingDegrees - absoluteBearing(enemy, me)) > 0){
                        long detectedTime = Math.round(getTime() - normalRelativeAngle(enemyHeadingDegrees - absoluteBearing(enemy, me)) / (360.0 / ENEMY_CIRCLE_TIME) );
                        if(Math.abs(detectedTime - lastFireTime) > 10){
                            print("SpinBot fired while I was moving!! estimated time: " + detectedTime);
                            break;
                        }
                }
            }
            enemyHeadingAtMoveStart = enemyHeadingDegrees;
            
            //Dodge
            goTo(getDodgePoint());
        }while(true);
    }
    
    public void onScannedRobot(ScannedRobotEvent e) {
        double enemyAngleRadians = Math.toRadians(getHeading()) + e.getBearingRadians();
        enemyAngleDegrees = Math.toDegrees(enemyAngleRadians);
        double distance = e.getDistance();
        
        
        
        enemy = new Point2D.Double( getX() + Math.sin(enemyAngleRadians) * distance,
                                    getY() + Math.cos(enemyAngleRadians) * distance);
        enemyHeadingDegrees = e.getHeading();
        
        boolean enemyHeadingIndicatesFired = Math.abs(normalRelativeAngle(absoluteBearing(enemy, new Point2D.Double(getX(), getY())) - e.getHeading())) < 10;
        boolean enemyEnergyIndicatesFiredAtStartOfRound = lastScanTime == -100 && e.getEnergy() <= 97.0;
        boolean fired = enemyEnergyIndicatesFiredAtStartOfRound || enemyHeadingIndicatesFired;
        if(fired){
            fire = new Point2D.Double(enemy.x, enemy.y);
            lastFireTime = getTime();
            print("Enemy fired at range " + distance);
            fireRange = distance;
        }
        
        enemyEnergy = e.getEnergy();
        
        //The point that SpinBot is orbiting
        circleCenter = project(enemy, ENEMY_TURN_CIRCLE_RADIUS, e.getHeading() + 90);
        
        lastScanTime = e.getTime();
    }
    
    private Point2D getDodgePoint(){
        Point2D.Double enemyCircleCenter = circleCenter;
        double angleOffset;
        double dodgeDistance;
        double distance = enemyCircleCenter.distance(getX(), getY());
        
        if(distance < PANIC_DISTANCE) {
            //out.println("< min");
            state = "panic move - " + distance;
            angleOffset = 140;
            dodgeDistance = 170;
        } else if(distance < MIN_DISTANCE) {
            state = "min move - " + distance;
            //out.println("< min");
            angleOffset = 140;
            dodgeDistance = 120;
        } else if(distance < IDEAL_DISTANCE){
            state = "less than ideal move - " + distance;
            //out.println("< ideal");
            angleOffset = 95;
            dodgeDistance = 65;
        } else if (distance < MAX_DISTANCE){
            state = "greater than ideal move - " + distance;
            //out.println("< max");
            angleOffset = 75;
            dodgeDistance = 65;
        } else {
            //print("> max");
            state = "greater than max move - " + distance;
            angleOffset = 55;
            dodgeDistance = 100;
        }
        Point2D.Double me = new Point2D.Double(getX(), getY());
        double angleToTarget = absoluteBearing(me, circleCenter);
        
        Point2D cw, ccw;
        
        int smoothCW = 0; //How much we have to wallsmooth to drive clockwise
        do{
            cw = project(me, dodgeDistance, angleToTarget + angleOffset - smoothCW * 3);
            smoothCW++;
        }while(!field.contains(cw));
        
        int smoothCCW = 0; //How much we have to wallsmooth to drive counter-clockwise
        do{
            ccw = project(me, dodgeDistance, angleToTarget - angleOffset + smoothCCW * 3);
            smoothCCW++;
        }while(!field.contains(ccw));
        
        
        //Keep traveling in the same direction unless it forces us to wallsmooth a lot more than reversing
        if(lastDirectionWasClockwise){
            if(smoothCW <= smoothCCW + 3){
                return cw;
            } else {
                lastDirectionWasClockwise = false;
                return ccw;
            }
        } else {
            if(smoothCCW <= smoothCW + 3){
                return ccw;
            } else {
                lastDirectionWasClockwise = true;
                return cw;
            }
        }
    }
    
    public void findSpinBot(){
        while(lastScanTime != getTime()) turnRadarRight(44.9);
    }
    
    public void sitStill(){
        
        //if(firingEnabled && getGunHeat() == 0.0 && getTime() - lastScanTime <= 1 && enemy.distance(getX(), getY()) > MIN_DISTANCE){
        if(firingEnabled && getGunHeat() == 0.0 && getTime() - lastScanTime <= 1 && circleCenter.distance(getX(), getY()) > MIN_DISTANCE){
            state = "aim and fire";
            gun();
            return;
        }
        state = "radar";
        /*if(getTime() - lastScanTime >= 3){
            turnRadarRight(44.9);
        } else {*/
            double radarTurnAngle = normalRelativeAngle(enemyAngleDegrees - getRadarHeading());
            if(radarTurnAngle > 0){
                radarTurnAngle = Math.min(44.9, radarTurnAngle + 15);
            } else {
                radarTurnAngle = Math.max(-44.9, radarTurnAngle - 15);
            }
            turnRadarRight(radarTurnAngle);
        }
    //}
    
    public void gun(){
        double gunTurnTime = Math.ceil(Math.abs(normalRelativeAngle(getGunHeading() - getAbsoluteAngleDegrees(getX(), getY(), enemy.x, enemy.y)))/ 10.0);
        double distanceBulletTraveled = - 11 *  gunTurnTime;
        double enemyPositionOnCircle = absoluteBearing(circleCenter, enemy);
        Point2D.Double projected;
        //print("gunTurnTime: " + gunTurnTime);
        if(enemyEnergy != 0.0){
            do
            {
                distanceBulletTraveled += 11.0; 
                enemyPositionOnCircle += 360.0 / ENEMY_FIRE_FREQUENCY;
                projected = project(circleCenter, ENEMY_TURN_CIRCLE_RADIUS, enemyPositionOnCircle);
                
            }while (projected.distance(getX(), getY()) > distanceBulletTraveled);
        } else {
            projected = enemy;
        }
        //print("enemy:     " + enemy);
        //print("projected: " + projected);
        double gunAngle = normalRelativeAngle(getAbsoluteAngleDegrees(getX(), getY(), projected.x, projected.y) - getGunHeading());
        if(gunTurnTime < 5){
            turnGunRight(gunAngle);
        } else {
            turnGunRight(Math.max(Math.min(gunAngle, 50), -50));
            return;
        }
        fire(3);
    }
    
    private void goTo(Point2D destination) {
        
        Point2D location = new Point2D.Double(getX(), getY());
        double distance = location.distance(destination);
        double angle = normalRelativeAngle(absoluteBearing(location, destination) - getHeading());
        if (Math.abs(angle) > 90) {
            distance *= -1;
            if (angle > 0) {
                angle -= 180;
            }
            else {
                angle += 180;
            }
        }
        turnRight(angle);
        ahead(distance);
    }
    
    private void print(String s){
        out.println(getRoundNum() + ":" + getTime() + ": " + s);
    }
    
    private double getAbsoluteAngleDegrees(double x1, double y1, double x2, double y2){
        return Math.toDegrees(Math.atan2(x2 - x1, y2 - y1));
    }
    
    private double absoluteBearing(Point2D source, Point2D target) {
        return Math.toDegrees(Math.atan2(target.getX() - source.getX(), target.getY() - source.getY()));
    }

    private double normalRelativeAngle(double angle) {
        angle = Math.toRadians(angle);
        return Math.toDegrees(Math.atan2(Math.sin(angle), Math.cos(angle))); 
    }
    
    private Point2D.Double project(Point2D source, double distance, double angle){
        return new Point2D.Double(source.getX() + distance * Math.sin(Math.toRadians(angle)), source.getY() + distance * Math.cos(Math.toRadians(angle)));
    }
    
    public void onRobotDeath(RobotDeathEvent e){
        enemyEnergy = 0;
        if(withinExpectedFireTime()) lastFireTime = e.getTime();
    }
    
    public boolean withinExpectedFireTime(){
        return Math.abs(getTime() - (lastFireTime + ENEMY_FIRE_FREQUENCY)) < 10;
    }
    
    public void onSkippedTurn(SkippedTurnEvent e){
        skippedTurns ++;
    }
    public void onWin(WinEvent e){
        onDeath(null);
    }
    public void onDeath(DeathEvent e){
        print("skipped turns: " + skippedTurns);
        print("damage taken: " + damageTaken);
        print("times rammed: " + ramCount);
    }
    
    public void onHitByBullet(HitByBulletEvent e){
        damageTaken += 4 * e.getPower();
        if(e.getPower() > 1) damageTaken += 2 * (e.getPower() - 1);
        if(lastScanTime != e.getTime()){
            enemyEnergy += 3 * e.getPower();
        }
        print("hit by bullet of power " + e.getPower() + " at distance " + fireRange + " while in state: " + state);
    }
    
    public void onHitRobot(HitRobotEvent e){
        ramCount++;
        enemyEnergy = e.getEnergy();
    }
    
    public void onBulletHit(BulletHitEvent e){
        enemyEnergy = e.getEnergy();
    }
}


Comments

I just want to say it's amazing, you are crazy, its the most amazing complex robot extends Robot i had seen. And the code is beautiful:). -- iiley

I'm as amazed as iiley is. How does this bot fare in the ExtendsRobotCompetition? -- PEZ

I think this robot would do very very badly against anything besides SpinBot. But I might modify it a bit and enter one in that. :-) --David Alves


Robo Home | David Alves | Changes | Preferences | AllPages
Edit text of this page | View other revisions
Last edited October 25, 2004 3:56 EST by David Alves (diff)
Search: