inteligencia en redes de comunicaciones robocode

38
INTELIGENCIA EN REDES DE COMUNICACIONES ROBOCODE Jorge Carrasco Troitiño NIA 100029724 Elisabeth Pérez Gracía NIA 100029638

Upload: others

Post on 15-Mar-2022

1 views

Category:

Documents


0 download

TRANSCRIPT

INTELIGENCIA EN REDES DE COMUNICACIONES

ROBOCODE

Jorge Carrasco Troitiño NIA 100029724 Elisabeth Pérez Gracía NIA 100029638

INTRODUCCIÓN:

Robocode es un entorno de simulación de guerras de robots, desarrollado por Alphaworks de IBM, con Mathew Nelson a la cabeza, en el que se pueden programar tanques de combate en Java para pelear en el campo de batalla contra tanques programados por otros jugadores. Existen dos modos de juego: batalla individual, en el que cada robot lucha contra todos los demás, y batalla en equipo, en el que un ejército de robots lucha por la victoria de modo colaborativo.

Se trata de un divertido juego de programación que permite aprender Java creando robots Java propios, que no son más que objetos Java reales que combaten en pantalla contra otros robots. Mediante la programación en Robocode se puede aprender Java, manejar eventos….Utiliza un gestor de seguridad particular que permite a clases Java particulares escritas por cualquiera ejecutarse de manera segura en nuestro sistema.

Todas las clases generadas en Robocode extienden de la clase robocode.Robot, con métodos que permiten interactuar con el juego.

En Robocode hay dos elementos principales, los robots y los combates. Los combates se llevan a cabo en el campo de batalla, entre los robots, que juegan por sí mismos. En cuanto a los robots, un robot Robocode típico tendrá el siguiente aspecto:

a simple vista podemos observar la presencia de un cañón giratorio sobre el robot, y un radar, también giratorio sobre el cañón. Tanto el vehículo, como el cañón, como el radar pueden rotar independientemente (siempre y cuando así lo indiquemos a través de métodos como setAdjustGunForRobotTurn(false)), gracias a una serie de métodos básicos (turnRight, ahead, turnGunmRight, turnRadarRight…). Toda la información acerca de nuestro Robot y su situación en la partida podrá ser accedida fácilmente (a través de métodos como getX, getHeading, getGunHeading, getRadarHeading), así como las características físicas del campo de batalla (getBattleFieldWidth/Height).

Al ser el objetivo del juego derrotar a los otros robots, Robocode contará también con multitud de métodos para gestionar los disparos y los ataques a nuestros enemigos.

Al principio de cada combate todo robot comienza con un nivel de energía por defecto, y se considera destruido el robot cuando su nivel de energía cae hasta cero, existiendo varias situaciones en la que la energía de un robot puede disminuir, como son que te alcance un disparo enemigo, que te choques con un enemigo, que te choques contra una pared, además de por la energía liberada en el disparo (cuanta más energía se

imprima a un disparo más daño inflingirá en el robot enemigo en caso de alcanzarlo pero también más nos restará a nosotros), por ejemplo. Pero no todo son pérdidas de energía, también se recupera energía cuando se alcanza a algún enemigo o de forma constante por “enfriamiento de los cañones”.

Durante la batalla se generan “eventos”, estos eventos son situaciones que se producen a partir de las cuales el robot puede decidir hacer unas cosas u otras. Un robot básico tiene por defecto manejadores para varios eventos, que consisten en no hacer nada cuando se produzcan, pero al ser todo robot heredero de la clase Robot, estos métodos se pueden sobrescribir y así añadir funcionalidad a los robots cuando dichos eventos se produzcan. Algunos de los eventos usados más frecuentes son:

� ScannedRobotEvent: Se produce cuando el radar detecta un robot a su paso por una zona angular.

� HitByBulletEvent: Se produce cuando el robot es alcanzado por una bala de uno de los cañones enemigos.

� HitRobotEvent: Se produce cuando nuestro robot alcaza con un disparo a uno de los enemigos.

� HitWallEvent: Se produce cuando el robot choca contra un muro de los que limitan el campo de batall.

A la hora de programar uno de estos robots tendremos que tener en cuenta cuatro

áreas fundamentales del código: - un área en la que se definan las variables de clase, disponibles dentro del método run(), así como en el resto implementados. - el propio método run(), que es llamado por el gestor de combate para comenzar la vida del robot y típicamente se divide en dos áreas: un en la que se definen las cosas que sólo se harán una vez por cada instancia del robot, y una segunda parte dentro de un while infinito en la que se define la acción repetitiva en la que se verá envuelto el robot. - Métodos auxiliares para usar por el robot dentro de su método run. En esta zona también se ponen los manejadores de eventos que se quieran implementar.

Cualquier persona puede crear subclases de Robot y añadir nuevas funcionalidades que pueden ser usadas para construir robots. Robocode proporciona una subclase a Robot: AdvancedRobot, que permite realizar las llamadas del API de manera asíncrona, es decir, cambiar las acciones asociadas al robot en cada turno (los métodos son básicamente los mismos, añadiendo “set” delante de sus nombres), que en Robocode reciben el nombre de “tick”(relativo a un frame gráfico plasmado en el campo de batalla). Cuando visualizamos combates en Robocode, la máquina de simulación está sacando por pantalla múltiples frames gráficos estáticos de manera secuencial, durante cada uno de estos frames nuestro código puede retomar el control y decidir qué hacer con el robot, y dada la velocidad de los ordenadores actuales, esto nos permitirá ejecutar gran cantidad de instrucciones durante uno de estos frames o ticks.

En general, AdvancedRobot proporciona tres nuevas habilidades: Llevar a cabo múltiples movimientos simultáneamente, decidir la acción o la estrategia del robot a cada tick de reloj y definir y gestionar “custom events”.

En definitiva, nosotros lo que queremos hacer es un robot competitivo y que sea capaz de derrotar a los adversarios y para ello tendremos que tener en cuenta múltiples factores. Deberemos anticiparnos a las posiciones que los robots objetivos de nuestros disparos tendrán antes de que nos encaremos y vayamos hacia ellos(predictive targeting). También debemos tener presente en todo momento la detección y evasión de los disparos enemigos. Sería bueno asimismo basar nuestra estrategia en la cantidad de energía restante en cada momento.

Arquitectura Robocode:

Veamos brevemente un esquema de cómo sería la arquitectura de la máquina de simulación de Robocode :

Se podría ver esta máquina de simulación como un programa de simulación que toma versiones particulares de los robots durante el tiempo de ejecución, este conjunto de robots hace uso de una serie de librerías suministradas (API robocode.Robot). Físicamente cada robot es un hilo independiente de Java y el método run() contiene la lógica que será ejecutada por el hilo.

El hilo gestor de combates gestiona los robots, las armas, y la distribución espacial en el campo de batalla. En un turno típico, el hilo gestor de combates despierta a cada hilo de robot y espera a que el robot complete su turno. Este intervalo dura típicamente decenas de milisegundos (los robots más complejos tienden a usar solo 1 o 2 ms para estrategia y cálculos).

ESTRATEGIA INDIVIDUAL

Para realizar el robot de lucha individual tuvimos que emplear varias estrategias que conjuntamente nos dieran un robot efectivo. Hay varias incógnitas a resolver:

� Cuándo disparar � Con qué potencia � Dónde apuntar � A qué distancia colocarme � Cómo nos movemos

Para ello nuestro robot irá almacenando ciertos valores que le harán falta para

optimizar la elección de las incóginitas anteriores. Para ello almacenaremos las variables más actuales:

� La posición de nuestro robot (Point2D posicion )� La posición del robot enemigo (Point2D posicionEnemigo )� La distancia entre ambos (double distanciaEnemigo)� La velocidad del robot enemigo (double velocidadEnemigo)� El ángulo relativo entre nuestra posicion y la del contrario (double

anguloEnemigo)

También almacenaremos algunos valores del anterior “escaneo” del robot, esto es;

� La posición anterior de nuestro robot (Point2D posicionAnt )� La posición anterior del robot enemigo (Point2D posicionEnemigoAnt )

Estos valores se actualizan cada vez que detecta un robot, esto es, en el método onScannedRobot.

Además de estos valores relativos a la posición, tomaremos medidas de las energías ganadas y perdidas de ambos robots. Como estas medidas las emplearemos en calcular la distancia óptima a la que colocarnos respecto del adversario, cada una de estas variables será un array de tamaño el número de distancias muestreadas. Pero, ahora nos crea un dilema de cuántas distancias quiero como muestras, esto es, de la distancia máxima posible entre ambos robots (la diagonal del campo de batalla) ¿de qué distancias tomo valores? Si tomamos todas las posibles distancias no obtendríamos ninguna conclusión ya que, a parte de ser computacionalmente más costoso, las medidas tomadas en cada distancia serían muy pobres. Por lo cual, decidimos escoger distancias de tamaño 50. Entonces generaremos cuatro arrays:

private static double[] miEnergiaGanada, miEnergiaPerdida, enemigoEnergiaGanada, enemigoEnergiaPerdida; que tendrán de tamaño distanciaMaxima/50.

También iremos almacenando una variable que nos permitirá analizar el movimiento del adversario. La llamamos anguloMovimiento que promedia el ángulo que se mueve nuestro adversario entre dos escaneos. Para ello en onScannedRobot calculamos el ángulo que se ha movido respecto al anterior escaneo, y promediamos con

