Programmiamo un CROBOT

Come si 'gioca':

CROBOTS è un compilatore ANSI 'C', non un videogioco interattivo! Il duello con gli avversari umani è pertanto solo mentale e per giunta a distanza. La sfida infatti consiste nel dotare il nostro alter-ego digitale di routine che gli consentano di sopravvivere nell'arena virtuale dove si troverà a combattere con altri programmi.

Domanda: ma come si scrive un robot? Risposta: in linguaggio C, è ovvio.

E se qualcuno non ha la più pallida idea di come si programma in C? Niente panico. Questo articolo è stato pensato proprio per quanti , nutrendo interesse per questa competizione, sono frenati dal timore reverenziale verso tale linguaggio. E poi CROBOTS è nato proprio per scopi didattici,quindi siate ottimisti!

Nonostante il manuale del gioco presenti già un paio di esempi, questi non sono né attuali (la crobotica negli ultimi anni ha fatto passi da gigante) né particolarmente semplici per l'utente inesperto, in quanto utilizzano da subito tutte le strutture e le possibilità permesse dal compilatore. Ho quindi deciso di seguire un percorso interamente italiano, partendo dallo studio di un crobot molto semplice (Erica.r), per arrivare poi, passando attraverso livelli di difficoltà crescente (Arale.re Jedi.r ), a quello più complesso (Goblin.r).

Ma adesso bando agli indugi e andiamo ad incominciare!

 

La dotazione iniziale:

Quello che ci serve per iniziare a sviluppare le nostre temibilissime macchine da battaglia è ridotto ai minimi termini:
  • Un computer (indispensabile)
  • Un sistema operativo
  • Un text editor che generi file in formato ASCII
  • Una versione a scelta di CROBOT tra quelle disponibili: AmigaDOS, Linux, MS-DOS
  • Tutti i sorgenti dei combattenti scritti finora, per attingere a piene mani al meglio della produzione mondiale.
  • La pazienza di allenare un Crobot.
Nonostante il programma abbia requisiti hardware veramente minimali,  per lo sviluppo di un robot competitivo è caldamente consigliato l'impiego di un computer potente, in modo da poter testare a lungo e in tutte le situazioni il comportamento della nostra creazione. Infatti, dal momento che l'andamento di una singola partita può essere influenzato anche da fattori casuali, èopportuno ripetere gli scontri un numero sufficientemente elevato di volte da garantire significatività statistica ai risultati ottenuti.

Il linguaggio di sviluppo:

Si tratta di un sottoinsieme dell'ANSI C, con alcune aggiunte che consentono un pieno controllo sul comportamento del robot, fornendogli le capacità di muoversi, cercare gli avversarie sparare loro. Non è presente un pre-compilatore, né è implementata alcuna gestione dei file. In conseguenza di ciò il nostro programma non può avere una memoria. Ogni partita per luiè la prima e l'ultima. Oggi esiste comunque PPC, un preprocessore di comandi esterno scritto da Marco Giovannini, che risolve parzialmente il primo limite. La gestione degli errori lascia a desiderare: nel caso se ne verifichi uno (per esempio una divisione per 0) il compilatore resetta semplicemente tutte le variabili del programma e riinizia ad eseguirlo dalla prima istruzione della procedura principale. Inoltre è presente un bugnella funzione che restituisce la distanza del nemico: la scan()non vede gli avversari che si trovano nel cono compreso tra 0 e -10 gradi. Questo, unito al fatto che il compilatore considera sempre il valore assoluto degli angoli che gli passiamo, può a volte generare dei problemi . Ogni robot ha a disposizione 999 VirtuaByte per il codice,e ciascuna istruzione occupa esattamente lo stesso ammontare di memoria.Ciò consente la stesura di programmi di circa 100/120 linee. Questo limite, inizialmente non molto sentito (i primi robot arrivavano a stentoai 100 VB), è oggi l'ostacolo più grande contro cui si infrangono gli sforzi dei crobotters, ma è anche grazie alla sua presenza che l'ottimizzazione delle routine ha raggiunto livelli incredibili. Solo per fare un esempio osservate l'evoluzione della procedura per il calcolo dell'angolazione necessaria a raggiungere un determinato punto:

