1 package foo;
  2
  3 import robocode.*;
  4 import robocode.util.Utils;
  5 import java.awt.Color;
  6 import java.lang.Math;
  7 import java.util.Random;
  8  
  9 /** 
 10  * @mainpage Foobar
 11  *
 12  * Foobar est un bot écrit pour robocode à l'occasion du concours
 13  * organisé par l'Institut Paul Lambin (http://robocode.ipl.be).
 14  * Pour l'utiliser, référez vous à la documentation de robocode
 15  * (http://robocode.sourceforge.net)
 16  *
 17  * Ce bot a été testé écrit sous un système entièrement libre, à l'aide
 18  * d'outils tels que vim, doxygen, openjdk, …
 19  * et fonctionne correctement avec OpenJDK 1.5 (version de javac 1.6)
 20  * sur robocode 1.7.1
 21  *
 22  * @section Fonctionnement
 23  * Le robot adopte plusieurs comportements en fonction de divers facteurs.
 24  *
 25  * @subsection Techniques
 26
 27  * @subsubsection Dodge
 28  * Fait d'éviter les balles en observant l'énergie
 29  * de l'ennemi.
 30
 31  * Inconvénients :
 32  * @li La classe @c Robot ne se déplace pas assez vite contrairement
 33  * à la classe @c AdvancedRobot. On peut corriger cela en évitant les
 34  * balles si le robot se trouve à une certaine distance (@c 150
 35  * semble raisonnable)
 36  * @li L'@c Inactivity @c Time étant fort bas pour ce concours
 37  * (30), cela peut nous induire en erreur. En effet, l'énergie du
 38  * robot chute un peu à chaque tour. Cale se corrige en n'évitant
 39  * pas les tirs en dessous d'une certaine puissance (@c 1.0 semble
 40  * raisonnable)
 41  *
 42  * Néanmoins, nous n'utiliserons pas cette technique, qui s'est
 43  * révélée peu efficace après quelques tests.
 44
 45  * @subsubsection Mouvement
 46  * En 1v1, le robot se placera perpendiculairement et se déplacera de
 47  * façon circulaire. Lorsqu'il touchera un mur, il change de
 48  * direction. Le robot à tendance à aller se bloquer dans les coins à
 49  * cause de ce type de déplacement, quand il est bloqué, il tourne
 50  * alors en angle droit et avance, ce qui le sort du coin (si pas du
 51  * premier coup, la seconde fois le robot ne sera plus dans le coin).
 52  *
 53  * En melée, on scanne jusqu'à ce qu'on trouve un robot assez près
 54  * pour tirer dessus avec succès, si on n'en trouve pas, on tire sur
 55  * le dernier robot scanné.
 56  *
 57  * Dans les deux cas, si on se trouve très proche d'un autre robot, on
 58  * lui fonce dessus si on a une chance de le tuer afin de récolter
 59  * plus de points, sinon on lui tire simplement dessus et on s'en
 60  * éloigne.
 61  *
 62  * @subsubsection Visée
 63  * Comme technique de visée, nous combinons le @c Linear @c
 64  * Targeting et une méthode de visée basique.
 65  * Si on tire des balles puissantes (et donc lentes), on essaie
 66  * d'estimer la position qu'aura l'ennemi à l'impact, en considérant
 67  * qu'il se déplace dans la même direction à la même vitesse.
 68  * Dans l'autre cas, on tire simplement des balles plus rapides (et
 69  * moins puissantes), directement là où se situe l'ennemi.
 70  *
 71  * @section Vocabulaire
 72  * La documentation contient du vocabulaire propre à robocode
 73  * (les termes anglais francisés), décrits ici.
 74  *
 75  * @li @c rammer : le fait de foncer dans un autre joueur pour lui
 76  * faire perdre de la vie, et gagner beaucoup de points.
 77  */
 78
 79 /**
 80  * Le robot.
 81  */
 82 public class Foobar extends Robot {
 83
 84   /* Variables (elles ne sont pas mis en private pour avoir les
 85    * commentaires dans la documentation doxygen. Elles ne sont pas
 86    * censées être accessibles à partir d'autre part que des méthodes
 87    * de la classe Foobar) */
 88
 89   /**
 90    * Le robot qu'on est en train de rammer
 91    * (vide si personne)
 92    */
 93   String m_sRamming = "";
 94
 95   /**
 96    * Le bearing du robot qu'on ramme
 97    */
 98   double m_dRamBearing = 0.;
 99   /**
100    * L'énergie du robot qu'on ramme
101    */
102   double m_dRamEnergy = 0.;
103   /**
104    * Le temps dernier ram effectué
105    */
106   long m_lLastRam = 0;
107
108   /**
109    * Le nombre de tir ratés
110    */
111   int m_iUnhit = 0;
112   /**
113    * Le nombre de fois qu'on est touché sans avoir bougé
114    */
115   int m_iHittedWithoutMove = 0;
116
117   /**
118    * La direction dans laquelle on se dirige .
119    * 1 si on se déplace en avant, -1 si on se déplace en arrière.
120    */
121   int m_iDirection = 1;
122
123   /**
124    * Le nombre de robot scannés (remis à zéro quand on arrive au
125    * nombre total de robot)
126    */
127   int m_iScanned = 0;
128
129   /* Constantes (ne sont pas en private pour la même raison que les
130    * variables) */
131   /**
132    * Distance à partir de laquelle un robot est trop loin pour
133    * qu'on lui tire dessus avec succès
134    */
135   static final double FarAway = 400.;
136   /**
137    * Discance à partir de laquelle le robot est considéré comme
138    * très proche, assez proche pour pouvoir rammer
139    */
140   static final double ReallyNear = 150.;
141   /**
142    * Énergie restante d'un robot pour qu'il soit considéré comme
143    * presque mort, et donc qu'on peut foncer dedans.
144    */
145   static final double AlmostDead = 25.;
146   /**
147    * Le nombre de tirs ratés avant de se déplacer
148    */
149   static final int MaxUnhit = 5;
150   /**
151    * Le nombre de tirs encaissés avant de se déplacer
152    */
153   static final int MaxHits = 3;
154   /**
155    * À chaque déplacement normal, on avance de cette distance
156    */
157   static final double MoveDistance = 70;
158
159   /**
160    * Fonction appelée au début du programme pour lancer le bot.
161    * Si on a rien à faire, on tourne le radar,
162    * si on était en train de rammer un autre robot, on continue à le
163    * rammer, tout en vérifiant qu'on peut continuer
164    */
165   public void run() {
166     looksGood();
167     while (true) {
168       if (m_sRamming == "") {
169         turnRadarRight(360);
170       }
171       else if (getTime() - m_lLastRam > 15 ||
172                !ramIfYouCan(m_sRamming, m_dRamBearing, m_dRamEnergy))
173         m_sRamming = "";
174     }
175   }
176
177   /**
178    * Fonction appelée lorsqu'un robot est scanné par le radar
179    */
180   public void onScannedRobot(ScannedRobotEvent e) {
181     /** 
182      * On garde en mémoire le nombre d'ennemis scannés,
183      * afin de savoir si on a tout scanné (et de faire quelquechose au
184      * dernier moment si il n'y a pas eu d'occasion intéressante
185      */
186      m_iScanned = (m_iScanned+1) % getOthers();
187     
188     /**
189      * Ennemi éloigné : on ne fait rien et on essaye de trouver
190      * un autre ennemi, sauf si c'est le seul ennemi
191      * ou qu'il est immobile
192      */
193     if (e.getDistance() > FarAway) {
194       if (getOthers() == 1 || e.getVelocity() == 0 || m_iScanned == 0) {
195         shootRobot(e.getVelocity(), e.getHeading(), e.getBearing(),
196                    e.getDistance(), e.getEnergy());
197         m_sRamming = "";
198         turnRight(Utils.normalRelativeAngleDegrees(e.getBearing()+90));
199         ahead(MoveDistance*m_iDirection);
200       }
201     }
202     /**
203      * Ennemi très proche : on ramme si c'est à notre avantage
204      * sinon on tire dessus
205      */
206     else if (e.getDistance() < ReallyNear) {
207       if (!ramIfYouCan(e.getName(), e.getBearing(), e.getEnergy())) {
208         shootRobot(e.getVelocity(), e.getHeading(), e.getBearing(),
209                    e.getDistance(), e.getEnergy());
210       }
211       else {
212         m_dRamBearing = e.getBearing();
213         m_dRamEnergy = e.getEnergy();
214       }
215     }
216     else {
217       if (getOthers() == 1) {
218         /**
219          * Sinon, on tire sur le robot et on se place
220          * perpendiculairement à lui à condition que ça soit le seul
221          * robot (1v1)
222          */
223         shootRobot(e.getVelocity(), e.getHeading(), e.getBearing(),
224                    e.getDistance(), e.getEnergy());
225         turnRight(Utils.normalRelativeAngleDegrees(e.getBearing()+90));
226         ahead(MoveDistance*m_iDirection);
227        }
228       else if (m_iScanned == 0) {
229         /**
230          * Si c'est le dernier robot scanné, on lui tire dessus
231          */
232         shootRobot(e.getVelocity(), e.getHeading(), e.getBearing(),
233                    e.getDistance(), e.getEnergy());
234         turnRight(Utils.normalRelativeAngleDegrees(e.getBearing()+90));
235         ahead(MoveDistance*m_iDirection);
236       }
237     }
238   }
239
240   /**
241    * Fonction appelée lorsqu'un tir est raté.
242    * On fuit si on rate trop de tirs
243    */
244   public void onBulletMissed(BulletMissedEvent e) {
245     m_iUnhit += 1;
246     if (m_iUnhit > MaxUnhit)
247       runAway();
248   }
249
250   /**
251    * Fonction appelée lorsqu'un tir touche un joueur
252    */
253   public void onBulletHit(BulletHitEvent e) {
254     m_iUnhit = 0;
255   }
256
257   /**
258    * Fonction appelée lorsqu'on est touché par une balle.
259    * On fuit si on est trop touché sans bouger
260    */
261   public void onHitByBullet(HitByBulletEvent e) {
262     m_iHittedWithoutMove += 1;
263     if (m_iHittedWithoutMove > MaxHits)
264       runAway();
265   }
266
267   /**
268    * Fonction appelée lorsqu'on se cogne contre un mur.
269    * On change la direction du déplacement
270    */
271   public void onHitWall(HitWallEvent e) {
272     m_iDirection = -m_iDirection;
273
274     /* Si on est dans un coin */
275     if ((getX() < 150 || getX() > getBattleFieldWidth() - 150) &&
276         (getY() < 150  || getY() > getBattleFieldHeight() - 150)) {
277       turnRight(90);
278       runAway();
279     }
280   }
281
282
283   /**
284    * Fonction appelée lorsqu'on est cogné par un autre robot.
285    * Si son énergie est supérieur à la notre, on lui tire dessus
286    * et on fuit.
287    * Si son énergie est inférieure à la notre, et que c'est le
288    * seul ennemi restant, on le ramme.
289    * Si il y a plus d'un ennemi, on le ramme si son énergie est
290    * très petite.
291    */
292   public void onHitRobot(HitRobotEvent e) {
293     if (e.getEnergy()-20 > getEnergy()) {
294       m_sRamming = "";
295       shootRobot(.0, .0,  e.getBearing(), .0, e.getEnergy());
296       runAway();
297     }
298     else if (e.getEnergy() < AlmostDead) {
299       if (!ramIfYouCan(e.getName(), e.getBearing(), e.getEnergy())) {
300         shootRobot(.0, .0, e.getBearing(), .0, e.getEnergy());
301         runAway();
302       }
303       else {
304         m_dRamBearing = e.getBearing();
305         m_dRamEnergy = e.getEnergy();
306       }
307     }
308     else if (getOthers() == 1) {
309       if (!ramIfYouCan(e.getName(), e.getBearing(), e.getEnergy())) {
310         shootRobot(.0, .0, e.getBearing(), .0, e.getEnergy());
311         runAway();
312       }
313       else {
314         m_dRamBearing = e.getBearing();
315         m_dRamEnergy = e.getEnergy();
316       }
317     }
318   }
319
320   /**
321    * Fonction appelée à la mort d'un autre robot.
322    * Si c'est le robot qu'on rammait, on arrête de rammer
323    */
324   public void onRobotDeath(RobotDeathEvent e) {
325     if (e.getName() == m_sRamming)
326       m_sRamming = "";
327   }
328     
329
330   /**
331    * Fonction appelée lorsqu'on a gagné.
332    * Petite danse de victoire
333    */
334   public void onWin(WinEvent e) {
335     //setAdjustGunForRobotTurn(false);
336     //setAdjustRadarForRobotTurn(false);
337     turnGunRight(720);
338   }
339
340   /**
341    * Tire sur le robot.
342    * Si on tire une balle de puissance proche de 3, comme les balles
343    * sont fort lentes, on estime où le robot se situera à l'impact, et
344    * on tire à cet endroit.
345    * Dans las autre cas, on tire directement là où se trouve le robot
346    * @param velocity La vitesse du robot (récupérée avec e.getVelocity())
347    * @param heading L'angle où regarde le robot (e.getHeading())
348    * @param bearing L'angle entre nous et le robot (e.getBearing())
349    * @param distance La distance entre nous et la cible(e.getDistance())
350    * @param energy L'énergie de la cible (e.getEnergy())
351    */
352   public void shootRobot(double velocity, double heading, double bearing,
353                          double distance, double energy) {
354     /* On ne bouge le canon que si on pourra tirer */
355     if (getGunHeat() == 0) {
356       double power = powerForMaxDamage(energy, distance);
357       double absoluteBearing = getHeading() + bearing;
358
359       if (power + 0.3 > 3 || velocity - 0.01 > 0) {
360         turnGunRight(Utils.normalRelativeAngleDegrees(
361                       absoluteBearing -
362                       (getGunHeading() +  
363                        velocity *
364                        Math.sin(Math.toRadians(getHeading() -
365                                                absoluteBearing)/13))));
366       }
367       else 
368         turnGunRight(Utils.normalRelativeAngleDegrees(
369                        bearing - (getGunHeading() - getHeading())));
370
371       fire(power);
372     }
373   }
374
375   /**
376    * Ramme le robot si c'est avantageux
377    * @param name Le nom du robot
378    * @param bearing Le bearing du robot
379    * @param energy L'énergie restante au robot
380    * @return true si on effectue le ram, false sinon
381    */
382   public boolean ramIfYouCan(String name, double bearing, double energy) {
383     if (energy < AlmostDead || getOthers() == 1 
384          || energy - 20< getEnergy()) {
385       double gunAngle;
386       m_sRamming = name;
387       m_lLastRam = getTime();
388
389       gunAngle =
390         Utils.normalRelativeAngleDegrees(getHeading()-getGunHeading());
391
392       setAdjustRadarForRobotTurn(false);
393       if (bearing > 90 || bearing < -90) {
394         turnLeft(Utils.normalRelativeAngleDegrees(180-bearing));
395         turnGunLeft(Utils.normalRelativeAngleDegrees(180-gunAngle));
396       }
397       else {
398         turnRight(bearing);
399         turnGunRight(gunAngle);
400       }
401
402       fire(powerForMaxDamage(energy, .0));
403
404       if (bearing > 90 || bearing < -90)
405         back(100);
406       else
407         ahead(100);
408
409       return true;
410     }
411     else {
412       m_sRamming = "";
413       return false;
414     }
415   }
416
417   /** 
418    * Le robot doit fuire, il s'éloigne de la position d'où il est
419    */
420   public void runAway() {
421     m_iHittedWithoutMove = 0;
422     boolean ahead = true;
423
424     if (m_sRamming != "") {
425       m_sRamming = "";
426       ahead = false;
427     }
428
429     /* Si on se dirige vers un bord */
430     if ((getX() < 100 && between(getHeading(), 180, 360)) ||
431         (getX() > getBattleFieldWidth() - 100 &&
432               between(getHeading(), 0, 180)) ||
433         (getY() < 100 && between(getHeading(), 90, 270)) ||
434         (getY() > getBattleFieldHeight() - 100 &&
435               (getHeading() > 270 || getHeading() < 90)))
436       ahead = false;
437
438     if (ahead)
439       ahead(200);
440     else
441       back(200);
442   }
443
444   /**
445    * Calcule la puissance d'une balle pour tuer l'ennemi.
446    * Dans le cas où l'ennemi est éloigné, on ne tire pas des balles
447    * puissantes, les balles moins légères étant plus rapides, et nous
448    * font perdre moins d'énergie
449    * @param energy L'énergie du robot sur lequel on tire
450    * @param distance La distance entre nous et la cible
451    * @return La puissance de la balle à tirer
452    */
453   public double powerForMaxDamage(double energy, double distance) {
454     /* API de robocode :
455      * The bullet will do (4 * power) damage if it hits another robot.
456      * If power is greater than 1, it will do an additional 2 * (power
457      * - 1) damage
458      */
459     if (energy < 16.0) {
460       /* On a pas de bonus, mais on tire quand même, pour l'achever */
461       if (energy/4 < 1.1)
462         return energy/4;
463       else
464         return energy/6 + 1/3;
465     }
466     else if (distance > FarAway) {
467       return 1.1; /* Juste pour avoir le bonus de dégats */
468     }
469     else
470       return 3.0;
471   }
472
473   /**
474    * Change les couleurs du robot.
475    */
476   public void looksGood() {
477     Random gen = Utils.getRandom();
478     setBodyColor(new Color(gen.nextFloat(), gen.nextFloat(),
479                            gen.nextFloat()));
480     setBulletColor(new Color(gen.nextFloat(), gen.nextFloat(),
481                              gen.nextFloat()));
482     setGunColor(new Color(gen.nextFloat(), gen.nextFloat(),
483                           gen.nextFloat()));
484     setRadarColor(new Color(gen.nextFloat(), gen.nextFloat(),
485                             gen.nextFloat()));
486   }
487
488   /**
489    * Affiche des informations sur la sortie standard
490    * @param str Les informations à afficher
491    */
492   public void debug(String str) {
493     System.out.println("Debug : " + str);
494   }
495
496   /**
497    * Si un nombre est situé entre deux nombres
498    * @param x le nombre à tester
499    * @param a la plus petite borne de l'intervalle
500    * @param b la plus grande borne de l'intervalle
501    * @return true si x est compris dans l'intervalle [a,b]
502    */
503   public boolean between(double x, double a, double b) {
504     return a <= x && b >= x;
505   }
506 }
507