los valores anteriores. Debido a que el adversario puede haber cambiado de tipo de movimiento no tiene sentido hacer una media aritmética donde los valores actuales tengan la misma importancia que los valores muy antiguos. Para ello realizamos una “rolling average” que consiste en hacer un promedio tal que se tengan más en cuenta los valores actuales, pero sin tampoco olvidar los valores muy antiguos. Esta técnica se utiliza bastante en robocode y hemos decidido implementarla también nosotros. En nuestro programa la función se denomina media y hace lo siguiente:

primanprimadanuevaEntranactualmedia

+×+×=

donde:

• actual es la media actual la cual vamos a ponderar. • nuevaEntrada es el valor leído recientemente el cual vamos a incluir en la

media. • n es el número que representa la profundidad de la historia reciente. • prima es el peso o importancia de esta lectura, permite a una nueva entrada tener

mayor o menor efecto.

Esta función devolverá la media alterada por el nuevo valor de manera que “simula” guardar una lista con todas las lecturas con longitud n y devolviendo la media de los valores de esta lista. Hay que tener en cuenta que los valores antiguos nunca son olvidados, simplemente juegan un menor papel en el cálculo de la media actual.

Todas las variables anteriores serán estáticas ya que almacenaremos los valores de una ronda a otra.

Al comenzar el método run() en primer lugar nuestro robot definirá un margen respecto al campo de batalla, dicho margen marcará un límite del cual el robot no deberá salir. Sería como un campo dentro del campo de batalla. Esto lo hacemos para no topar con los bordes y perder por ello energía. Tomamos un valor de 20 que nos parece después de varias pruebas un buen valor. Además activa que el cañón y el radar se mantegan en la misma dirección mientras gira el robot con las funciones setAdjustGunForRobotTurn(true) y setAdjustRadarForGunTurn(true);

Dentro de este método run() después de inicializar el robot entraremos en un bucle infinito que controlará el movimiento básico de nuestro robot. Dentro de ese bucle lo q se hacemos es:

1. Definir la velocidad máxima de nuestro robot 2. Controlar que no nos salgamos del margen establecido 3. Movernos en la dirección y la distancia calculadas 4. Mover el radar si no acabamos de detectar un robot 5. Ejecutar todo lo anterior ya que fueron llamadas no bloqueantes y tenemos que

pasarle el control al robocode para que las ejecute (execute()).

Planteamos ahora las incógnitas nombradas al principio:

¿Cuando disparar?

Existen varias condiciones que podemos tener en cuenta a la hora de disparar. Las primeras que tenemos en cuenta son:

• Que el cañón esté parado (haya terminado de girar) getGunTurnRemaining()==0

• Que el cañón esté frio: getGunHeat() == 0

Pero estas condiciones son necesarias pero no estratégicas. Buscaremos entonces alguna condición que optimice el momento a disparar. Definimos entonces que nuestro robot dispará sólo cuando la distancia a nuestro adversario sea menor que 500, ya que una distancia mayor la consideraremos como muy lejana. Además consideraremos una condición en la que si mi energía es muy pequeña sólo disparará a distancias más cortas. La condición considerada será que además de las dos condiciones se cumpla:

(getEnergy() > 0.2 || distanciaEnemigo < 100)

Esto es, que cuando mi energía sea menor que 0.2 sólo disparará si mi distancia

al enemigo es menor de 100, así aseguramos que cuando estamos apunto de morir si disparamos, tener mayor probabilidad de acertar.

Para gestionar esto utilizaremos la posibilidad que nos dan los AdvancedRobot

de utilizar condiciones. Para ello añadimos un evento propio mediante addCustomEvent pasándole como parámetro una nueva condición donde su función test() controle estas condiciones. Cuando esta función test() devuelva true (esto es, todas las condiciones se han cumplido) se lanzará un CustomEvent y ejecutará el método onCustomEvent, que disparará setFireBullet con la potencia estimada (explicado a continuación). ¿Con qué potencia disparar?

Conservar la energía de nuestro robot es una buena manera de sobrevivir en una batalla. Hay muchas formas de conseguir esto, por ejemplo:

• Disparar con misiles de baja potencia a largas distancias. • No disparar nunca misiles con una potencia mayor a la necesaria para matar al

enemigo. • Reducir la potencia de los misiles (o no disparar) cuando nuestro robot tenga una

energía muy baja.

Escoger la correcta potencia de los misiles a disparar es una estrategia muy importante.

Lo primero, la probabilidad de acertar en el disparo está relacionada con la potencia del disparo. Lo segundo es que hay que tener en cuenta que el enemigo nos está disparando, entonces quizás la estrategia de mejor eficiencia de energía no es la mejor…

Podemos minimizar:

• La ganancia de energía por unidad de tiempo. • La energía ganada por unidad disparada.

Hay varios factores que limitan la potencia de tiro. En primer lugar esta potencia deberá estar comprendida entre 1 y 3. En segundo lugar, esta potencia la elegiremos según sea la energía del enemigo, la distancia a éste y mi propia energía, de la siguiente forma:

• Cuando el enemigo tenga muy poca energía no dispararemos con potencia superior que con aquella con la que pudiéramos derribarle. Al dispararle su nivel de energía bajará según:

Disminución energía = máx{4*pot, 4*pot + 2*(pot-1)}

Como el mínimo daño que le vamos a causar va a ser una disminución de energía de 4*pot entonces, la máxima potencia con la que deberíamos disparar es de: energía del enemigo / 4.

• La distancia hacia el enemigo también delimitará la potencia con la que disparar, ya que a mayor distancia no conviene disparar con gran potencia ya que nuestro disparo tardará más en llegar y le habrá dado tiempo a moverse, por lo tanto tendremos menos probabilidad de acertar.

Pero… ¿para una distancia dada cómo modelamos la potencia con la que disparar? Consideramos la máxima distancia entre los dos robots que será cuando cada uno esté en una esquina del campo en la diagonal mayor. Tomamos como una distancia de referencia la mitad de esta distancia máxima, por lo que la máxima potencia con la que deberíamos disparar estaría promediada por: (Dmax/2) / Distancia al enemigo.

• El último factor a considerar es la energía que le queda a mi robot. Será importante este factor cuando me quede poca energía ya que deberé de tener cuidado con los disparos que hagamos. Promediamos por el valor de mi energía.

Así, escogeremos la potencia de la bala con la que disparar como el mínimo de los factores anteriores.

¿A qué distancia colocarme?

La distancia a la que esté del robot enemigo determinará en gran medida los resultados de la batalla. Si nos colocamos muy lejos conseguiremos que nuestro enemigo falle más si nos movemos deprisa, pero también nosotros fallaremos más, por lo que procuraremos no disparar si estamos muy lejos. La distancia óptima a la que situarnos dependerá del robot adversario contra el que nos enfrentemos, por lo que la calculamos dinámicamente. Como tomamos valores de las energías ganadas y perdidas de ambos robots para una serie de distancias podemos definir una función de ventaja para cada punto donde definimos. Entonces para una de las distancias muestreadas promediamos la ventaja que obtenemos a partir de una relación entre nuestro beneficio (la energía que gano y la energía que pierde el otro) y nuestra penalización (la energía que perdemos y la que gana el otro). Esta relación tomara valor máximo 1 cuando no obtengamos nada de penalización, el valor -1 cuando no obtengamos nada de beneficio y 0 cuando obtengamos el mismo beneficio que penalización.

Por ello para calcular la distancia óptima a la que colocarme será evaluando la ventaja obtenida para cada una de las distancias. ¿Dónde apuntar?

Cada vez que ejecutamos onScannedRobot() deberemos apuntar nuestro cañón hacia el enemigo, teniendo en cuenta el ángulo de movimiento que vamos midiendo en cada escaneo para predecir el movimiento del robot adversario. Para ello, investigando en las diversas páginas sobre robocode descubrimos muchas estrategias y decidimos implementar una de ellas. Se trata de los “cañones virtuales”. Consiste en detectar el movimiento del robot, si se suele mover hacia la derecha o hacia la izquierda y en qué medida. Para ello tomamos 3 cañones virtuales, uno recto, otro a la izquierda y otro a la derecha. Definimos por tanto para cada cañón virtual una variable estática que determina un promedio del ángulo de error del cañón. Esto es, cada vez que disparamos, se genera una nueva condición cuya función de test() controlará cuando el tiro sea fallido. En tal caso, calcula la diferencia entre el ángulo donde está el enemigo (dónde debía nuestro robot haber disparado) y el ángulo donde estaba el misil cuando mi distancia a éste es aproximadamente mi distancia al enemigo. Esta diferencia de ángulos me dará el ángulo de error. Este ángulo de error se promediará con los anteriores del cañón correspondiente mediante la función media explicada anteriormente.

Así, para cada cañón virtual disponemos de un ángulo donde se estima que se suele encontrar el robot adversario cuando se mueve en esa dirección. Por lo que a la hora de apuntar nuestro cañón, lo primero es elegir el cañón virtual correspondiente en función del ángulo movido:

� cañon virtual Izquierdo si anguloMovimiento < - 0.3 � cañon virtual Derecho si anguloMovimiento > 0.3 � cañon virtual Central si anguloMovimiento está en (-0.3,0.3)

Al ángulo en que se encuentra el robot enemigo le sumaremos el valor promedio del ángulo de error cometido para el cañón correspondiente. ¿Cómo nos movemos?