Questa è quella fornita nel manuale:

 
plot_course(xx,yy) /*Inizia la procedura, passandogli come parametri
    le coordinate della destinazione.....*/
int xx, yy;              /*....che vengono dichiarate esternamente alla
     procedura stessa*/
 {
  int d;                    /*Dichiara le variabili che userà nella procedura*/
  int x,y;                 /*Poichè il compilatore e` molto semplificato */
  int scale;             /*non gestisce i numeri in virgola mobile*/
  int curx, cury;     /*ma solo gli interi*/
  scale = 100000;  /*quindi le funzioni trigonometriche restituiscono
   un risultato compreso tra 1 e 10000 anzichè tra
   0 e 1*/
  curx = loc_x();   /*Memorizza in due variabili la posizione
    attuale*/
  cury = loc_y();
  x = curx - xx;
  y = cury - yy;
 /*Poichè l' arcotangente (atan) restituisce un
    compreso tra -90 to +90, per ottenere un
   risultato utile sui 360 gradi dobbiamo
   modificarlo con altre informazioni di cui
   disponiamo.........*/
  if (x == 0)           /*....ed è proprio ciò di cui si occupa questa
    parte*/
  {
    if (yy > cury)
      d = 90;
   else
      d = 270;
  }
 else
  {
    if (yy < cury)
     {
      if (xx > curx)
        d = 360 + atan((scale * y) / x); /*Ci troviamo nel quadrante di sud-est*/
      else
        d = 180 + atan((scale * y) / x); /*Siamo nel quadrante di sud-ovest */
      } 
   else
   {
      if (xx > curx)
        d = atan((scale * y) / x);        /*Siamo nel quadrante nord-est */
      else
        d = 180 + atan((scale * y) / x); /*E questo e` il quadrante nord-ovest*/
    }
  }

 

Questa era inclusa in un crobot del 1997:

 
Go(x1,y1)
  {
    int curx,nx,ny,dist; /* Calcola la direzione per recarsi al punto  stabilito: */
    nx=(curx=loc_x())-x1;
    ny=(loc_y()-y1)*100000;
    if (x1>curx) dir=360+atan(ny/nx);
           else dir=180+atan(ny/nx);
  }

 

E questa infine viene dalla collezione autunno-inverno 1998:

 
vai(xx,yy,vel)
{
 drive(dir=180+180*(xx>loc_x())+atan(100000*(loc_y()-yy)/(loc_x()-xx)),vel);
 }

 

È interessante notare che la compattezza si paga:le ultime due routine contengono infatti un errore. Nel caso in cui il punto di partenza e quello di arrivo abbiano la stessa ascissa ci si trova davanti a una divisione per 0, con il risultato di far ripartire il crobot dalla prima istruzione. L'evento è quanto mai raro, ma se volete stare sul sicuro usate la procedura del manuale e vivete felici.

Le Convenzioni da usare:

Del formato del file, che deve essere ASCII puro, per permettere al compilatore di eseguirlo correttamente, vi ho già parlato. Aggiungo adesso che è opportuno salvare il file con estensione *.r, per permettere a chiunque di identificarlo all'istante come il sorgente di un crobot. è poi consigliabile identare e commentare abbondantemente il listato, spiegando la strategia della vostra creatura e il funzionamento delle routine principali: mettetevi nei panni di chi deve leggerlo. Come vi trovereste a dover decifrare del codice disordinato e senza la più piccola nota esplicativa? Potreste arenarvi su procedure che, con l'aiuto di un semplicissimo:

/* .....(Tutto quello che c'è all'interno di /*......*/ è solo un commento e non viene eseguito)....*/

vi sarebbero immediatamente chiare. Infine, mentre è possibile impiegare indifferentemente lettere maiuscole e minuscole per i nomi delle variabili e delle procedure che andremo a definire (solo le prime sette lettere sono significative), le istruzioni proprie del compilatore vanno inserite in minuscolo.

Gli accessori esterni:

Si tratta di un set di quattro programmi, scritti con lo scopo di facilitare le fasi di sviluppo e di test di un crobot.  
PPC di Marco Giovannini: preprocessore di comandi per crobots.
Torneo2k di Michelangelo Messina: potente gestore di tornei, in grado di far disputare competizioni sia in modalità 4vs4 che f2f.
Count di Simone Ascheri e Michelangelo Messina: analizzatore di report per Crobots. Per ora è l'unico a supportare l'assegnazione dei punteggi in modalità Pranzo.

