Introduzione
Dopo aver visto lo scorso mese le basi per iniziare
a "giocare" con Robocode, cioè come
installarlo e come fare il primo "Bot", in
questo numero vedremo come funziona il simulatore di
battaglia e come costruire un robot più complesso,
ma anche più potente, usando come base l'AdvancedRobot.
Lungo il percorso avremo occasione di "ripassare"
un po' di matematica, necessaria per progettare algoritmi
avanzati.
Il
simulatore: l'architettura
Il simulatore di robocode è un sofisticato motore
disegnato sia per essere efficiente (deve simulare le
battaglie a velocità sostenuta), che flessibile
(consente la creazione di robot dalla logica complessa
con un accoppiamento molto basso). Un ringraziamento
va a Mathew Nelson, il creatore di Robocode, per aver
pubblicato una breve descrizione del meccanismo interno
di funzionamento di Robocode.
Il motore di simulazione è stato creato sfruttando
le possibilità offerte dai thread non-preemptive
delle ultime VM e le funzioni di rendering offerte dalle
librerie 2D incluse in Java.
Figura 1 - L'architettura del simulatore
Come
si nota dallo schema, il gestore delle battaglia dirige
la simulazione, comandando i thread dei singoli robot
e il sottosistema di rendering grafico, basato su AWT
e Java 2D.
Per
diminuire le possibilità di deadlock nell'accesso
alle risorse condivise, è stato necessario ridurre
l'accoppiamento tra il thread del gestore e quelli dei
robot: questo scopo è stato raggiunto dotando
ogni robot della sua coda di eventi. In questa maniera
è stato rimossa ogni possibilità di accesso
contemporaneo da parte dei vari thread.
Il
simulatore: la logica
Si può vedere il simulatore come un programma
che carica a run-time un insieme di plugin (i robot);
ognuno di questi plugin può accedere alle API
fornite ereditando dalla classe robocode.Robot.
Ogni robot è un singolo thread e all'interno
del metodo run() è contenuto il codice che sarà
eseguito durante la simulazione. In ogni istante, un
robot può chiamare un metodo fornito dalla classe
padre, e questo tipicamente potrebbe fermare l'esecuzione
del robot con una Object.wait().
Il
gestore delle battaglia è incaricato di gestire
il movimento dei robot, dei proiettili e il disegno
del campo di battaglia. Il tempo della simulazione è
identificato dal numero di frame disegnati sullo schermo.
E il frame rate è regolabile dall'utente.
In
ogni turno, il gestore "sveglia" i thread
di tutti robot e aspetta che questi termino il loro
turno chiamando un metodo bloccante. La durata di ogni
turno è tipicamente una decina di millisecondi,
e anche i robot dalla logica più complessa impiegano
1-2 ms con gli attuali sistemi di calcolo.
Vediamo
come funziona un turno di simulazione (in pseudo-codice):
fino
a che il round non è finito
disegna i robot, i proiettili e le esplosioni
per ogni robot
sveglia il thread
aspetta che il robot chiami
un funzione bloccante, fino ad un massimo intervallo
di
tempo
end
svuota le code degli eventi dei robot
sposta i proiettili, ed eventualmente genera
degli eventi nelle code dei robot
sposta i robot, ed eventualmente genere
gli eventi nelle code dei robot
finalizza il campo di battaglia e genera
eventualmente altri eventi nelle code dei robot
attende per adeguarsi al frame-rate impostato
end do
Da
notare che ad ogni turno il gestore non aspetta per
un infinito intervallo di tempo che un robot completi
le sue elaborazioni, ma, dopo un massimo periodo di
tempo viene, terminato il turno del robot e viene generato
un evento di tipo SkippedTurnEvent. Questo accade in
genere se c'è qualche errore nel codice del robot
(es loop infinito).
Il
sistema di rendering grafico attuale usa AWT e Java2D
e disegna sullo schermo ricevendo le istruzioni dal
gestore delle battaglia. E' stato implementato in modo
che fosse abbastanza disaccoppiato dal resto del sistema
per rendere possibile l'implementazione di sistemi di
rendering alternativi (già esiste un renderer
3D basato su OpenGL). Al momento il sistema di rendering
viene disattivato quando l'applicazione viene minimizzata,
per consentire alla simulazione di girare al frame-rate
massimo.
Costruiamo qualcosa di più
complesso: AdvancedRobot
Il
mese scorso abbiamo realizzato un robot ereditando dalla
classe Robot
public
class MokaRobot extends Robot
cioè
il MokaRobot aveva accesso alle API fornite della classe
Robot, tipo la turnLeft() e la ahead().
Una grossa limitazione di queste API è rappresentata
dal fatto che le chiamate ai metodi di "movimento"
sono bloccanti, cioè non restituiscono il controllo
al codice fino a che lo spostamento non è terminato.
Siccome queste operazioni potrebbero durare molti turni,
siamo impossibilitati dal fare operazioni per tutto
questo tempo.
Fortunatamente esiste la classe AdvancedRobot che ci
permette di riprendere il controllo. Per fare questo
dovremo dichiarare che il nostro nuovo robot eredita
da AdvancedRobot
public
class AdvancedMokaRobot extends AdvancedRobot
E'
importante notare che AdvancedRobot è una sottoclasse
di Robot e che il nostro AdvancedMokaRobot è
a sua volta una sottoclasse di AdvancedRobot.
Figura 2 - Diagramma delle classi
Ovviamente
dall'interno di AdvancedMokaRobot ora avremo a disposizione
sia i metodi della classe Robot che quelli della classe
AdvancedRobot.
Ora
vedremo come utilizzare le nuove possibilità
offerteci dalle nuove API.
I
metodi non bloccanti
Con
gli AdvancedRobot abbiamo la possibilità di modificare
il comportamento del nostro robot ad ogni turno. Un
turno è chiamato tick ed è relazionato
con i frame disegnati sul campo di battaglia. Durante
ogni turno il nostro codice riprende il controllo e
decide cosa fare con il robot: con i PC attuali si possono
eseguire molte linee di codice (e quindi prendere decisioni
molto complesse) ad ogni tick.
Tutti
i metodi che abbiamo incontrato fino ad ora sono bloccanti,
ossia restituiscono il controllo solo dopo aver eseguito
tutta l'operazione. Ora, poiché AdvancedMokaRobot
è una sottoclasse di AdvancedRobot, possiamo
usare tutta una serie di metodi non bloccanti, cioè
che restituiscono immediatamente il controllo al chiamante.
Esiste un metodo non bloccante per ognuno dei metodi
bloccanti visti in precedenza: ad esempio abbiamo setTurnLeft(),
setAhead(), setBack(), ecc
Notate che tutti i
nomi dei metodi sono composti da "set" e il
nome del metodo bloccante. Usando queste chiamate possiamo
fare compiere al nostro robot più movimenti contemporaneamente.
Iniziamo
con l'analizzare il codice di AdvancedMokaRobot per
vedere come questi metodi si usano in pratica.
protected
void reverseDirection() {
if(clockwise) {
setTurnRightRadians(PI);
setAhead(2000);
}
else {
setTurnLeftRadians(PI);
setBack(2000);
}
setTurnGunRightRadians(PI);
clockwise=!clockwise;
}
In
questa funzione, noi comandiamo al robot di girare di
360°, di andare avanti di 2000px e di ruotare la
torretta a destra di 360°, tutto nello stesso turno.
Tutti questi metodi restituiscono il controllo al programma
immediatamente dopo essere stati chiamati e il nostro
robot non farà nulla fino a che il controllo
non sarà restituito al gestore di battaglie.
E questo avviene non appena viene chiamata un funzione
bloccante (cioè tutte quelle senza "set",
oppure fire) o quando viene chiamato il metodo execute().
Execute() ritorna il controllo a Robocode per esattamente
un tick, cioè è come se io dicessi a Robocode
che ho deciso cosa fare per questo turno e lui muove
il robot.
Un'altra
alternativa è quella di usare il metodo waitFor()
che non restituisce il controllo al codice fino a che
non avviene una determinata condizione.
while(true)
{
//attendo di aver completato la rotazione
waitFor(new TurnCompleteCondition(this));
//inverto la direzione di rotazione
reverseDirection();
}
Nel
nostro caso attendiamo che il robot abbia finito di
girare. E siccome si muove e gira allo stesso tempo,
il robot percorrerà una traiettoria circolare.
Nel
codice viene usata anche la condizione MoveCompleteCondition,
per attendere il completamento di tutto il movimento,
rotazione e spostamento.
Eventi
customizzati e classi anonime
Il
mese scorso abbiamo visto come gestire gli eventi estendendo
i metodi per la loro gestione.
Ora analizziamo una nuova funzionalità fornita
dagli AdvancedRobot: i customEvents.
Questi verificano l'esistenza di una certa condizione,
e in caso affermativo, alzano l'evento
addCustomEvent(new
Condition("NearWall") {
public boolean test() {
return
(getX()<allowedWallDistance ||
getY()<allowedWallDistance
||
getX()>getBattleFieldWidth()-allowedWallDistance
||
getY()>getBattleFieldHeight()-allowedWallDistance);
}
});
Nel
robot d'esempio abbiamo creato una condizione usando
il meccanismo delle classi anonime.
All'interno del metodo addCustomEvent ho creato una
nuova classe che eredita da Condition e esteso il metodo
test(). La classe è chiamata "anonima"
perché non ha nome.
La nostra condizione solleva l'evento quando il robot
si trova sul bordo del campo di battaglia
Per
eseguire del codice in seguito ad un customEvent bisogna
estendere il metodo onCustomEvent() e verificare al
suo interno quale evento è stato sollevato.
public
void onCustomEvent(CustomEvent ev) {
Condition
cd = ev.getCondition();
System.out.println("event
with " + cd.getName());
if
(cd.getName().equals("NearWall")) {
centerRobot();
}
}
RoboMath:
la matematica usata in Robocode
Nel
robot d'esempio abbiamo avuto a che fare con le coordinate
del campo di battaglia e con i vari heading e bearing
.
Chi avesse conoscenze di orientamento e topografia sono
rispettivamente direzione e "rilevamento".
E questo paragone è anche abbastanza azzeccato
perché lo zero degli angoli è verso l'alto,
come se fosse una bussola, e non verso destra come il
solito cerchio usato in trigonometria.
Figura 3 - Coordinate del campo
Nel
file d'esempio ho incluso verso la fine un set di metodi
che è possibile riutilizzare per eseguire le
più comuni operazioni necessarie per realizzare
i calcoli matematici necessari per funzioni di puntamento.
Per la spiegazione del codice rimando ai commenti inclusi,
ma volevo far notare che tutte le operazioni con gli
angoli sono fatte in radianti e non i gradi: Robocode
fornisce metodi per lavorare sia con gradi che con i
radianti, ma nel suo funzionamento interno usa i radianti,
quindi le operazioni con i radianti sono più
precise. Inoltre le funzioni trigonometriche fornite
da Java lavorano in radianti.
La
fisica in robocode
I robot nel campo di gioco si spostano rispettando alcune
regole fisiche. Conoscere quali siano queste regole
è necessario per programmare correttamente il
nostro robot. Ora ne vedremo alcune, ma vi rimando alle
FAQ su Robocode accessibili dal menu Help->FAQ o
direttamente alla pagina [7] indicata nella webliografia.
-
la massima velocità di avanzamento è
di 8 px/tick
- la
rotazione di un robot è inversamente proporzionale
la velocità di avanzamento e varia da 4°/tick
a 9°/tick
- la
torretta gira a 20°/tick e il radar a 45°/tick,
e queste velocità si sommano (se ruoto entrambi
uno sull'altro avrò 65°/tick)
- i
proiettili viaggiano a 20 - 3*potenza px/tick
Conclusione
In questo articolo abbiamo visto la classe AdvancedRobot,
necessaria per realizzare dei robot competitivi al pari
di quelli che competono nelle varie leghe su internet.
Inoltre abbiamo iniziato a lavorare con gli angoli e
abbiamo visto la pseudo-fisica che sta alla base del
movimento dei robot.
Ora che avete tutti gli strumenti per sviluppare i vostri
robot, il prossimo mese racconterò la storia
di Robocode, vedremo l'attuale stato dell'arte e come
costruire un team di robot che combattono insieme contro
un altro team di avversari.
Link
e risorse
[1] http://www.alphaworks.ibm.com/tech/robocode - Sito
ufficiale di Robocode
[2] http://www.ecs.soton.ac.uk/~awo101/robocode.html
- SnippetBot tutorial: tutti i primi robocoder hanno
iniziato da qui
[3] http://robowiki.dyndns.org/perl/robowiki - Un wiki
su Robocode, il punto di ritrovo attuale di tutti i
robocoders
[4] http://www.robocoderepository.com - Il primo sito
su robocode, e ancora ora repository generale per tutti
i robot sviluppati e resi disponibili
[5] http://www.eternal-rumble.com - Sito che gestisce
una ranking list generale
[6] http://robowiki.dyndns.org/perl/robowiki?Robocode_3D
- HomePage per Robocode 3D
[7] http://robocode.alphaworks.ibm.com/help/physics/physics.html
- Tutte le costanti fisiche del gioco
[8]
Gli esempi descritti
in questo articolo
|