Al arrancar nuestro robot con el método run() inicializamos una variable llamada unidadMov la cual indica la máxima velocidad que podemos alcanzar y por tanto la distancia que nos vamos moviendo. Este valor será aleatorio y variará cuando un misil nos alcance, esto es, en el método onHitByBullet. Además variaremos el sentido de nuestro robot (+1, -1) también aleatoriamente. Esto permitirá que el robot contrario pueda analizar nuestro movimiento, y, sobre todo, cuando consiga dispararnos.

SOLUCIÓN - IRCBot

Pero justamente para poder probar nuestro robot generamos varios casos para ver como reaccionaba éste. Observamos que para un robot que cambiara constantemente de sentido, nuestro IRCeitor era poco eficiente. Para probarlo generamos el IRCBot, un robot con estrategia diferente al IRCeitor, capaz de ganarle la mayoría de veces. Su estrategia la explicaremos mostrando los comentarios en el código que adjuntamos:

package ircPackage; import robocode.*;

import java.awt.Color; import java.util.Enumeration; import java.util.Hashtable;

/* * IRCBot.java * El movimiento de este robot impide que se choque contra los muros, de * manera que no pierde energía en ese sentido. * Asimismo evita las balas y los enemigos. Todo ello es posible gracias a la * "Evasion", para la que hay implementada una clase. Dependiendo del tipo de * evasion en el que nos encontremos en cada momento(que depende de * los eventos y situaciones que se van dando en el juego) así será el * movimiento del robot. * * Los potencia de los disparos se lleva a cabo de manera aleatoria pero * siempre a partir de nuestra energía y de la distancia a la que se * encuentre el enemigo al que pretendemos disparar. * * Targeting: si no tenemos ya fijado un enemigo, se fija como enemigo el * primero que detecte nuestro radar y otra serie de factores. se calculará de * manera ciertamente aleatoria la orientación del cañón para el disparo, * pero teniendo en cuenta el movimiento de dicho oponente, para así poder * adelantarnos a él y darle de lleno. * */

public class IRCBot extends AdvancedRobot{ /* Para fijar la velocidad adecuada en cada momento */ static double e_velocity; /* Objetivo detectado al realizar el scaneo con el radar */ ScannedRobotEvent target; /* Para saber si merece la pena una confrontacion directa con un enemigo */ double energy; /* Objeto de tipo Evasor, que nos permitirá escapar de los distintos peligros dependiendo del estado en el que se encuentre */ Evasor evasor; /* Direccion de avance, +1 hacia donde ibamos, -1 en sentido contrario */ int dir; /* Lista de objetivos */ Hashtable targets; /* Variable de tiempo y de orientacion que nos ayudarán a no chocarnos contra los muros */ double tiempoChoque; double orientacionChoque; /* Para saber cuanto tiempo llevamos sin ver a un enemigo: */ double lastsighted;

/* * En este método se implementarán las operaciones que tiene que hacer el * robot una vez por instancia (parte de código fuera del do-while) * y las operaciones que se realizarán en cada turno del robot * (parte de código contenida dentro del do-while). */

public void run(){ inicializacion();

//Del disparo se encargará el método onScannedRobot, aqui lo unico que //hacemos es definir el movimiento en caso de que queden más robots

// o marcarnos un bailecito en caso de que no queden más robots, // es decir, que hayamos ganado. do{

if(getOthers() > 0) mover(); else bailecito(); execute(); } while(true); }

/* * Constructor de IRCBot donde se inicializan algunos de sus atributos: */

public IRCBot(){

target = null; /*Inicializamos un objeto de tipo Evasor, que nos gestionará la evasión de las situaciones de peligro.*/ evasor = new Evasor(); dir = 1;

targets = new Hashtable(); orientacionChoque = 0.0; }

/* * Método para inicializar el modo de operacion del robot */

public void inicializacion(){ setColors(Color.yellow, Color.blue, Color.black); //Hacemos que tanto el cañón como el radar giren de manera independiente al robot: setAdjustGunForRobotTurn(true); setAdjustRadarForGunTurn(true); //Y hacemos un scaneo inicial setTurnRadarRight(360); }

/* * El movimiento de este robot funciona de la siguiente manera: * En primer lugar hacemos un movimiento dependiendo del tipo de evasion en * la que nos encontremos. * En segundo lugar tratamos de evitar los muros * Y también atendemos al caso en el que estemos solos contra otro robot. */

public void mover(){ //No haremos nada hasta que fijemos un objetivo: if(target == null) return;

//Si tenemos un objetivo con el que cebarnos.... if(getOthers() > 1){ setTurnRadarRight(360); circleGrav(); }

else{ setMaxVelocity(Math.random() + 7); setTurnRight(target.getBearing() + 90 + -((double)dir * evasor.ang)); }

//Distancias a los muros que limitan el escenario de combate, que los

//tenemos que evitar ya que un choque contra estos nos resta energía //vital:

double N = getBattleFieldHeight() - getY(); double S = getY(); double E = getBattleFieldWidth() - getX(); double W = getX();

//No nos vamos a chocar con los muros, ya que cuando estemos a menos //de una cierta distancia de estos daremos media vuelta.

if(N < 70 && getHeading() > 270 && getHeading() < 360)

dir = -1;else

if(N < 70 && getHeading() > 0.0 && getHeading() < 90) dir = -1;

else //Aquí estamos en el caso de que estemos en zona próxima al muro // norte pero nuestro sentido de avance es tal que nos aleja de //este peligro. if(N < 70) dir = 1;

if(S < 70 && getHeading() > 90 && getHeading() < 270) dir = -1;

else if(S < 70) dir = 1;

if(E < 70 && getHeading() > 0.0 && getHeading() < 180) dir = -1;

else if(E < 70) dir = 1;

if(W < 70 && getHeading() > 180 && getHeading() < 360) dir = -1;

else if(W < 70) dir = 1;

//Si estamos en las zonas de peligro: if(N < 70 || S < 70 || E < 70 || W < 70){ evasor.movetime = getTime(); evasor.sub_tipoEvasion = 1; orientacionChoque = -dir * 10 - 90; tiempoChoque = getTime(); }

else{ orientacionChoque = 0.0; }

if(Evasor.tipoEvasion == 6){

if((double)getTime() - Evasor.type6time > 60){ Evasor.tipoEvasion++; Evasor.type6time = getTime();

if(Evasor.tipoEvasion > 3){ Evasor.tipoEvasion = 1; }

}}

if(getOthers() > 1){ circleGrav(); }

//Solo hay un enemigo: else{

switch(Evasor.tipoEvasion){

case 1: mov1(); break; case 2: mov2(); break; case 3: mov3(); break; case 4: mov1(); mov2(); mov3(); break; default: mov1(); }

}}

/* * Movimiento que seguimos cuando tenemos más de un robot acechándonos: */

public void circleGrav() { double orientacion = 10000; double closest = 10000;

//Buscamos el enemigo más cercano: for(Enumeration e = targets.elements(); e.hasMoreElements();){ Enemigo en = (Enemigo)e.nextElement(); if((en.estaVivo = true) && en.distancia < closest) { closest = en.distancia; //Establecemos una futura orientacion del robot enemigo aleatoria, //basándonos en la que teníamos almacenada suya: orientacion = en.orientacion + 180 + (Math.random() * 60 - 30); if(getOthers() < 4) orientacion -= 90; } }

if(getOthers() >= 4) orientacion += orientacionChoque;

if((double)getTime() - tiempoChoque > 10) dir = 1;

setAhead(dir * 100); setTurnRight(NormalRelativeAngle(orientacion)); }

/* * A partir de aqui definimos tres tipos de movimiento distintos para * cuando solo quede otro robot, para ser más efectivos en ese caso. * Con esto lo que hacemos es que al adversario que está contra nosotros * le resulte muy complicado preveernos */

/* * En este primer movimiento, si ya se ha acabado el movimiento * anteriormente definido,cambiamos el sentido y angulo de nuestro * movimiento, así como el tiempo que va a durar este. */

public void mov1(){ if((double)getTime() - evasor.movetime > evasor.time){ dir *= -1; evasor.movetime = getTime();

evasor.ang = Math.random() * 25; evasor.time = Math.random() * 7 + 15; }

setAhead(dir * 50); }

/* * Definimos otro movimiento de manera completamente distinta: * Utilizando incluso un subtipo del evasor. */