CrobotsHelper di Marco Pranzo: coordina il lavoro dei programmi di cui sopra.

E.C.A.T. ovvero Enhanced Crobots Auto Trainer:dalla mente malata del sottoscritto e dalla geniale tastiera di MichelangeloMessina uno strumento per il fine tuning dei parametri dei crobot. Richiedela presenza di PPC 0.9.

Crutils di Maurizio "Joshua" Camangi: insieme di utilities per la gestione di un torneo.

Dal momento che non mi sembra ci sia altro da aggiungere, possiamo passare senza indugio ad...

Analizzare il nostro primo Crobot:

 
   /* ERICA.R è un robot in C
  Leggi il file ERICA.TXT per maggiori informazioni
  ERICA.R è stato scritto da... XXXX YYYY
 Esempio di crobot breve, commentato e di facile lettura*/
 int     direz, alfa, range, dato, grado;
 main()
 {
   direz = rand(360);                   /* DEFINISCE UNA DIREZIONE A CASO */
   grado = rand(360);                  /* DEFINISCE LA DIREZION DEL  CANNONE */
   while (1)
      {
     drive(direz, 100);
      if (loc_y() > 850 )                /* GIUNTO AI MARGINI CAMBIA */
      direz = 180 + rand(180);      /* DIREZIONE CASUALMENTE */
       if (loc_y() < 150)
             direz = rand(180);
       if (loc_x() > 850 )
           direz = 90 + rand(180);
       if (loc_x() < 150 )
           direz = 270 + rand(180);
      range = scan (grado,20);     /* RICERCA PRESENZA ROBOTS */
       if (range > 0 )
            {
             cannon(grado, range);  /* CE N'è UNO? FUOCO!!!! */
             cannon(grado, range);
            }
         else grado = grado+20;    /* NON C'ERA? INCREMENTA ANGOLAZIONE */
        }
  }

 

è un crobot molto semplice e anche molto ben commentato. Un grazie all'autore, che probabilmente non immaginava l'impiego della sua opera in questo tutorial. Analizziamolo un pò in dettaglio (i numeri di linea non fanno parte del programma e non devono essere inseriti:sono stati aggiunti per facilitare la spiegazione):

  • Le linee dalla 1 alla 7 sono dei semplici commenti, il compilatore le ignora
  • Alla linea 8 sono dichiarate le variabili: è INDISPENSABILEfarlo nella procedura principale (pena strani e incomprensibili malfunzionamenti del crobot), mentre per le sub-routine è solo facoltativo.
  • Alla linea 9 inizia il programma vero e proprio: mentre i nomi di tutte le procedure possono essere scelti arbitrariamente, il corpo principale DEVE chiamarsi
 
main()
 {
  /* Tutto quello che sta tra le due parentesi graffe è il corpo principale del programma*/
 } 
  • Le linee 11 e 12 scelgono casualmente, tramite la funzionerand(), alcuni parametri (la direzione di marcia e il puntamento del cannone)
  • La linea 13 inizia un ciclo senza fine: tutto ciòche sta tra le parentesi graffe delle linee 14 e 31 viene ripetuto fino alla distruzione del robot o al termine della partita. Questo accade perchè l'istruzione while(1) è sempre vera: per interrompere il loop bisognerebbe infatti che l'espressione tra parentesi fosse nulla, ma essendo il numero 1 una costante, non può mai assumere valore 0.

