package cslib;
import robocode.*;
import java.awt.Color;

/**
* SnippetBot - a robot by Alisdair Owens
* This bot includes all sorts of useful snippets.  It is not
* designed to be a good fighter (although it does well 1v1),
* just to show how certain things are done
* Bits of code lifted from Nicator and Chrisbot
* Conventions in this bot include: Use of radians throughout
* Storing absolute positions of enemy bots rather than relative ones
* Very little code in events
* These are all good programming practices for robocode
* There may also be methods that arent used; these might just be useful for you.
*/
public class SnippetBot extends AdvancedRobot
{
        /**
        * run: SnippetBot's default behavior
        */
        Enemy target;					//our current enemy
        final double PI = Math.PI;			//just a constant
        int direction = 1;				//direction we are heading...1 = forward, -1 = backwards
        double firePower;       			//the power of the shot we will be using - set by do firePower()

        public void run() {
                target = new Enemy();
                target.distance = 100000;			//initialise the distance so that we can select a target
                setColors(Color.red,Color.blue,Color.green);	//sets the colours of the robot

                //the next two lines mean that the turns of the robot, gun and radar are independant
                setAdjustGunForRobotTurn(true);
                setAdjustRadarForGunTurn(true);
                turnRadarRightRadians(2*PI);			//turns the radar right around to get a view of the field
                while(true) {
                        doMovement();				//Move the bot
                        doFirePower();				//select the fire power to use
                        doScanner();				//Oscillate the scanner over the bot
                        doGun();
                        out.println(target.distance);		//move the gun to predict where the enemy will be
                        fire(firePower);
                        execute();				//execute all commands
                }
        }

        void doFirePower() {
                firePower = 400/target.distance;//selects a bullet power based on our distance away from the target
        }

        void doMovement() {
                if (getTime()%20 == 0)  { 		//every twenty 'ticks'
                        direction *= -1;		//reverse direction
                        setAhead(direction*300);	//move in that direction
                }
                setTurnRightRadians(target.bearing + (PI/2)); //every turn move to circle strafe the enemy
        }

        void doScanner() {
                double radarOffset;
                if (getTime() - target.ctime > 4) { 	//if we haven't seen anybody for a bit....
                        radarOffset = 360;		//rotate the radar to find a target
                } else {

                        //next is the amount we need to rotate the radar by to scan where the target is now
                        radarOffset = getRadarHeadingRadians() - absbearing(getX(),getY(),target.x,target.y);

                        //this adds or subtracts small amounts from the bearing for the radar to produce the wobbling
                        //and make sure we don't lose the target
                        if (radarOffset < 0)
                        radarOffset -= PI/8;
                        else
                        radarOffset += PI/8;
                }
                //turn the radar
                setTurnRadarLeftRadians(NormaliseBearing(radarOffset));
        }

        void doGun() {

                //works out how long it would take a bullet to travel to where the enemy is *now*
                //this is the best estimation we have
                long time = getTime() + (int)(target.distance/(20-(3*firePower)));

                //offsets the gun by the angle to the next shot based on linear targeting provided by the enemy class
                double gunOffset = getGunHeadingRadians() - absbearing(getX(),getY(),target.guessX(time),target.guessY(time));
                setTurnGunLeftRadians(NormaliseBearing(gunOffset));
        }


        //if a bearing is not within the -pi to pi range, alters it to provide the shortest angle
        double NormaliseBearing(double ang) {
                if (ang > PI)
                ang -= 2*PI;
                if (ang < -PI)
                ang += 2*PI;
                return ang;
        }

        //if a heading is not within the 0 to 2pi range, alters it to provide the shortest angle
        double NormaliseHeading(double ang) {
                if (ang > 2*PI)
                ang -= 2*PI;
                if (ang < 0)
                ang += 2*PI;
                return ang;
        }

        //returns the distance between two x,y coordinates
        public double getrange( double x1,double y1, double x2,double y2 )
        {
                double xo = x2-x1;
                double yo = y2-y1;
                double h = Math.sqrt( xo*xo + yo*yo );
                return h;
        }

        //gets the absolute bearing between to x,y coordinates
        public double absbearing( double x1,double y1, double x2,double y2 )
        {
                double xo = x2-x1;
                double yo = y2-y1;
                double h = getrange( x1,y1, x2,y2 );
                if( xo > 0 && yo > 0 )
                {
                        return Math.asin( xo / h );
                }
                if( xo > 0 && yo < 0 )
                {
                        return Math.PI - Math.asin( xo / h );
                }
                if( xo < 0 && yo < 0 )
                {
                        return Math.PI + Math.asin( -xo / h );
                }
                if( xo < 0 && yo > 0 )
                {
                        return 2.0*Math.PI - Math.asin( -xo / h );
                }
                return 0;
        }


        /**
        * onScannedRobot: What to do when you see another robot
        */
        public void onScannedRobot(ScannedRobotEvent e) {
                //if we have found a closer robot....
                if ((e.getDistance() < target.distance)||(target.name == e.getName())) {
                        //the next line gets the absolute bearing to the point where the bot is
                        double absbearing_rad = (getHeadingRadians()+e.getBearingRadians())%(2*PI);
                        //this section sets all the information about our target
                        target.name = e.getName();
                        target.x = getX()+Math.sin(absbearing_rad)*e.getDistance(); //works out the x coordinate of where the target is
                        target.y = getY()+Math.cos(absbearing_rad)*e.getDistance(); //works out the y coordinate of where the target is
                        target.bearing = e.getBearingRadians();
                        target.head = e.getHeadingRadians();
                        target.ctime = getTime();				//game time at which this scan was produced
                        target.speed = e.getVelocity();
                        target.distance = e.getDistance();
                }
        }

        public void onRobotDeath(RobotDeathEvent e) {
                if (e.getName() == target.name)
                target.distance = 10000; //this will effectively make it search for a new target
        }

}

class Enemy {
        /*
        * ok, we should really be using accessors and mutators here,
        * (i.e getName() and setName()) but life's too short.
        */
        String name;
        public double bearing;
        public double head;
        public long ctime; //game time that the scan was produced
        public double speed;
        public double x,y;
        public double distance;
        public double guessX(long when)
        {
                long diff = when - ctime;
                return x+Math.sin(head)*speed*diff;
        }
        public double guessY(long when)
        {
                long diff = when - ctime;
                return y+Math.cos(head)*speed*diff;
        }

}