public void mov2(){ if(target.getDistance() < 400) evasor.time = Math.min(target.getDistance() / 12, 24); else evasor.time = 28;

//Como poco el tiempo del siguiente movimiento será 12 ticks evasor.time = Math.max(evasor.time, 12);

if((double)getTime() - evasor.movetime > evasor.time){ evasor.sub_tipoEvasion *= -1; evasor.movetime = getTime(); evasor.ang = Math.random() * 20; }

//A su vez dentro de este movimiento tenemos dos variantes: ir hacia delante o hacia atrás if(evasor.sub_tipoEvasion == 1) setAhead(dir * 100); else setAhead(dir * -5); }

public void mov3(){ evasor.ahead = (int)(Math.random() * 30 + 30); evasor.ang = Math.random() * 15; }

/* * Cuando nuestro robot muere ahí no se queda la cosa, * aprende un poco y pone el estado en el que ha de comenzar el evasor * en la siguiente ronda. */

public void onDeath(DeathEvent e){ switch(Evasor.tipoEvasion){ case 1: Evasor.tipoEvasion = 2; break; case 2: Evasor.tipoEvasion = (int)(Math.random()*3.9900000000000002)+3; break; default: Evasor.tipoEvasion = 1; }

}

/* * En este método se implementan las acciones a realizar cuando nos alcance * una bala enemiga */

public void onHitByBullet(HitByBulletEvent e){ if(Evasor.tipoEvasion == 5){ Evasor.tipoEvasion++;

if(Evasor.tipoEvasion > 3){ Evasor.tipoEvasion = 1; }

}}

/* * Este método se ejecuta cuando muere uno de los robots de la batalla, * evento que es recogido por RobotDeathEvent. * De entre los robots que teníamos como posibles objetivos, pasamos a * considerar como muerto al robot que acaba de morir, y si dicho robot * coincidía con el objetivo actual, a través de decir que lastsighted es * cero dejará de ser nuestro objetivo. */

public void onRobotDeath(RobotDeathEvent e){

Enemigo en = (Enemigo)targets.get(e.getName()); en.estaVivo = false; targets.put(e.getName(), en); if(e.getName() == target.getName()) lastsighted = 0.0; }

/* * En este método se implememtan aquellas acciones que hay que realizar * cuando al paso del scanner se detecta la presencia de un robot, no * detectado antes o sí */

public void onScannedRobot(ScannedRobotEvent e){

Enemigo en;

//Vemos si el robot detectado ya lo habíamos visto antes o no if(targets.containsKey(e.getName())){ en = (Enemigo)targets.get(e.getName()); }

else{ en = new Enemigo(); targets.put(e.getName(), en); }

en.nombre = e.getName(); en.distancia = e.getDistance(); en.orientacion = e.getBearing(); en.estaVivo = true;//si lo hemos visto...

if(Evasor.tipoEvasion == 3){ energy -= e.getEnergy(); if(energy <= (double)3 && energy > 0.0) setAhead((double)dir * evasor.ahead); energy = e.getEnergy(); }

/* Si se cumple una o más de estas cosas: - Actualmente no tenemos objetivo(porque se haya muerto el que teníamos p ej) - El objetivo es el mismo que acabamos de detectar - El robot recién detectado está mucho más cerca del que tenemos fijado como enemigo prioritario - Hace más de ocho ticks que no vemos un robot entonces vamos a por el. */ if(target == null || e.getName() == target.getName() || e.getDistance() < target.getDistance() - 50 || (double)getTime() - lastsighted > 8){

target = e; lastsighted = getTime(); //comienza a contar de nuevo el tiempo desde el que vimos al ultimo enemigo.

double abs_orientacion = e.getBearingRadians() + getHeadingRadians(); setTurnRadarRightRadians(Math.asin(Math.sin(abs_orientacion - getRadarHeadingRadians())));

// Elegimos la potencia de nuestro disparo en función de la energía // que nos quede y de la distancia a la que se encuentra nuestro // enemigo. double firepower = Math.min(getEnergy(), 99 / e.getDistance() + Math.random() * (double)2); double bullet = 20 - (double)3 * firepower;

e_velocity = e_velocity * 0.98999999999999999 + target.getVelocity() * 0.01; double velocity = e_velocity * Math.random();

if(Math.random() > 0.5 || getOthers() > 1) velocity = e.getVelocity();

if(getOthers() > 1 && Math.random() > 0.5) velocity = 0.0;

//Enfocamos el arma y disparamos: setTurnGunRightRadians(Math.asin(Math.sin((abs_orientacion - getGunHeadingRadians()) + Math.asin((velocity / bullet) * Math.sin(target.getHeadingRadians() - abs_orientacion)))));

setFire(firepower); }

mover(); }

/* * Movimientos para celebrar la victoria: */

public void bailecito(){ setTurnLeft(36000); setTurnGunRight(36000); setAhead(50); setBack(50); }

/* * Método para que todos los ángulos que utilicemos estén entre -180 y 180 * grados. */

public double NormalRelativeAngle(double angle){

if (angle > -180 && angle <= 180) return angle; while (angle <= -180) angle += 360; while (angle > 180) angle -= 360; return angle; }

}

ESTRATEGIA EN EQUIPO

Una de las opciones que ha incluido recientemente Robocode ha sido el juego por equipos. En el modo de juego por equipos, mejor que diseñar un solo robot se diseña un equipo completo de robots diferentes, donde, para cada equipo, se pueden usar todas las clases java que sean necesarias o tener muchos robots instancia de la misma clase.

Algunas de las opciones que ofrece el juego por equipos son:

� Podemos designar a un robot como el líder del equipo(el primero que añadamos al equipo). Los líderes tienen energía extra al comienzo de la batalla, 200 unidades en total. Si el líder del equipo muere, todos los miembros del equipo sufren un descenso de 30 unidades de energía cada uno.

� Los robots pertenecientes a un equipo tienen la capacidad de enviarse mensajes unos a otros.

Estos robots pertenecientes a un equipo extenderán de la clase TeamRobot, que

a su vez es una subclase de la clase AdvancedRobot, pudiendo además implementar la interfaz Droid.

Un Droid no tiene radar y por tanto no puede hacer un escaneo. Sin embargo,

todo Droid tiene 20 puntos extra de vida respecto a aquellos robots que no son Droid.

Para la implementación de nuestros Robots de equipo hemos elegido realizar equipos de robots de la misma índole, es decir, el robot líder (que es designado simplemente por ser la primera instancia que se incluye al equipo) y el resto de robots de nuestro equipo pertenecerán a la misma clase Java. Hemos tomado esta decisión al considerar que puede ser mejor que todos los robots tengan inteligencia propia (donde los radares juegan un papel fundamental) a que sólo uno de ellos, el líder, escanee el terreno y los otros se limiten a moverse y disparar.

De esta manera, cada uno de los robots será capaz de determinar donde están sus enemigos (lo que servirá para moverse de manera inteligente), cuál es el enemigo más débil y dónde están sus compañeros. A todo esto le añadiremos una cierta coordinación entre los miembros del equipo que hará que su táctica de combate sea más eficaz.

En la implementación y búsqueda de buenos robots de equipo, hemos implementado dos tipos de robots de combate en grupo, uno de ellos, IRCTeamBot es una derivación del IRCBot, que ha sufrido algunas modificaciones para soportar el modo de combate en grupo, y el otro Teamy, robot de equipo basado en una máquina de estados. Pasamos a explicar el funcionamiento y modo de operación de estos dos robots:

IRCTeamBot:

Cuando nos enfrentamos al diseño de un robot de lucha y estrategia en grupo debemos tener en cuenta dos tipos de acciones, las individuales y las colectivas. Dentro de las individuales podemos considerar el movimiento del robot, la búsqueda de enemigos o la potencia de disparo. Dentro de las colectivas tenemos que atender a cosas tales como saber la posición de nuestros compañeros para no atacarles a ellos o saber cuáles son los enemigos del grupo para ir acabando con ellos.

En el aspecto individual, al ser este robot, como hemos dicho, un derivado de IRCBot, su comportamiento será similar al de aquel.

En el movimiento de IRCTeamBot (moving) intervienen varias cosas:

� Tendremos que evitar confrontaciones directas con los enemigos. � Tendremos que evitar chocarnos contra los límites del escenario de combate,

evento que como sabemos también resta energía. � Tendremos que evitar que nos alcancen las balas enemigas. � Tendremos que evitar chocarnos contra robots de nuestro equipo, evento que

sería fatal para los intereses del grupo.

Para el control de todo este tipo de situaciones se llevan a cabo numerosas acciones, que pasamos a detallar:

En primer lugar hacemos uso de una clase Evasor, que ya utilizábamos en

nuestro IRCBot y que sirve básicamente para que, dependiendo de la situación del juego, escapemos de una forma o de otra de situaciones de peligro y consecuentemente nuestro comportamiento sea menos predecible. Así, habrá asociado un objeto de este tipo a cada robot del grupo, objeto que será de carácter estático, para así poder cambiar el tipo de movimiento con el que nos zafamos del peligro, de manera que lo que aprendan nuestros enemigos acerca de nuestro movimiento les servirá de más bien poco en posteriores rondas.

El tipo de movimiento de huida de los peligros dependerá del valor en el que se

encuentre el atributo tipoEvasion del objeto evasor, que cambiará en situaciones tales como cuando nos enfrentamos a un robot solo (de manera que este se vuelva un poco loco al intentar darnos), cuando morimos(para movernos de forma distinta a la siguiente ronda) o cuando chocamos con otro robot por ejemplo.

Otra parte importante en el control de nuestros movimientos es la tendencia “anti gravitatoria” que seguimos, tomando como puntos de atracción a los enemigos. Esta tendencia viene implementada en la función circleGrav() y está basada en que cada vez que un robot va a moverse, si hay más de un enemigo, se calcula las distancias a todos los enemigos, eligiendo el más cercano, y en función de su movimiento calcula otro para que nos alejemos de la influencia de este enemigo más cercano.

En el caso de que hubiera solamente un enemigo, lo que hacemos es una

sucesión de movimientos distintos, que nos van acercando a dicho contrincante, y que dada su naturaleza y continua variación, hacen de nuestro IRCTeamBot un gran robot cuerpo a cuerpo(característica que ya tenía IRCBot).

En cuanto a la evasión del choque contra los muros, dentro de la función mover() tenemos un fragmento de código dedicado a ello, en el que calculamos la distancia a las paredes y si estamos demasiado cerca de alguna(menos de 70 píxeles) y nuestro movimiento nos conduce a la colisión, daremos media vuelta.