Per ora tutto chiaro vero? Dopotutto fino a questo si tratta di banalissime istruzioni in C standard. Ma adesso viene il bello. Stiamo per analizzare quei comandi che ci permettono di controllare ilnostro crobot, e di rendere il suo comportamento diverso da quello di qualsiasialtro combattente in circolazione:

  • Troviamo infatti per prima cosa le funzioni loc_x() e loc_y(). L'arena è un quadrato di lato 1000 unità con l'origine degli assi (il punto 0,0) fissata nell'angolo in basso a sinistra (esattamente come nel piano cartesiano standard). Le 2 funzioni restituiscono un valore compreso tra 0 e 999 che corrispondenti al punto in cui si trova il nostro robot all'interno del campo di battaglia.
  • Alla linea 24 incontriamo la funzione scan(ang,ampiezza):rappresenta gli occhi del nostro robot. Essa è in grado di individuare i robot nemici guardando nella direzione che le indichiamo con ang e conl'apertura che scegliamo. Più l'ampiezza è elevata (puòessere di 10 gradi al max alla destra e alla sinistra della direzione scelta,per un totale di 20 gradi) più è probabile trovare il nemico,ma anche meno facile centrarlo con un proiettile. La funzione restituisce un numero intero compreso tra 1 e 999 che rappresenta la distanza a cui è stato individuato il nemico, oppure 0 se non è stato trovato nulla.
  • La linea 25 controlla che ci sia un nemico: in effetti è persino troppo lunga. In caso fossimo a corto di spazio per il codice l'espressione if (range) sarebbe una adeguata sostituta.
  • Le linea 27 spara al nemico nella direzione scelta e con la gittata che abbiamo ricavato dalla scan(). È importante ricordarsi che l'arena ha lati di 1000 metri: il nemico può quindi essere distante da noi fino a 1440 unità, mentre la massima distanza acui il proiettile è efficace sono 700 metri. Pertanto, se la scan() trova un nemico lontano da noi 900 unità è inutile fare fuoco.
  • La linea 28 è inutile: il cannone deve avere il tempo di ricaricarsi dopo aver esploso un colpo. A questo proposito il manuale di Crobots per AMIGA riporta una tabella dei tempi richiesti dalle varie funzioni che non è presente nella versione Dos/Linux. Eccola qui.
    Azione Cicli di CPU
    Ricarica PPC 150
    Accelera/Decelera 10% 10
    Pulse Travelling 50 metri 10
    Il robot si muove di 7 metri a velocità 100 1

Leggetela attentamente perchè vi sarà molto utile in futuro, soprattutto quando esamineremo le routine di fuoco derivate da Tox.r

Un paio di considerazioni: Erica.r è un ottimo programma per imparare le basi di Crobots, ma non aspettatevi di riuscire a vincere con esso una partita contro i combattenti moderni. Innanzitutto si muove a caso nelle zone centrali dello schermo, e quindi è predisposto a ricevere colpi da tutti gli altri concorrenti. In secondo luogo la routine di fuoco è assolutamente primitiva. Non effettuando correzioni né sull'angolo né sulla distanza di sparo è assolutamente probabile che quando il proiettile raggiunge l'obiettivo, questo non sia più lì. A questo proposito il manuale riporta questa tabellina dei casi in cui un crobot può subire dei danni:

Situazione Danni
Collisione contro il muro o con un nemico (spegne il motore) 2%
Missile che cade a 40 metri 3%
Missile che cade a 20 metri 5%
Missile che cade a 5 metri 10%

Prestate attenzione particolarmente al primo punto. Se il vostro crobot si blocca vicino ad un lato o nel mezzo dell'arena potrebbe essere quella la causa.

Comunque direi che è ora di mettere da parte Erica.r per passare a studiare un crobot che supera i limiti che ho evidenziato finora.  è il momento di esaminare...

...Il mio crobot preferito: Arale.r:

Questo programma è per molti versi il mio ideale di crobot: Innanzitutto ha una presentazione simpatica e divertente. In secondo luogo è versatile: non si fossilizza su una posizione, ma è programmato per poter spaziare in tutti i quattro angoli dell'arena a seconda della locazione in cui nasce. Infine i suoi autori sono tra i pochi in circolazione che non battono sentieri ormai consolidati ma si sforzano di esplorare strade sempre nuove. L'unico difetto è il sorgente scarsamente commentato.

 

 
/*                                      
     AAAA   RRRRR    AAAA   LL      EEEEEE
    AA  AA  RR  RR  AA  AA  LL      EE
    AAAAAA  RRRRR   AAAAAA  LL      EEEE
    AA  AA  RR RR   AA  AA  LL      EE
    AA  AA  RR  RR  AA  AA  LLLLLL  EEEEEE
   Nome      : Arale.r  (30-09-97)
   Creatore  : Senbee Norimaki (Dr. Slump)
   Autori    : XXXXX YYYYYY
                                       
 Tu costruisci robot
 belli e geniali però
 tutti senza qualche rotella!
 ...
 Il robot Arale  all'inizio della partita si dirige verso l'angolo più vicino
 e comincia a muoversi rapidamente avanti ed indietro, giocando con gli altri
 robot suoi amici  con gli attacchi  Hoyoyo (più preciso), ed  Gupi_Gupi (più
 veloce).  Dopo 100000 cicli Arale si annoia e finisce la pazienza, allora si
 sposta  al centro dell'arena  e  fa  l'aereoplano  (WOOOOOOOSH!)  muovendosi
 velocemente  da un lato  all'altro e  sparando ai suoi amichetti superstiti.
                                      */
