robocode

Transcription

robocode
Inteligencia en Redes de
Comunicaciones
ROBOCODE
David de Miguel Medina 100029556
Pablo Andreu barasoain 100027455
INDICE
Pag.
Introducción …………………………………………………………..
3
El Campo de juego y los robots ………………………………………. 4
Estrategia………………………………………………………………. 6
¿Como movernos?.................................................................... . 6
¿Cuando, como y con que potencia disparar?........................... 7
¿Como localizar a nuestro enemigo?.......................................... 8
Código…………………………………………………………………… 9
Conclusiones……………………………………………………………16
Bicliografia……………………………………………………………. 17
-2-
Introducción
Robocode es un simulador de combate donde tanques programados en Java luchan en
un escenario preparado para ello, siendo el vencedor aquel que quede vivo.
Este proyecto fue creado por Mathew Nelson, ingeniero de IBM, en un intento de
convencer a la comunidad de que Java está preparado para ser utilizado en juegos y
además, ser una metodología adictiva de aprendizaje del lenguaje. Se trata de un juego en el
que se puede aprender dicho lenguaje y el manejo de los eventos de java programando tu
propio robot mediante el propio lenguaje y métodos de java. Ayudándote de los métodos
que se te proporcionan en el API específico para Robocode, el cual te permite ejecutar el
programa y tu robot en cualquier sistema o PC, puedes implementar o sobrescribir métodos
de dicho API para conseguir un robot más eficiente. Este API, parte de la clase “Robot”,
“Bullet”, “Condition” y “Event” y se apoya en algunas otras clases de java como
“java.io.OutputStream” y “java.io.Writer”. Además, el API cuenta con una interfaz llamada
“Droid”.
En Robocode el programador debe de encargarse de elegir la mejor estrategia para su
robot e implementarla mediante desplazamientos, giros, controlando el radar y cañón del
robot así como haciéndole disparar cuando considere apropiado. También tiene que
controlar los eventos que se producen mientras combate (impacto contra un muro, de una
bala….)
Todas las batallas entre robots constan de uno o varios combates, en los cuales los
robots parten de una posición inicial aleatoria y tienen que luchar entre ellos tanto
individualmente como por equipos. El objetivo principal del juego es obtener más puntos
que los robots contrarios al final de la batalla, par ello se debe destruir al robot contrincante
aunque también se pueden obtener puntos por diversas actuaciones.
Hay dos tipos de combates: los combates individuales, y los combates en equipos, en
el cual, un conjunto de robot luchan contra otro para conseguir la destrucción del equipo
entero contrario. Dentro de los combates individuales, hay dos modalidades, una
consistente en un “todos contra todos” en el cual solo debe quedar uno o los combates
one2one, es decir uno contra otro, en los cuales combaten únicamente dos robots. Nosotros
nos centraremos en este tipo de combates.
-3-
El Campo de juego y los robots
Los combates se realizan en superficies rectangulares que oscilan entre las
dimensiones de 400x400 hasta 5000x5000 y por las cuales el robot tiene total libertad para
moverse y localizar a los robots contrarios. Para ello, el robot consta de tres partes básicas
diferenciadas: el propio “chasis” del robot, el escáner y el cañón, los cuales se pueden
mover conjunta o separadamente en función de la estrategia seguida por el jugador. Todos
los movimientos que se le ordenen al tanque, deberán ir encaminados a mover el chasis,
mientras que con el escáner, este podrá localizar a los enemigos para apuntarlos con el
cañón, con el cual disparara.
Los robots heredan de la clase “Robot” por defecto, aunque también pueden heredar de
“AdvancedRobot” o “TeamRobot”. Las principales diferencias son que la clase “Robot”
dispone únicamente de los métodos básicos para la implementación del tanque.
“AdvancedRobot” hereda a su vez de la clase Robot y permite, además de todas las
funciones básicas, otras más avanzadas que nos facilitaran el desarrollo de nuestro robot,
así como el acceso a métodos mas eficientes y variados para controlarlo. Por último la clase
‘TeamRobot’ hereda de ‘AdvancedRobot’ y permite el intercambio de mensajes entre
robots de un mismo equipo. Los robots que heredan de esta clase, suelen ser aquellos que
compiten por equipos y que necesitan un paso de mensajes par comunicarse entre ellos.
Cualquiera puede crear nuevas subclases de Robot y añadir nuevas funcionalidades
que pueden ser usadas para construir robots. Como se acaba de explicar, 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).
Esto nos vendrá muy bien ya que podremos realizar diversos movimientos a la vez, decidir
qué hacer en cada turno y tener acceso a la creación y gestión de eventos creados por
nosotros mismos.
Al principio del combate, todos los robot parten de una energía inicial de 100, dicha
energía se ira reduciendo en función de los impactos que reciba así como de los disparos
perdidos que realice, de igual modo, si el robot consigue que sus balas impacten en el
contrincante, su energía aumentara en la misma proporción que la del enemigo disminuya.
También disminuyen la energía el hecho de impactar contra un muro del terreno de juego o
contra otro robot. Cuando su energía sea cero, el robot entrara en un estado llamado disable,
en el cual permanece quieto y no puede realizar ninguna acción, siendo destruido en el caso
de que alguna bala impacte contra el.
Debido a esto ultimo, se pueden implementar muchas estrategias, pero todas estarán
basadas en acertar los máximos disparos en el tanque enemigo mientras que nuestro robot
no reciba ningún disparo ni choque contra las paredes o enemigos.
Como se ha comentado anteriormente, al principio del combate, todas las partes de las
que se compone el tanque están alineadas y forman una unidad, es decir, cuando un tanque
-4-
gira, el cañón y el radar harán el mismo movimiento a no ser que se le indique lo contrario.
Esto viene del hecho de que el cañón está montado sobe el cuerpo y el radar sobre el cañón.
Debido a esto, si giro el chasis de nuestro tanque para en una dirección en concreto, tanto
el cañón como el radar, giraran también hacia esa dirección en concreto. Si giro el cañón, el
cuerpo no se mueve, pero si lo hace el radar. Y por último, si muevo el radar, no se mueve
nada más.
-5-
Estrategia
A la hora de elegir una estrategia en concreto para nuestro robot, se vio que teníamos
que concretar estrategias para diversos puntos, en concreto:
•
•
•
Como movernos
Cuando, como y con que potencia disparar.
Como localizar a nuestro enemigo
Tras consultar diversas paginas y tutoriales de Internet y realizar simulaciones de los
robots básicos que se adjuntaban con el Robocode, decidimos que la mejor estrategia
podría ser seguir la pared al igual que hacia el tanque “walls”, además, se podrían
realizar diversas mejoras para que nuestro robot tardar menos en escasear, disparara
mas eficientemente y esquivara las balas del enemigo.
Por todo ello, se opto por una estrategia de evasión, ya que al estar pegado a la pared, si
se realizaran los movimientos correctamente, podríamos conseguir que nuestro enemigo
fuera perdiendo energía poco a poco debido a que no nos acertaría en los disparos.
Centrándonos en cada una de las estrategias seguidas para cada uno de los tres
puntos enunciados anteriormente, tenemos:
¿Como movernos?
Para elegir esta opción, lo primero que tuvimos que hacer es consultar que
métodos del API de Robocode nos ayudarían a realizar los movimientos y por donde
moveríamos a nuestro robot:
Para que tanto el radar, como el cañón se muevan independientemente del chasis
del tanque, se necesitan:
void setAdjustGunForRobotTurn(boolean newAdjustGunForRobotTurn) sirve para
tener un movimiento del cañón independiente del cuerpo
void setAdjustRadarForGunTurn(boolean newAdjustRadarForGunTurn) sirve para
tener un movimiento del radar independiente del cañón.
void setAdjustRadarForRobotTurn(boolean newAdjustRadarForRobotTurn) sirve para
tener un movimiento del radar independiente del robot.
Para obtener las dimensiones del campo se pueden utilizar los siguientes
métodos:
double getBattleFieldHeight() nos indica la altura máxima del campo de batalla.
double getBattleFieldWidth() nos indica la anchura máxima del campo de batalla.
Sabiendo que el campo de batalla, tiene el origen de coordenadas en la esquina
inferior izquierda, durante cualquier instante del combate, se puede obtener la posición
en el campo de batalla del robot mediante:
double getX() nos indica la posición en la coordenada X de nuestro robot.
double getY() nos indica la posición en la ordenada Y de nuestro robot.
Para mover en si el tanque, están los siguientes métodos:
void setAhead(double distance) sirve para mover el robot hacia adelante
void setBack(double distance) sirve para mover el robot hacia atrás
void turnLeft(double degrees) sirve para girar a la izquierda
void turnRight(double degrees) sirve para girar a la derecha
de igual modo, existen métodos para mover tanto el radar como el escáner:
void setTurnGunLeft(double degrees)
void setTurnGunRight(double degrees)
void setTurnRadarLeft(double degrees)
void setTurnRadarRight(double degrees)
con todos estos métodos, podemos conseguir que nuestro robot mueva
independientemente el escáner y el cañón del chasis, y por tanto, podemos ir moviendo
el tanque siguiendo las paredes mientras que escaneamos y disparamos
independientemente de nuestro movimiento. De esta manera, si nos movemos mucho y
deprisa, sin que el movimiento sea predecible por los robots enemigos, ellos iran
gastando poco a poco su energía disparándonos. Con lo que habremos implementado
nuestra estrategia de evasión.
El principal problema de esto, es que al movernos tan cerca de la pared, tenemos que
controlar constantemente que no se choca con ella ya que estaríamos perdiendo energía
inútilmente. Y de igual modo, tenemos que tener cuidado con el robot que está cercano
a la pared, o como nosotros la siguen para que no colisionemos, y en caso de que lo
hagamos, salgamos beneficiados de ello.
¿Cuando, como y con que potencia disparar?
Una vez decidida la estrategia del movimiento, y sabiendo que es independiente de los
disparos, se nos planteo el problema de que el adversario se moviese mucho y estuviese
muy lejos de nosotros. Ya que nosotros no nos acercamos a el, y no sabemos si el lo
hará, decidimos que no salía rentable dispararle desde lejos ya que estaríamos
utilizando nuestra propia técnica en nuestra contra, así que decidimos que si se
encontraba a mas de 300 de distancia, no le dispararíamos.
Aunque este a menos de 300 de distancia, si nos queda poca energía mientras que el
enemigo tiene mas que nosotros, tampoco le disparamos, es preferible esquivarle para
que siga perdiendo energía hasta que tenga aproximadamente la misma que nosotros.
También, tenemos que pensar con que potencia de disparo lanzamos nuestra bala contra
el robot contrincante. Tras decidir que a mas de 300 de distancia no le dispararíamos, y
que la potencia de fuego puede variar entre 0 y 3 fue sencillo pensar que si estaba a
menos de 50 le dispararíamos con la máxima energía, si estaba entre 50 y 100 con algo
menos y así progresivamente tal y como muestra el cuadro siguiente:
7
Potencia de
disparo
3
2.5
2
1.5
1
0.5
distancia
0 - 50
50 - 100
100 - 150
150 – 200
200 – 250
250 – 300
En resumen, solo dispararíamos al contrincante si tiene nuestra misma energía o menos
y se encuentra cerca de nosotros, sino, preferimos esquivarle para que siga perdiendo
energía disparándonos.
¿Como localizar a nuestro enemigo?
Para localizar a nuestro enemigo, lo primero que tenemos que hacer es girar el radar 360
grados para obtener por primera vez su posición y demás datos necesarios para el
disparo. Además, si llevamos un algún tiempo sin localizarle, también vendría bien
volver a rotar el escáner para volver a localizarle cuanto antes.
En caso de que le tengamos localizado, puesto que el radar estará siempre intentando
apuntar hacia el enemigo, lo único que tenemos que hacer para actualizar la posición
del radar es obtener la posición relativa del otro robot y restarle el ángulo de diferencia
que haya entre nuestro escáner y la posición de 0º de nuestro robot (el heading).
Además, si se le añade un pequeño offset, se conseguirá corregir el desfase entre el
momento en el que calculamos la situación del otro tanque y la que tendrá en el
momento siguiente de actualizar el escáner. De esta forma estará siempre localizado y
puesto que solo nos tenemos que preocupar de un solo contrincante, no necesitaremos
implementar muchas mejoras respecto a este movimiento de escáner.
Para almacenar toda la información que necesitamos de nuestro enemigo, tendremos
que crearnos una clase aparte de la del propio robot donde estén almacenadas todas las
variables y métodos que tenemos que realizar sobre el cálculo de posición y disparo de
nuestro enemigo.
8
Código
package dmm;
import robocode.*;
import java.awt.Color;
/**
* Definitivo – by dmm y pa
*/
public class Definitivo extends AdvancedRobot
{
double movimiento; // cuanto nos movemos
double movMaxHor; //movimiento maximo horizontal
double movMaxVer; //movimiento maximo vertical
double potencia; // potencia de disparo
//variables de estado para saber el movimiento siguiendo las paredes
int pared = 2;
// variable final para tener el valor de 180º
final double PI = Math.PI;
Enemigo target;// referencia a un objeto de la clase enemigo
// varibles que nos ayudan a saber en que sentido va el escaner y el robot
int direction = 1;
int sentidoEscaner = 1;
/**
* run: Método principal de nuestro Robot
*/
public void run() {
//Coloreamos nuestro robot
setColors(Color.black,Color.black,Color.yellow);
//inicializamos variables del entorno de combate y nos creamos el
enemigo
target = new Enemigo();
target.distance = 100000;
movMaxHor = getBattleFieldWidth();
movMaxVer = getBattleFieldHeight();
//Iniciamos movimiento hacia la pared superior y giramos para empezar
//el movimiento evasivo
turnLeft(getHeading());
movimiento = movMaxVer - getY() -25;
ahead(movimiento);
turnRight(90);
9
//activamos las funciones del cañon y radar para que sean independientes
setAdjustGunForRobotTurn(true);
setAdjustRadarForGunTurn(true);
setAdjustRadarForRobotTurn(true);
//Bucle principal de funcionamiento
while (true) {
//nos movemos y actualizamos la pared en la que estamos
pared = mover();
// escaneamos en busca del objetivo, y si cumple las especificaciones de
// cercania disparamos
escanear();
calcularTiro();
if(calcularPotencia()) fire(potencia); //Disparamos
execute();
}
}
/**
* mover: Metodo que define el movimiento general de nuestro Robot
*/
public int mover(){
// varible que contiene el movimiento maximo que puede realizar
el robot
double movimientoMax = 100000;
// variable local que nos indica en que pared estamos
int isInWall;
isInWall = pared;
// el movimiento esta comprendido entre 300 y 0, siendo mas o
menos aleatorio
movimiento = Math.random()*300;
//si nos ha salido un movimiento muy pequeño, lo cambiamos
if (movimiento < 50) movimiento = 50;
switch (isInWall){
//actualizamos el movimiento maximo en funcion de la pared en
la que
//estemos y la situacion del robot actual
case 1: movimientoMax = movMaxVer - getY() - 25; break;
case 2: movimientoMax = movMaxHor - getX() - 25; break;
case 3: movimientoMax = getY() - 25; break;
case 4: movimientoMax = getX() - 25; break;
default: movimientoMax = 25; pared = 4;
}
// si el movimiento es justo para llegar al fin de esa pared,
giramos, sino simplemente nos movemos hacia adelante
10
if (movimiento > movimientoMax &&( pared == 1 || pared == 2 ||
pared == 3 || pared == 4 )) {
if (pared == 4 && movimientoMax == 25){isInWall =
4;}
else {isInWall = ++pared %4;
movimiento = movimientoMax;
ahead(direction*movimiento);
turnRight(90);
}
}
else {setAhead(movimiento);}
return isInWall;
}
/**
* onHitRobot: Si chocamos con un adversario, retrocedemos o avanzamos segun
*donde este y cambiamos el sentido de la marcha.
*/
public void onHitRobot(HitRobotEvent e) {
direction = direction*-1;
// Si esta delante nuestra, echamos para atrás
if (e.getBearing() > -90 && e.getBearing() < 90)
setBack(100);
// Si está detrás nos desplazamos en sentido contrario
else
setAhead(100);
}
/**
* calcularPotencia(): Calculamos la potencia optima basado en la distancia al
objetivo
*/
boolean calcularPotencia() {
/*variables auxiliares donde se almacenara la distancia al enemigo y
los valores maximos y minimos que acotan cada potencia de disparo*/
double distancia, min, max;
distancia = target.distance;
/*si la distancia esta fuera del rango asignado, no se dispara,
con lo que se pone el poder de disparo a 0 y se devuelve false*/
if (distancia > 300 || distancia < 0) potencia = 0;
else{
min = 250;
max = 300;
potencia = 3;
11
// calculamos la potencia de tiro en funcion de la distancia a la que
este
while(!(distancia > min && distancia < max)){
potencia = potencia -0.5;
min= min -50;
max = max -50;
if (distancia > 300 || distancia < 0){potencia = 0; break;}
}
// si esta en el rango, devolvemos true, con lo que se disparara
if (potencia != 0) return true;
}
// en caso de no devolver true, es porque no esta en el rango, con lo que
no se disparara.
return false;
}
/**
* escanear(): Realizamos un scanner
*/
void escanear() {
double radarOffset;
//Si hace tiempo que no localizamos a nadie
if (getTime() - target.ctime > 4) {
radarOffset = 360;//lo rotamos 360º
} else {
//Calculamos el giro necesario del radar para seguir al objetivo
radarOffset
=
getRadarHeadingRadians()
absbearing(getX(),getY(),target.x,target.y);
//Calculamos el offset debido al seguimiento del objetivo
//para no perderlo
if (radarOffset < 0)
radarOffset -= PI/8;
else
radarOffset += PI/8;
}
//giramos el radar
setTurnRadarLeftRadians(NormaliseBearing(radarOffset));
}
/**
* calcularTiro(): Realizamos los cálculos de balística
*/
void calcularTiro() {
12
-
// Primera estimación: calculamos el tiempo que tardaría en llegar el
proyectil a la posicion actual del objetivo
long time = getTime() + (int)(target.distance/(20-(3*potencia)));
// calculamos el offset de giro según una estimación de movimiento lineal
double
gunOffset
=
getGunHeadingRadians()
absbearing(getX(),getY(),target.guessX(time),target.guessY(time));
//giramos el cañon
setTurnGunLeftRadians(NormaliseBearing(gunOffset));
}
//ajustamos el ángulo si no se encuentra de -pi a pi
double NormaliseBearing(double ang) {
if (ang > PI)
ang -= 2*PI;
if (ang < -PI)
ang += 2*PI;
return ang;
}
//Si no se encuentra de 0 a 2pi lo modificamos para que sea el ángulo más corto
double NormaliseHeading(double ang) {
if (ang > 2*PI)
ang -= 2*PI;
if (ang < 0)
ang += 2*PI;
return ang;
}
//Calcula la distancia entre dos puntos x e y
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;
}
//Calcula el ángulo entre dos puntos
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 )
13
{
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: Método que se ejecuta cuando el scanner encuentra un
robot
*/
public void onScannedRobot(ScannedRobotEvent e) {
//Si encontramos un robot más cercano al objetivo actual
if ((e.getDistance() < target.distance)||(target.name == e.getName())) {
//Calcular apuntamiento
double
absbearing_rad
=
(getHeadingRadians()+e.getBearingRadians())%(2*PI);
//actualizamos las variables del nuevo objetivo
target.name = e.getName();
target.x = getX()+Math.sin(absbearing_rad)*e.getDistance();
target.y = getY()+Math.cos(absbearing_rad)*e.getDistance();
target.bearing = e.getBearingRadians();
target.head = e.getHeadingRadians();
target.ctime = getTime();
target.speed = e.getVelocity();
target.distance = e.getDistance();
}
}
/**
* onRobotDeath: Método que corre cuando muere un adversario
*/
public void onRobotDeath(RobotDeathEvent e) {
if (e.getName() == target.name)
target.distance = 10000; //actualizamos la variable de distancia
}
}
14
/*
*
Clase Enemigo. La utilizamos para almacenar la información acerca de nuestros
adversarios
*
Posición, velocidad, disparos, etc
*/
class Enemigo {
String name;
public double bearing;
public double head;
public long ctime;
public double speed;
public double x,y;
public double distance;
//metodo que intenta adivinar la coordenada X del enemigo en function de los
datos que tenemos de el
public double guessX(long when)
{
long diff = when - ctime;
return x+Math.sin(head)*speed*diff;
}
//metodo que intenta adivinar la coordenada Y del enemigo en function de los
datos que tenemos de el
public double guessY(long when)
{
long diff = when - ctime;
return y+Math.cos(head)*speed*diff;
}
}
15
Conclusiones
La primera de las conclusiones es que ha sido extremadamente difícil depurar el
código ya que no contábamos con ninguna manera de poder mostrar mensajes por
pantalla con lo que nunca hemos tenido a ciencia cierta si el robot hacia lo que nosotros
pensábamos o no. Esto se ha debido a que los métodos del paquete “java.io” que
teníamos en el API de Robocode daban errores de compilación. Este problema ha sido
determinante en la construcción del robot ya que no hemos podido implementar muchas
de las mejoras y estrategias que teníamos pensadas en el robot, teniendo que simplificar
el código con lo que nuestro robot se ha hecho mucho más vulnerable.
El comportamiento, aun así del robot ha sido satisfactorio en las pruebas
realizadas con los robots de ejemplo suministrados con Robocode con los cuales
siempre se ha obtenido la victoria para cada uno de ellos.
el siguiente ejemplo muestra otra batalla con otro de los robots de muestra, se ha
probado con todos los robots tanto en 10 como en 5 asaltos y siempre se ha obtenido la
victoria.
No se ha podido implementar totalmente la estrategia de disparo ni de
movimiento por el motivo anteriormente mencionado, pero ajustando el disparo al un
valor por defecto, también se han conseguido buenos resultados con los robots de
prueba.
La experiencia ha sido pese a todo, entretenida y muy divertida.
16
Bibliografía
http://www.it.uc3m.es/jvillena/irc/
http://www.robocode.ie/index.html
http://robocode.sourceforge.net/
http://www.codepoet.org/~markw/robocode/
http://www.ibm.com
17