En cuanto a la evasión de las balas enemigas, este robot no utiliza gran

sofisticación (como podría ser la empleada por nuestro IRCeitor, en el que se detectan los cambios en la energía de los enemigos para determinar quién nos dispara y evitarlo), simplemente cambia su modus operandi a la hora de moverse para que el robot enemigo tenga más difícil la tarea de apuntar y darnos.

Para evitar el choque con miembros de nuestra cuadrilla hay dos fragmentos de

código dedicados a ello, el primero de ellos se encuentra en la función onScannedRobot,en la que al detectar otro robot, comprobamos si es un compañero, en cuyo caso calculamos la distancia a la que se encuentra de nosotros y si dicha distancia supone un riesgo de colisión inminente giramos nuestro sentido de avance para evitar ese choque. La segunda medida tomada al respecto se encuentra en el método onHitRobot, que se ejecuta cuando se produce un onHitRobotEvent, es decir, cuando ya nos hemos chocado contra otro robot. Esta situación puede producirse ya que el movimiento de un robot está sujeto a varios factores y puede que estos lleven a una colisión antes de que podamos detectar su posibilidad y evitarlo. En este caso, lo que se hace es generar un movimiento aleatorio, de manera que lo más probable sea que cada robot, al calcularlo por separado, lo haga de una manera distinta, y así al detectar los dos robots el choque contra un compañero, los dos calculan por separado un movimiento aleatorio, que al llevarlo a cabo lo más probable es que les aleje.

Como apunte, decir que en este método onHitRobot hay dos posibilidades, que

el robot contra el que chocamos sea amigo o sea enemigo, el primer caso ya lo hemos explicado, mientras que en el segundo estamos ante una estupenda oportunidad de disparar con todas nuestras fuerzas al enemigo, que al estar pegado a nosotros recibiría un gran impacto. Mientras tanto nosotros también estamos perdiendo energía al estar en contacto con él, pero al estarle disparando con la máxima potencia, recuperaremos parte de esa energía y lo más probable es que aniquilemos a ese enemigo a costa de descender un poco nuestra energía vital.

Pasemos a explicar cómo elegimos nuestros objetivos (targeting).

Al haber elegido la opción de implementar equipos en los que todos los robots tienen bastante autonomía guardando cierta coordinación para ser más letales, cada uno de los robots estará continuamente escaneando en busca de nuevos enemigos o en busca de actualizar la información sobre los ya existentes.

Los robots enemigos se almacenarán en una estructura de tipo Hashtable, en donde como sabemos se pueden añadir, acceder, modificar y borrar elementos de su interior. Está estructura será de carácter estático para la clase IRCTeamBot, de manera que todas las instancias de esta clase la compartirán, es decir, en todo momento todos los robots de nuestro equipo considerarán los mismos enemigos.

Todos los robots, al detectar un robot ajeno (onScannedRobotEvent), lo que harán será consultar si es compañero de escuadra o no. En caso de no pertenecer al equipo, se consulta la tabla Hashtable, de nombre targets, para ver si dicho robot está considerado como enemigo. Pueden ocurrir dos cosas:

- que esté registrado como enemigo anteriormente, en cuyo caso se actualizan sus parámetros asociados, para que siempre haya una información actualizada de los enemigos.

- que no se encuentre en targets, en cuyo caso se creará una nueva instancia de la clase Enemigo y se introducirá en la tabla con los parámetros asociados actuales.

Vemos como todos los robots del grupo contribuyen de esta manera a que la

tabla de enemigos esté siempre actualizada con las últimas posiciones y parámetros de los mismos. Este comportamiento colectivo se traduce un control exhaustivo de los enemigos para un ataque más eficaz.

De la misma manera, cuando muere un enemigo, dado que se ejecutará el código presente en el método onRobotDeath, provocado por la aparición de un RobotDeathEvent, se comprobará en este método si el robot fallecido es un enemigo(no es un compañero) en cuyo caso lo eliminamos de la tabla de enemigos. Esta eliminación hará que de aquí en adelante la búsqueda de enemigos para disparar sea más rápida, al no tener que pararnos a comprobar si está vivo o no, simplemente no está.

Si el robot fallecido fuese un compañero lo que hacemos es decrementar el entero que lleva la cuenta de robots en mi equipo, para así conocer aspectos del juego durante el mismo que van en función del número de robots que queden en el escenario(como por ejemplo determinar cuando nuestro equipo ha alcanzado la victoria o incluso para funciones propias).

¿Cómo llevamos a cabo los disparos? (firing):

Cuando, al paso de nuestro scanner detectamos un robot enemigo, hay que dispararle, para ello el cálculo del ángulo en el que tenemos que poner nuestro cañón y la potencia del disparo son calculados de la misma forma que en IRCBot, es decir, el ángulo de cañón lo calculamos a partir de la orientación, dirección de avance y velocidad tanto enemigas como propias, y la potencia de disparo a partir de las energías y las distancias.

Finalmente, pueden ocurrir dos cosas, o que el robot muera y termine muriendo el equipo entero o que salgamos victoriosos. En el primero de los casos, se transmitirá algo de información a la siguiente ronda, que en realidad es más bien un cambio aleatorio en ciertos parámetros del movimiento para ser menos predecibles. En el segundo de los casos se invocará nuestro método “bailecito()” en el que haremos una serie de movimientos indicando nuestra alegría por la victoria.

Código de IRCTeamBot:

package ircPackage; import robocode.*;

import java.awt.Color; import java.util.*; import java.io.*; /** * IRCTeamBot: Robot que extiende de TeamRobot y que por tanto está * implementado con la intención de que pertenezca a un grupo de ataque de * robots de la misma clase. */

public class IRCTeamBot extends TeamRobot{

static double e_velocity; double energy; int dir; int numAmigosVivos;

//Hacemos el robot objetivo estático para que lo compartan todas las instancias static ScannedRobotEvent target; Evasor evasor; static Hashtable targets; //Tabla en la que almacenamos todos los objetivos del grupo. //Tambien la hacemos estatica para que los robots del grupo compartan objetivos

double tiempoChoque; double orientacionChoque; double ultimaVezVisto;

public void run(){

inicializacion(); do{

if(getOthers() > numAmigosVivos){ setTurnRadarRight(360); mover(); }

else bailecito();

execute(); } while(true); }

public IRCTeamBot(){

target = null; targets = new Hashtable();

//Inicializamos un objeto de tipo Evasor, que nos gestionará la evasión de las situaciones de peligro. evasor = new Evasor(); dir = 1;

numAmigosVivos=3; ultimaVezVisto = 0.0; orientacionChoque = 0.0; }

public void inicializacion(){

setColors(Color.yellow, Color.blue, Color.black); setAdjustGunForRobotTurn(true); setAdjustRadarForGunTurn(true); setTurnRadarRight(360); }

/* * En primer lugar hacemos un movimiento dependiendo del tipo de evasion * en la que nos encontremos * En segundo lugar tratamos de evitar los muros * Y también atendemos al caso en el que estemos solos contra otro robot. */

public void mover(){

//No haremos nada hasta que fijemos un objetivo: if(target==null) return;

if(getOthers() > numAmigosVivos + 1){ setTurnRadarRight(360); circleGrav(); }

else{ setMaxVelocity(Math.random() + 7); setTurnRight(IRCTeamBot.target.getBearing() + 90 + -((double)dir * evasor.ang)); }

//Distancias a los muros que limitan el escenario de combate, que los tenemos que evitar ya que un choque contra estos nos resta energía vital: double N = getBattleFieldHeight() - getY(); double S = getY(); double E = getBattleFieldWidth() - getX(); double W = getX();

//No nos vamos a chocar con los muros, ya que cuando estemos a menos de una cierta distancia de estos daremos media vuelta. if(N < 70 && getHeading() > 270 && getHeading() < 360) dir = -1;

else if(N < 70 && getHeading() > 0.0 && getHeading() < 90) dir = -1;

else //Aquí estamos en el caso de que estemos en zona próxima al muro norte pero nuestro sentido de avance es tal que nos aleja de este peligro. if(N < 70) dir = 1;

if(S < 70 && getHeading() > 90 && getHeading() < 270) dir = -1;

else if(S < 70) dir = 1;

if(E < 70 && getHeading() > 0.0 && getHeading() < 180) dir = -1;

else if(E < 70) dir = 1;

if(W < 70 && getHeading() > 180 && getHeading() < 360) dir = -1;

else if(W < 70) dir = 1;

//Si estamos en las zonas de peligro: if(N < 70 || S < 70 || E < 70 || W < 70){ evasor.movetime = getTime(); evasor.sub_tipoEvasion = 1; orientacionChoque = -dir * 10 - 90; tiempoChoque = getTime(); }

else{ orientacionChoque = 0.0; }

if(getOthers() > numAmigosVivos+1){ circleGrav(); }

else{ switch(Evasor.tipoEvasion){ case 1: mov1(); break; case 2: mov2(); break; case 3: mov3(); break; case 4: mov1(); mov2(); mov3(); break; default: mov1(); }

}}

/* Movimiento que seguimos cuando tenemos más de un robot acechándonos: */

public void circleGrav() {

double orientacion = 10000; double closest = 10000;

for(Enumeration e = targets.elements(); e.hasMoreElements();){

Enemigo en = (Enemigo)e.nextElement(); //Calculamos nuestra orientacion de giro en funcion del enemigo mas cercano: if((en.estaVivo = true) && en.distancia < closest) { closest = en.distancia;

orientacion = en.orientacion + 180 + (Math.random() * 60 - 30);

if(getOthers() < 4 + numAmigosVivos) orientacion -= 90; }

}

if(getOthers() >= 4 + numAmigosVivos) orientacion += orientacionChoque;

if((double)getTime() - tiempoChoque > 10) dir = 1;

setAhead(dir * 100); //Metemos esa aleatoriedad en el giro porque sino miembros del mismo equipo tienden a agruparse:

setTurnRight(NormalRelativeAngle(orientacion + Math.random()*20)); }