int
 posy,        /* Luogo di nascita */
 pazienza, /* Pazienza di Arale */
 ang,d;       /* Angolo e distanza degli amici di Arale */
main()
 {
/*  C I R I C I A O  */
 pazienza=100;
 posy=loc_y();
   if (loc_x() > 500) if (posy > 500)
     {
      /* Angolo Nord-Est */
  drive(90,100); while (loc_y()<930) hoyoyo();
  drive(90,0);   gupi_gupi();
  drive(0,100);  while (loc_x()<930) hoyoyo();
  drive(0,0);    gupi_gupi();
  while (pazienza-=1)
   {
    drive(180,100); while(loc_x() > 800) hoyoyo();
     drive(180,0);  gupi_gupi();
    drive(0,100);   while(loc_x() < 930) hoyoyo();
     drive(0,0);     gupi_gupi();
    }
   drive(270,100); while (loc_y() > 520) hoyoyo();
   drive(270,0);   gupi_gupi(); 

 

  • La main() è composta da 5 blocchi, di cui i primi 4 sono strutturalmente identici: descrivono infatti il comportamento del robot in relazione al quadrante in cui è posizionato. Le linee tra la 39 e la 54 si riferiscono all'angolo Nord-Est.
  • Le linee dalla 42 alla 45 posizionano Arale.r nel punto da cui inizierà il suo movimento oscillatorio
  • Alla 46 inizia un loop, che dura circa 100000 cicli virtuali di CPU. Si arresta quando la variabile "pazienza" raggiunge il valore 0 (false).
  • Le linee tra la 48 e la 51 forniscono il moto oscillante al Crobot e richiamano le due funzioni di sparo di cui è dotato. Questa strategia consente di essere sufficientemente mobili da non diventare facilibersagli dei nemici che girano nello schermo, ma anche di rimanere lontanida quelli che si annidano in un angolo e aspettano gli avversari. Le continue inversioni di marcia poi, rendono più difficile il calcolo del terzo punto di fuoco tramite interpolazione.
  • Terminato il ciclo di attesa le linee 53 e 54 portano Arale alle coordinate (900,500), per iniziare la fase finale di attacco.
 
}
  else
  {
  /* Angolo Sud-Est */
   drive(0,100);   while (loc_x()<930) hoyoyo();
   drive(0,0);     gupi_gupi();
   drive(270,100); while (loc_y()>70) hoyoyo();
   drive(0,0);     gupi_gupi();
   while (pazienza-=1)
    {
     drive(90,100);  while(loc_y() < 200) hoyoyo();
     drive(90,0);    gupi_gupi();
     drive(270,100); while(loc_y() >  70) hoyoyo();
     drive(270,0);   gupi_gupi();
    }
   drive(90,100); while (loc_y() < 480) hoyoyo();
   drive(90,0);   gupi_gupi();
  •   Vedi la prima parte: l'angolo è quello Sud-Est
 
}
  else if (posy > 500)
  {
  /* Angolo Nord-Ovest */
   drive(180,100); while (loc_x()>70) hoyoyo();
   drive(180,0);   gupi_gupi();
   drive(90,100);  while (loc_y()<930) hoyoyo();
   drive(0,0);     gupi_gupi();
   while (pazienza-=1)
    {
     drive(270,100); while(loc_y() > 800) hoyoyo();
     drive(270,0);   gupi_gupi();
     drive(90,100);  while(loc_y() < 930) hoyoyo();
     drive(90,0);    gupi_gupi();
    }
   drive(270,100); while (loc_y() > 520) hoyoyo();
   drive(270,0);   gupi_gupi();
    drive(90,0);   gupi_gupi(); 
  •   Vedi la prima parte: l'angolo è quello Nord-Ovest
 
}
  else
  {
  /* Angolo Sud-Ovest */
   drive(270,100); while (loc_y()>70) hoyoyo();
   drive(270,0);   gupi_gupi();
   drive(180,100); while (loc_x()>70) hoyoyo();
   drive(0,0);     gupi_gupi();
   while (pazienza-=1)
    {
     drive(0,100);   while(loc_x() < 200) hoyoyo();
     drive(0,0);     gupi_gupi();
     drive(180,100); while(loc_x() > 70) hoyoyo();
     drive(180,0);   gupi_gupi();
    }
   drive(90,100); while (loc_y() < 480) hoyoyo();
   drive(90,0);   gupi_gupi();
     drive(90,0);   gupi_gupi(); 
  •   Vedi la prima parte: l'angolo è quello Sud-Ovest
 
}
/* WOOOOOOSH!! (Aereoplano)*/
while(1)
 {
  drive(0,100);  while (loc_x() < 900) if (scan(ang,10))
     cannon(ang+=7*(!(scan(ang+356,7)))+353*(!(scan(ang+4,7))),scan(ang,10));
                                       else ang+=21;
  drive(0,0);     gupi_gupi();
  drive(180,100); while (loc_x() > 100) if (scan(ang,10))
     cannon(ang+=7*(!(scan(ang+356,7)))+353*(!(scan(ang+4,7))),scan(ang,10));
                                        else ang+=21;
  drive(180,0);   gupi_gupi();
 }
  • Quest'ultimo blocco della main() è quello che si occupa dell'attacco finale. Indipendentemente dal numero dei sopravvissuti,o dal livello dei danni personali subiti, Arale va allo sbaraglio. Non sarebbe male invece fare almeno un conto del numero degli avversari rimasti in campo. Vedremo poi con Goblin come questo sia possibile, nonostante non esista nel compilatore una funzione apposita.
  • Il ciclo è infinito: quando inizia la fase finale Arale non può più pentirsi e tornare indietro.
  • La strategia consiste nel muoversi parallelamente all'asse delle ascisse avanti e indietro alla massima velocità (linee 5 e 9)
  • Le routine di fuoco utilizzate sono di nuovo due: La gupi_gupi(),per lo sparo veloce, che viene impiegata solo mentre il crobot rallenta, e un'altra, senza nome, che trovate alle righe 6 e 10.
  • Quest'ultima cerca di combinare la rapidità e la precisione del tiro: il range viene ricavato pochi istanti prima che il proiettile parta, mentre l'angolo di fuoco viene corretto 'al volo'aggiungendo e sottraendo il risultato di due scan() successive effettuate intorno alla direzione scelta.
 
}
/* Routine di attacco standard */
hoyoyo()
 {
 if ( (d=scan(ang,10)) && (d<770) )
  {
   if (d=scan(ang+353,3)) cannon(ang+=353,d);
   else if (d=scan(ang,3)) cannon(ang,d);
   else if (d=scan(ang+7,3)) cannon(ang+=7,d);
 }
else
 {
   if ((d=scan(ang+21,10))&&(d<700)) {ang+=21;cannon(ang,d);}
   else if ((d=scan(ang+42,10))&&(d<700)) ang+=42;
        else ang+=63;
 }
  • La hoyohoyo() è la routine di fuoco più precisa di cui dispone Arale. è anche molto compatta, e questo a volte può rivelarsi un inconveniente: è infatti possibile che quando viene trovato un nemico il cannone non sia ancora pronto per sparare, perdendo così l'opportunità di danneggiare l'avversario.
  • Il funzionamento è semplice: se la linea 6 trova un robot abbastanza vicino le linee tra la 7 e la 12 cercano di puntarlo con maggiore precisione riducendo l'ampiezza della scansione, e poi fanno fuoco.
  • Se invece un nemico non c'è, la linea 10 prova a cercarlo nei 20 gradi successivi.
  • Se neanche questa volta si è avuto successo lo scan() si sposta in avanti di altri 20 gradi.
 
}
/* Routine di attacco veloce */
gupi_gupi()
 {
  while (speed() > 49) if ((d=scan(ang,10))) cannon(ang,d);
                      else ang+=21;
 }
 