/* En este primer movimiento, si ya se ha acabado el movimiento * anteriormente definido, * cambiamos el sentido y angulo de nuestro movimiento, así como el tiempo * que va a durar este. */

public void mov1(){

if((double)getTime() - evasor.movetime > evasor.time){ dir *= -1; evasor.movetime = getTime(); evasor.ang = Math.random() * 25; evasor.time = Math.random() * 7 + 15; }

setAhead(dir * 50); }

/* Definimos otro movimiento de manera completamente distinta: * Utilizando incluso un subtipo del evasor. */

public void mov2(){

if(target.getDistance() < 400) evasor.time = Math.min(target.getDistance() / 12, 24); else evasor.time = 28;

//Como poco el tiempo del siguiente movimiento será 12 ticks evasor.time = Math.max(evasor.time, 12);

if((double)getTime() - evasor.movetime > evasor.time){ evasor.sub_tipoEvasion *= -1; evasor.movetime = getTime(); evasor.ang = Math.random() * 20; }

//A su vez dentro de este movimiento tenemos dos variantes: ir hacia delante o hacia atrás if(evasor.sub_tipoEvasion == 1) setAhead(dir * 100); else setAhead(dir * -5); }

public void mov3(){

evasor.ahead = (int)(Math.random() * 30 + 30); evasor.ang = Math.random() * 15; }

/* Al morir no lo hacemos en balde, segun el tipo de evasion que * tuvieramos, * generamos otra parala ronda siguiente, ¿No se trata de desconcertar?*/

public void onDeath(DeathEvent e){

switch(Evasor.tipoEvasion){ case 1: Evasor.tipoEvasion = 2; break; case 2: Evasor.tipoEvasion=(int)(Math.random()*3.9900000000000002) + 3; break; default:

Evasor.tipoEvasion = 1; }

}

/* Si ha muerto un enemigo dejamos de considerarle, y si ha muerto un amigo tambien hay que tenerlo en cuenta para saber cuando hemos ganado y cuando no*/

public void onRobotDeath(RobotDeathEvent e){

String nombreFallecido = e.getName();

if(!isTeammate(nombreFallecido)){ Enemigo en = (Enemigo)targets.get(nombreFallecido); if(target.getName().equals(nombreFallecido)) ultimaVezVisto = 0.0;

targets.remove(nombreFallecido); }

else numAmigosVivos--; }

/* * Si chocamos con un robot de nuestro propio equipo nos movemos de * inmediato con un movimiento aleatorio, de manera que si el otro * también detecta que se ha chocado con nosotros se moverá, pero al ser * aleatorio lo más probable es que lo haga de otra forma y así podamos * separarnos. */

public void onHitRobot(HitRobotEvent e) {

if(isTeammate(e.getName())){ double tpRnd = Math.random() * 10; int rndInt = (int) Math.ceil(tpRnd); tpRnd = tpRnd % 3; switch (rndInt) { case 0: turnLeft(90-e.getBearing()); back(100); break; case 1: turnRight(90-e.getBearing()); ahead(100); break; case 2: turnLeft(90); back(200); }

}else{

setTurnGunRightRadians(Math.asin(Math.sin(e.getBearingRadians() + getHeadingRadians() - getGunHeadingRadians()))); setFire(3);//Si nos chocamos con un enemigo le freimos }

}

/* En este método se implememtan aquellas acciones que hay que realizar * cuando al paso del scanner se detecta la presencia de un robot, no * detectado antes o sí */

public void onScannedRobot(ScannedRobotEvent e){

Enemigo en; double abs_orientacion = e.getBearing() + getHeading(); double enemyX = getX() + e.getDistance() * Math.sin(Math.toRadians(abs_orientacion)); double enemyY = getY() + e.getDistance() * Math.cos(Math.toRadians(abs_orientacion)); double dx = enemyX - getX();

double dy = enemyY - getY();

if(!isTeammate(e.getName())){

//Vemos si el robot detectado ya lo habíamos visto antes o no if(targets.containsKey(e.getName())){ en = (Enemigo)targets.get(e.getName()); }

else{ en = new Enemigo(); en.nombre = e.getName(); en.distancia = e.getDistance(); en.orientacion = e.getBearing(); en.estaVivo = true; targets.put(e.getName(), en); }

en.nombre = e.getName(); en.distancia = e.getDistance(); en.orientacion = e.getBearing(); en.estaVivo = true;

if(target==null || e.getName().equals(target.getName()) || e.getDistance() < target.getDistance() - 50 || (double)getTime() - ultimaVezVisto > 8){

target = e;

ultimaVezVisto = getTime(); //comienza a contar de nuevo el tiempo desde el que vimos al ultimo enemigo. double theta = Math.toDegrees(Math.atan2(dx,dy));

setTurnRadarRight(NormalRelativeAngle(abs_orientacion - getRadarHeading()));

double firepower = Math.min(getEnergy(), 99/e.getDistance() + Math.random() * (double)2); double bullet = 20 - (double)3 * firepower; e_velocity = e_velocity * 0.98999999999999999 + e.getVelocity() * 0.01; double velocity = e_velocity * Math.random();

if(getOthers() > (numAmigosVivos+1)) velocity = e.getVelocity();

//Si el robot esta quieto le disparamos y punto: if(e.getVelocity()==0) setTurnGunRight(NormalRelativeAngle(theta - getGunHeading())); else setTurnGunRight(NormalRelativeAngle(theta - getGunHeading()) + Math.asin((velocity / bullet) * Math.sin(e.getHeadingRadians())));

setFire(firepower); }

}//Fin del if isTeammate else{

//Si nos estamos acercando mucho a un compañero cambiamos de direccion: double distanciaComp = e.getDistance(); if(distanciaComp<100){ setTurnLeft(90-e.getBearing()); }

//y vamos en busca de charlies setTurnRadarRight(360); }

mover(); }

public void bailecito(){

setTurnLeft(36000); setTurnGunRight(36000); setAhead(50); setBack(50); }

public double NormalRelativeAngle(double angle){

if (angle > -180 && angle <= 180) return angle; while (angle <= -180) angle += 360; while (angle > 180) angle -= 360; return angle; }

}

Teamy:

En busca de un mejor robot de grupo también se ha implementado este otro robot de simpático nombre. Su funcionamiento y manera de hacer las cosas es diferente al anterior y éste no es ninguna derivación / adaptación de ningún otro robot.

El principal atractivo de este robot es su implementación a través de una sencilla máquina de estados y su mecanismo de puntería y disparo, sobre todo ante enemigos cuasi estáticos o de movimientos más lineales, algo que no es ni mucho menos una minusvalía ya que multitud de robots se implementan para llevar a cabo un movimiento de dicha índole.

Antes de pasar a explicar la máquina de estados y las acciones llevadas a cabo en cada uno de ellos vamos a comentar algunos de los atributos de este robot:

En primer lugar vemos que, al igual que en el IRCTeamBot, contamos con una Hashtable enemigos donde se almacenan los enemigos que los robots de nuestro equipo van captando, también aquí es estática para que todos los miembros del grupo vean y consideren a los mismos enemigos.

A continuación vemos enemigoActual, que contiene la estructura de tipo Enemigo sobre la que estamos abriendo fuego, así como su nombre, nombreEn, quealmacenamos aparte para no generar accesos a null cuando ya se halla muerto y queramos borrarlo de la tabla de enemigos.

Aquí también contamos con un objeto de tipo Evasor, pues dado el buen resultado en el movimiento del anterior robot implementado hemos decidido que era una buena opción mantener esta funcionalidad. Vemos también una serie de constantes: GUN_TURNING_RATE, START_HIT_TIME, TOL, MAX_ITERATIONS y BULLET_POWER, que servirán para los cálculos relativos a disparar hacia la dirección y con la fuerza adecuadas.

Y como parte fundamental de esta implementación, encontramos la matriz de double’s enemyParameters:

public double[] enemyParameters = new double[4]; //enemyParameters[0]: Distancia lineal a la que se encuentra nuestro enemigo

//enemyParameters[1]: Distancia angular a la que se encuentra nuestro enemigo //enemyParameters[2]: Direccion de avance del enemigo(heading) //enemyParameters[3]: Velocidad del enemigo que nos servirá para afinar nuestros disparos, para calcular nuestros movimientos en función de los enemigos...

La ejecución de nuestro robot será más sencilla y ordenada que en el caso del robot anterior, ya que en cada iteración del método run() lo que se hará será sencillamente invocar una función operación(), en la que dependiendo del estado de la máquina de estados en el que nos encontremos invocará unas funciones u otras, ara una vez hecho esto, invocar al método que se encarga de nuestro movimiento, todo ello realizado de forma “paralela” debido a la utilización de métodos de tipo setXXX que permiten la realización de múltiples tareas no bloqueantes las unas con las otras, todas ejecutadas al llamar a la función execute(), hecho que se da nada más regresar de la función operación() (método run()).

La máquina de estados sobre la que funciona este robot tiene cuatro estados: INICIO:

Estado en el que comienza la ejecución del robot y al que no volverá hasta una

nueva llamada al método run() del robot. En este estado se determina el número de compañeros que hay luchando a nuestro lado, se establece la velocidad del robot y se da paso al segundo estado, el de búsqueda de objetivos. *Previo paso a este estado se da la ejecución de un método inicio en el que se inicializan algunas características de nuestro robot, más orientado a lo que sería un método constructor. TARGETING:

Dado que en este estado lo que se pretende es adquirir objetivos, lo que se hará

aquí será básicamente generar giros completos del radar, para que cuando se detecte algún robot, el método onScannedRobot haga el resto.

En onScannedRobot lo que se hará será, al detectar un robot ajeno, comprobar

en primer lugar si pertenece a nuestro equipo, en cuyo caso gestionamos la evasión de un choque contra él y en caso de ser un robot enemigo, recogeremos todos sus parámetros en la matriz enemyParameters previamente mencionada, para después ver si ya conocíamos a este enemigo de antes (se encuentra en la matriz enemigos). Si ya sabíamos de su existencia actualizamos su información, y si no, lo añadimos a la tabla de enemigos.

Además llegados a este punto, se comprueba si el robot tiene enemigo asignado (enemigoActual != null). Si no tiene ninguno asignado, bien porque acabe de comenzar la partida, bien porque el que tenía ya ha sido aniquilado, se le asigna el recientemente escaneado y se pasa al estado de FIRING. En caso de tener ya enemigo, si es el que hemos detectado lo actualizamos, y si no, no hacemos nada. FIRING:

Este es el estado en el que se lleva el cálculo del ángulo con el que disparar, la dirección y la fuerza del disparo, así como el disparo en sí.

Para ello tenemos implementado el método disparar(), en el que se lleva a cabo todo eso que hemos dicho. En este método, con la inestimable ayuda de los datos contenidos en la matriz enemyParameters y con otros que calculamos aquí a partir de aquellos, teniendo en cuenta tanto nuestro movimiento y situación como el movimiento y situación del enemigo, llegamos a un ángulo de giro de nuestro cañón y una potencia de disparo, que al aplicarse deberían dar en el blanco.

Se distingue el caso en el que la velocidad del enemigo sea nula (puede que se haya quedado atascado en una esquina, es carne de cañón) en cuyo caso apuntamos hacia él directamente sin ningún tipo de cálculo previo, del caso en el que el enemigo se está moviendo, caso en el que intervienen una serie de cálculos e iteraciones de optimización de los parámetros implicados.

CELEBRATING:

Este es el estado al que se desea ganar en toda partida, aquel en el que todos los enemigos han sido derrotados y hacemos nuestro baile de la victoria. A este estado se llega cuando ha muerto un robot (RobotDeathEvent) y se ha comprobado que sólo quedan robots aliados (en onRobotDeath).

Una vez ejecutada la máquina de estados, en nuestro método operacion() invocamos el método mover(), que determinará el avance de nuestro robot. Este movimiento es básicamente el mismo que el desarrollado por el IRCTeamBot, con su tendencia anti-gravitacional, su evasión de los muros, y su movimiento especial en el caso de la presencia de un solo robot enemigo para mejorar en el cuerpo a cuerpo.

Asimismo en Teamy también se ha implementado la prevención de choques con compañeros, de la misma forma que en IRCTeamBot.

De entre los métodos que recogen eventos, la principal diferencia se encuentra en el método onRobotDeath, en el cual, dada la máquina de estados implementada, tendrá que devolver a nuestro robot a estado TARGETING cuando el robot fallecido sea el enemigo que tuviese nuestro robot en ese momento, y llevar a nuestro robot al siempre agradable estado de CELEBRATING cuando ya no queden más robots contrincantes.

Hay una ligera variación en la forma de enfocar y disparar al robot enemigo en el onHitRobot, y en onRobotDeath se sigue comunicando a la siguiente ronda una variación aleatoria en el movimiento para que los enemigos no puedan aprender de nosotros.

Código de Teamy:

package ircteam; import robocode.*; import java.util.*; import java.awt.Color; /***************************************************************************** * Teamy:robot implementado por Jorge Carrasco y Elisabeth Pérez * *Este robot utiliza una máquina de estados y esta implementado para funcionar *dentro de un equipo de robots de la misma indole. ****************************************************************************/

public class Teamy extends TeamRobot{

static Hashtable enemigos;//La tabla de enemigos va a ser comun a todos.Enemigo enemigoActual;

String nombreEn; Evasor evasor; public int estadoActual;

public int numAmigosVivos; public int dir;

double tiempoChoque; double orientacionChoque;

//Constantes para controlar el disparo a objetivos moviles: private static final double GUN_TURNING_RATE = 40.0/180.0*Math.PI; private static final double START_HIT_TIME = 20; private static final double TOL = 0.5; private static final double MAX_ITERATIONS = 10; private static final double BULLET_POWER = 1.5;

//Parametros para calcular el disparo certero: public double[] enemyParameters = new double[4];

//enemyParameters[0]: Distancia lineal a la que se encuentra nuestro enemigo //enemyParameters[1]:Distancia angular a la que se encuentra nuestro enemigo //enemyParameters[2]: Direccion de avance del enemigo(heading) //enemyParameters[3]: Velocidad del enemigo

//Para controlar los estados por los que pasa el robot. public final int INICIO = 0; public final int TARGETING = 1; public final int FIRING = 2; public final int CELEBRATING = 3;

public void run() { inicio(); while(true) { operacion(); execute(); }

}//Fin del metodo run

//Método inicio(), donde se inicializan los colores y las reglas de movimiento de las partes integrantes del robot.

public void inicio(){

setColors(Color.red, Color.blue, Color.yellow); setAdjustGunForRobotTurn(true); setAdjustRadarForRobotTurn(true); setAdjustRadarForGunTurn(true); setTurnRadarRight(360);

dir = 1; enemigos = new Hashtable(); evasor = new Evasor(); enemigoActual = null; estadoActual = INICIO; }

public void operacion(){

switch(estadoActual){ case INICIO: if(getTeammates()==null) numAmigosVivos = 0; else numAmigosVivos = getTeammates().length; setMaxVelocity(Math.random() + 7); estadoActual = TARGETING; break; case TARGETING: setTurnRadarRight(360); break; case FIRING: setTurnRadarRight(360); disparar(); break; case CELEBRATING: bailecito(); return; }

mover(); }

public void mover(){

if(enemigoActual==null){ estadoActual = TARGETING; return; }

if(getOthers() > numAmigosVivos + 1){ setTurnRadarRight(360); circleGrav(); }

else{ setTurnRight(enemyParameters[1] + 90 -((double)dir * evasor.ang)); }

//Evitaremos chocarnos contra los muros: double N = getBattleFieldHeight() - getY(); double S = getY(); double E = getBattleFieldWidth() - getX(); double W = getX();

if(N < 70 && getHeading() > 270 && getHeading() < 360) dir = -1;

else

if(N < 70 && getHeading() > 0.0 && getHeading() < 90) dir = -1;

else //Aquí estamos en el caso de que estemos en zona próxima al muro norte pero nuestro sentido de avance es tal que nos aleja de este peligro. if(N < 70) dir = 1;

if(S < 70 && getHeading() > 90 && getHeading() < 270) dir = -1;

else if(S < 70) dir = 1;

if(E < 70 && getHeading() > 0.0 && getHeading() < 180) dir = -1;

else if(E < 70) dir = 1;

if(W < 70 && getHeading() > 180 && getHeading() < 360) dir = -1;

else if(W < 70) dir = 1;

//Si estamos en las zonas de peligro: if(N < 70 || S < 70 || E < 70 || W < 70){ evasor.movetime = getTime(); evasor.sub_tipoEvasion = 1; orientacionChoque = -dir * 10 - 90; tiempoChoque = getTime(); }

else{ orientacionChoque = 0.0; }

if(getOthers() > numAmigosVivos+1){ circleGrav(); }

else{ switch(Evasor.tipoEvasion){ case 1: mov1(); Evasor.tipoEvasion++; break; case 2: mov2(); Evasor.tipoEvasion++; break; case 3: mov3(); Evasor.tipoEvasion++; break; case 4: mov1(); mov2(); mov3(); Evasor.tipoEvasion++; break; default: mov1(); Evasor.tipoEvasion++; }

}}

/* Movimiento que seguimos cuando tenemos más de un robot acechándonos: */

public void circleGrav() {

double orientacion = 10000; double closest = 10000; double distancia; double orientacionEnemiga; double alpha;

//Dependiendo de la posicion del enemigo mas cercano asi nos moveremos, para no acercarnos peligrosamente a uno de ellos. for(Enumeration e = enemigos.elements(); e.hasMoreElements();){

Enemigo en = (Enemigo)e.nextElement(); distancia = enemyParameters[0]; orientacionEnemiga = Math.toDegrees(enemyParameters[1]);

if(distancia < closest) { closest = distancia; //Al sumarle 180 lo que hacemos es alejarnos del enemigo orientacion = orientacionEnemiga + 180 + (Math.random() * 60 - 30); if(getOthers()<4+numAmigosVivos) orientacion-=90; }

}if(getOthers() >= 4 + numAmigosVivos)

orientacion += orientacionChoque;

if((double)getTime() - tiempoChoque > 10) dir = 1;

setAhead(dir * 100); setTurnRight(NormalRelativeAngle(orientacion)+ Math.random()*20); }

/* En este primer movimiento, si ya se ha acabado el movimiento * anteriormente definido, cambiamos el sentido y angulo de nuestro * movimiento, así como el tiempo que va a durar este. */

public void mov1(){

if((double)getTime() - evasor.movetime > evasor.time){ dir *= -1; evasor.movetime = getTime(); evasor.ang = Math.random() * 25; evasor.time = Math.random() * 7 + 15; }

setAhead(dir * 50); }

/* Definimos otro movimiento de manera completamente distinta: * Utilizando incluso un subtipo del evasor. */

public void mov2(){

if(enemyParameters[0] < 400) evasor.time = Math.min(enemyParameters[0]/12, 24); else evasor.time = 28;

//Como poco el tiempo del siguiente movimiento será 12 ticks evasor.time = Math.max(evasor.time, 12);

if((double)getTime() - evasor.movetime > evasor.time){

evasor.sub_tipoEvasion *= -1; evasor.movetime = getTime(); evasor.ang = Math.random() * 20; }

//A su vez dentro de este movimiento tenemos dos variantes: ir hacia delante o hacia atrás if(evasor.sub_tipoEvasion == 1) setAhead(dir * 100); else setAhead(dir * -5); }

public void mov3(){

evasor.ahead = (int)(Math.random() * 30 + 30); evasor.ang = Math.random() * 15; }

public void disparar(){

double[] posicion = enemigoActual.getPosicion(); double x,y,fire_length,fire_time; double firepower = Math.min(getEnergy(), 99/enemyParameters[0] + Math.random()*(double)2);

double bulletSpeed = 70.0 - 3.0*firepower; // La velocidad del cañon se recalcula cada vez. Es innecesario pero nos permitirá modificarlo para variar la potencia dle cañón si queremos

double ett = amountToTurn(enemyParameters[1])/GUN_TURNING_RATE; // Tiempo estimado de giro

double hit_time = START_HIT_TIME; double actual_time = -1.0; double fire_angle; int count = 0;

if(enemyParameters[3]==0){ //Calculamos el angulo a girar para apuntar al enemigo double theta = Math.toDegrees(Math.atan2(posicion[0]-getX(),posicion[1]-getY())); //Apuntamos setTurnGunRight(NormalRelativeAngle(theta - getGunHeading())); }

else{ do {

if (count != 0) hit_time = actual_time;

count++; x = enemyParameters[0] * Math.sin(enemyParameters[1]); y = enemyParameters[0] * Math.cos(enemyParameters[1]);

//Tomando el movimiento del enemigo en cuenta: x+=hit_time*enemyParameters[3] * Math.sin(enemyParameters[2]); y+=hit_time*enemyParameters[3] * Math.cos(enemyParameters[2]);

//Tomando nuestro movimiento en cuenta: y -= ett * this.getVelocity()/2;

fire_length = Math.sqrt((x * x) + (y * y)); //Distancia fire_time = fire_length / bulletSpeed; fire_angle = Math.atan(x / y); if (y < 0) fire_angle += Math.PI; ett = amountToTurn(fire_angle)/GUN_TURNING_RATE;

actual_time = fire_time + ett; }

while (Math.abs(actual_time - hit_time) > TOL && count < MAX_ITERATIONS); setTurnGunRightRadians(amountToTurn(fire_angle));

}

setFire(firepower); }

/*********** METODOS PARA CONTROLAR LOS EVENTOS ROBOCODE ***************/

public void onScannedRobot(ScannedRobotEvent e) {

String nombreDetectado = e.getName(); Enemigo enemigoDetectado;

double X = getX() + e.getDistance() * Math.sin(Math.toRadians(getHeading()+e.getBearing())); double Y = getY() + e.getDistance() * Math.cos(Math.toRadians(getHeading()+e.getBearing()));

if(isTeammate(nombreDetectado)){

double distanciaComp = calcularDistancia(getX(),getY(),X,Y); if(distanciaComp<100){ setTurnLeft(90-e.getBearing()); }

//y vamos en busca de charlies setTurnRadarRight(360); }

else{ enemyParameters[0]=e.getDistance(); // Distancia al enemigo

enemyParameters[1]=e.getBearingRadians();//Angulo hasta el enemigo enemyParameters[2]=e.getHeadingRadians()-this.getHeadingRadians(); // Direccion del enemigo

enemyParameters[3]=e.getVelocity(); // Velocidad del enemigo

if(enemigos.containsKey(nombreDetectado)){ enemigoDetectado = (Enemigo)enemigos.get(nombreDetectado); enemigoDetectado.posX = X; enemigoDetectado.posY = Y; enemigoDetectado.energia = e.getEnergy(); }

else{ enemigoDetectado=new Enemigo(nombreDetectado,X,Y,e.getEnergy());

}enemigos.put(nombreDetectado, enemigoDetectado);

//Con esto asignamos enemigo a nuestro robot, priorizando ir a por el

lider del equipo contrario: if(enemigoActual == null){ enemigoActual = enemigoDetectado; nombreEn = nombreDetectado; estadoActual = FIRING; }

else{ //De la siguiente manera actualizamos la posicion almacenada de nuestro enemigo: if((estadoActual== FIRING)&&(nombreEn.equals(nombreDetectado))) enemigoActual = (Enemigo)enemigos.get(nombreDetectado); }

}}

public void onRobotDeath(RobotDeathEvent e){

String nombreFallecido = e.getName(); if(!isTeammate(nombreFallecido)){ Enemigo en = (Enemigo)enemigos.get(nombreFallecido); if(nombreEn.equals(nombreFallecido)){ enemigoActual = null; nombreEn = ""; estadoActual = TARGETING; }

enemigos.remove(nombreFallecido); }

else numAmigosVivos--;

if(getOthers()-numAmigosVivos == 0){ estadoActual = CELEBRATING; operacion(); }

}

/** * onHitByBullet: Turn perpendicular to the bullet */

public void onHitByBullet(HitByBulletEvent e) {

// Evasive action setTurnLeft(90 - e.getBearing()); Evasor.tipoEvasion++; }

//Aqui meteremos lo que queramos guardar para la ronda que viene:

public void onDeath(DeathEvent e){

switch(Evasor.tipoEvasion){ case 1: Evasor.tipoEvasion = 2; break; case 2: Evasor.tipoEvasion =(int)(Math.random()*3.9900000000000002)+ 3; break; default: Evasor.tipoEvasion = 1; }

}

public void onHitRobot(HitRobotEvent e) {

if(isTeammate(e.getName())){ double tpRnd = Math.random() * 10; int rndInt = (int) Math.ceil(tpRnd); tpRnd = tpRnd % 3; switch (rndInt) { case 0: turnLeft(90-e.getBearing()); back(100); break; case 1: turnRight(90-e.getBearing()); ahead(100); break; case 2: turnLeft(90); back(200); }

}else{

double bearingFromGun= NormalRelativeAngle(getHeading()+e.getBearing()-getGunHeading());

turnGunRight(bearingFromGun); setFire(Math.min(3-Math.abs(bearingFromGun),getEnergy()-.1));

//Si nos chocamos con un enemigo le freimos }

}

/*************** METODOS AUXILIARES ******************/

public void bailecito(){

setTurnLeft(36000); setTurnGunRight(36000); setAhead(50); setBack(50); }

public double calcularDistancia(double x1,double y1,double x2,double y2) {

return Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)); }

private double amountToTurn(double heading) {

double res = heading + getHeadingRadians() - getGunHeadingRadians(); while (res > Math.PI) res -= 2 * Math.PI; while (res < -Math.PI) res += 2 * Math.PI; return res; }

public double NormalRelativeAngle(double angle){

if (angle > -180 && angle <= 180) return angle; while (angle <= -180) angle += 360; while (angle > 180) angle -= 360; return angle; }

}

RESULTADOS:

Como comentario final decir que hemos puesto a luchar a los dos equipos para evaluar sus prestaciones uno contra el otro y estos han sido los resultados proporcionados por robocode:

De estos resultados podemos sacar la conclusión de la superioridad del equipo formado por robots IRCTeamBot contra el formado por Teamy’s, pero también nos gustaría decir que estos resultados también dependen mucho de los contrincantes, ya que unos equipos están mejor preparados por sus estrategias utilizadas para derrotar a equipos con ciertas características. En este sentido el ejemplo lo obtuvimos al comprobar que, a pesar de ganar los dos equipos, si los poníamos a luchar por separado contra el equipo de ejemplo que suministra el paquete Robocode, el equipo de Teamy´s sufría muchas menos bajas a la larga que el equipo de IRCTeamBot’s.

REFERENCIAS:

Guía de aprendizaje robocode: http://www.ug.cs.usyd.edu.au/~csled/biwf/robocode/guide.html

Para el juego de equipo: http://www-106.ibm.com/developerworks/java/library/j-robocode2/?loc=j

CS 3230 Robocode project: http://www.codepoet.org/~markw/weber/java/robocode/

RoboHome http://robowiki.net/cgi-bin/robowiki

Robocode FAQ : http://robocode.alphaworks.ibm.com/help/robocode.faq.txt

Robocode API http://robocode.alphaworks.ibm.com/docs/robocode/index.html