/*   -  C I R I C I A O    G E N T E ! ! !     -*/
  • La gupi_gupi() è la routine di attacco veloce: non effettua alcuna correzione né sull'angolo né sulla distanza. Per questo viene usata solo in condizioni particolari come il rallentamento prima di cambiare direzione. Dal momento che il cambio di direzione è consentito solo a velocità inferiori alle 50 unità, il ciclo di while() viene eseguito fino a quando la funzione speed() non ritorna un valore minore di 49. Questo dà il tempo di richiamare la funzione di fuoco almeno una mezza dozzina di volte.
Benissimo, se siete arrivati a leggere fino a questo punto probabilmente siete interessati a programmare un crobot. Arale è certamente un ottimo combattente, tant'è vero che è riuscito a piazzarsi quarto nel torneo del 1997. Presenta tuttavia ancora delle limitazioni:
  • Innanzitutto non controlla mai se il suo angolo è occupato.
  • Non tiene conto dei danni che subisce.
  • Non si cura di quanti nemici sono in circolazione prima di attaccare.
  • E infine  la routine di fuoco ha ancora il problema del caricamento del proiettile.
Supereremo i primi tre problemi esaminando Goblin. Ora invece è opportuno che abbiate qualche nozione sulle routine di sparo più precise che si trovino al momento in circolazione. Vediamo un pò di che si tratta.

Le routine di fuoco:

  Esse risolvono una volta per tutte il problema del cannone scarico, a prezzo però di un elevato ingombro in termini di codice e di tempo macchina. Nella loro versione originale sono state inserite nel 1995 nel Crobot Tox.r. Da allora hanno subito solo modifiche marginali e sembra proprio che allo stato attuale delle cose nessun programmatore che aspiri alla vittoria possa farne a meno.

 

 
Tox()
    {
     if(scan(ang,5))
      {
       if(scan(ang-5,1)) ang-=5;
       if(scan(ang+5,1)) ang+=5;
       if(scan(ang-3,1)) ang-=3;
       if(scan(ang+3,1)) ang+=3;
       if(scan(ang-1,1)) ang-=1;
       if(scan(ang+1,1)) ang+=1;

 

  • Il blocco di sei scan() qui sopra, e il suo gemello posto immediatamente sotto, hanno una duplice funzione: ridurre l'ampiezza della ricerca per ottenere dei risultati sempre più accurati, e fornire una temporizzazione efficace. Infatti il compilatore non ha alcuna istruzione per permetterci di conoscere quanto tempo è trascorso da una certa operazione in poi. Bisogna quindi simulare tale funzione.
 
if (range=scan(ang,5))
         {
           orange=range;
           oang=ang;
  • Se viene trovato nuovamente un nemico, le informazioni relative a distanza ed angolo vengono memorizzate, dopo di che si procede a raffinare nuovamente la ricerca come nel punto precedente.
 
if(scan(ang-5,1)) ang-=5;
if(scan(ang+5,1)) ang+=5;
if(scan(ang-3,1)) ang-=3;
if(scan(ang+3,1)) ang+=3;
if(scan(ang-1,1)) ang-=1;
if(scan(ang+1,1)) ang+=1;
 if (range=scan(ang,10))
   {
     aa=(ang+(ang-oang)*((1200+range)>>9)-(sin(ang-an)>>14));
     rr=(range*160/(160+orange-range-(cos(ang-an)>>12))); 
  • Adesso, conoscendo la posizione del nemico in un dato momento e il suo spostamento dopo un certo periodo, è possibile interpolare angolo e distanza per sparare poi nella posizione dove è più probabile si sia traferito. è il compito che svolgono le due linee qui sopra. Per sapere da dove arrivano i calcoli effettuati in queste due linee, si deve seguire questo interessante link.
 
while(!cannon(aa,rr)); 
  • Dato che è comunque possibile che il cannone non sia ancora carico quando siamo pronti a sparare, la while ripete l'operazione fino a che il proiettile non parte.
 
if (range>700) ang+=30;
               }
             else if(scan(ang-=10,10));
                  else if(scan(ang+=20,10));
                       else while ((scan(ang+=11,10))== 0);
  • Questo è il primo dei tre blocchi che, in caso di assenza di nemici nel punto iniziale, ampliano la ricerca nei 20 gradi circostanti.
 
}
       else if(scan(ang-=10,10));
            else if(scan(ang+=20,10));
                 else while ((scan(ang+=11,10))== 0);
      }
    else if(scan(ang-=10,10));
         else if(scan(ang+=20,10));
              else while ((scan(ang+=11,10))== 0);
}
Quella sopra, e la sua variante per gli spari da fermo, sono senz'altro delle routine eccezionali. Tuttavia mirare bene non basta per vincere: serve anche una buona strategia durante l'incontro e soprattutto un buon attacco finale.

Gli attacchi finali:

  Le routine finali che esaminiamo provengono da due partecipanti all'edizione 1999 del torneo di Crobots. La prima è un'evoluzione dell'attacco di Arale.r realizzata dal solito Daniele Nuzzo. La seconda è completamente nuova. Si trova nel crobot iLbEsTiO.r e si è dimostrata efficacissima negli scontria singolar tenzone. Una sua versione più cattiva è implementata in Satana.r,ma essendo questo un robot realizzato esclusivamente per il f2f, preferisco analizzare il primo. Ambedue le routine impiegano le Toxiche come funzioni di fuoco.

Stealth attack:

 
Attacca()
{
  up(450); dw(550);
  dam=damage();
  while(damage()<=dam+20)
            {
                dam=damage();
                dx(850);
                 sx(150);
             }
  while (1)
            {
                dx(650);
                sx(350);
            }
}

La funzione di attacco di Stealth.r è concettualmente molto semplice, ma nonostante ciò è efficacissima. Quando il robot scopre di avere un unico avversario si porta al centro dell'arena. Inizia quindi ad oscillare orizzontalmente lungo tutta l'ampiezza del campo di battaglia, sparando con una funzione Tox-like. Se in un solo passaggio subisce più del 20% di danni accorcia l'oscillazione. La funzione di fuoco non è la Toxica originale,bensì una delle sue implementazioni più letali: la fire() di Coppi.r (1998) leggermente modificata per aumentarne ulteriormente l'efficienza.

iLbEsTiO attack:

 
main()
 {
/*[...]*/
         while (drive(dir,100))
            {
              serpiko();
              if ( flag)
                if (loc_y()>700) frena(270);
                else if (loc_y()<300) frena(90);
                     else if (loc_x()>700) frena(180);
                          else if (loc_x()<300) frena(); else ;
              else frena(ang+135+90*(clock^=1));
            }
/*[...]*/
}

Qui le cose iniziano a complicarsi un pò: la funzione di attacco è micidiale, ma è anche assai ostica. Dunque....... durante i rallentamenti (frena()) la variabile flag viene posta uguale a tre: ciò permette al crobot di eseguire il ciclo di attacco al massimo 3 volte consecutive, prima di cambiare direzione. Quest'ultima viene calcolata considerando l'angolo formato con il crobot da attaccare. Se però il crobot si avvicina troppo ad uno dei bordi non attende che il contatore arrivi a zero, bensì rimbalza con un angolo prefissato e reimposta a 3 il flag. Lo scopo di questi frequenti cambi è ovviamente quello di disorientare le routine di fuoco avversarie, specie quelle che cercano di interpolare la nuova posizione del bersaglio.

 

 
frena(newdir)
 {
  drive(newdir,(flag=3));           /* freno                           */
  while (speed()>50)
            {              /* finchè freno sparo (ma veloce) */
                if (oldrng=scan(ang,10))
                    {
                          if (range=scan((ang=(6*(scan((ang+=(10*(scan((oldang=ang)+10,10)>0)-5))+10,10)>0)-3)+ang),10))
                                cannon(ang+(ang-oldang),range+(range-oldrng)*2);
                    }
              }
  return (drive((dir=newdir),100)); /* riaccelero                      */
 }

 

Come già detto tutto questo non basta ancora. Una buona routine finale è senz'altro un tassello importante, ma il comportamento nella fase centrale dell'incontro è fondamentale. A questo punto dovete andare avanti da soli. Tuttavia, esaminando i lavori di altri programmatori, è possibile farsi un'idea sulle strade da seguire. È dunque venuto il momento di cedere........
TRATTA DA : http://crobots.deepthought.it/home.php