----Indice

Ecco le sezioni del tutorial:

1)  Tutorial - come si scrivono i dna dei robots
2)  Come sono fatti i robots, dentro.
3)  Il linguaggio del dna
4)  La notazione inversa polacca
5)  Scrivere i dna: la parte divertente
6)  Qualcosa di piu': girarsi
7)  Un robot che caccia
8)  Le variabili ref...
9)  I movimenti
10) E se vogliamo "centralizzare" la decisione su come comportarsi?
11) Seguire una preda
12) Rompere le scatole agli altri robot
13) Altre celle di input
14) Altre istruzioni
15) Divertitevi


1)----Tutorial - come si scrivono i dna dei robots

Al centro di tutto DarwinBots ci sono i dna. Un individuo e' il suo dna, senza quello non va da nessuna parte. Un mio amico guardando il programma ha obiettato che i miei robots non potevano evolversi, essendo tutti ugualmente quadrati.
Beh, il dna non puo' dargli una forma diversa. Ma puo' dargli un comportamento diverso. Sono i comportamenti che vengono mutati e selezionati nelle simulazioni, non la forma.

Quindi, un dna=un comportamento.

Il comportamento deve essere tale da permettere ad un robot, fondamentalmente, di cercare il cibo, mangiarlo e riprodursi. Se non fa queste cose basilari, il robot morira' senza  figli, e il suo dna sparira' nel nulla. Non per fare sfoggio di cinismo, ma e' cosi' anche per gli esseri umani (non vorrei che qualcuno obbiettasse che non guadagnero' del cibo scrivendo questo tutorial, ma...).

Ci sono tanti software di vita artificiale in giro in cui il dna degli individui si limita a specificare quanto alcuni comportamenti decisi a priori dal programmatore verranno effettivamente espressi dagli individui. Nel caso di DarwinBots non e' cosi', perche' un dna e' un vero e proprio programma che equivale, nelle possibilita', ad un qualsiasi altro programma scritto in un qualsiasi linguaggio. Il che non significa naturalmente che potete usarlo per scriverci un applicativo, ma che avete un controllo completo sul comportamento del robot nei limiti delle sue possibilita' sensoriali e di movimento, ovvero delle leggi fisiche che regolano l'universo delle simulazioni.



2)----Come sono fatti i robots, dentro.

Per capire come scrivere un dna per un robot, bisogna sapere come funziona un robot. Non e' difficile.
Ogni robot ha due cose veramente fondamentali: il dna e una memoria, formata da mille celle numerate da 1 a 1000.
Il dna e' il programma, e la memoria e' cio' su cui il programma agisce, leggendola e scrivendola: il dna-programma non puo' solo e soltanto agire sulla memoria.

Ma, allora, vi chiederete, come si fa a far muovere un robot, o a fargli percepire qualcosa attraverso, diciamo, la vista? Semplice: certe precise celle della memoria sono collegate direttamente ai "muscoli", e certe altre sono collegate direttamente agli "occhi": scrivendo un certo valore in una di queste celle si puo' far muovere un robot in avanti, e leggendo il valore contenuto in un'altra di queste celle, si puo' sapere cosa sta vedendo.



3)----Il linguaggio del dna.

Se avete mai scritto dei programmi, sarete abituati ad un linguaggio tipo questo:

INIZIO
  dichiarazione variabili
  FINO A CHE condizione ESEGUI
    ......
    FINO A CHE condizione ESEGUI
      .....
    FINE BLOCCO
    ......
  FINE BLOCCO
FINE

Per i miei robots, invece, ho scelto un approccio diverso. Il problema e' che in un tipo di programma come questo qui sopra, una mutazione casuale in un punto esterno, rischia di compromettere il funzionamento di TUTTO il programma. Inoltre, sebbene un programma del genere possa benissimo codificare per un comportamento, il comportamento non e' facilmente scindibile in caratteri DISCRETI, cioe' separati e indipendenti dagli altri (come la rugosita' dei piselli di Mendel, per intenderci).

Quindi questa e' la forma dei dna-programmi.

INIZIO

  SE condizione1 AND condizione2 .... AND condizioneN ALLORA
    BLOCCO
      .... (nessuna condizione all'interno)
    FINE BLOCCO

  SE condizione1 AND condizione2 .... AND condizioneN ALLORA
    BLOCCO
      .... (nessuna condizione all'interno)
    FINE BLOCCO

eccetera.

Ad ogni ciclo di esecuzione della simulazione (e ne vengono fatti parecchi al secondo), ognuno di questi programmi viene eseguito DA CAPO. Percio', e' un po' come se la struttura mostrata qui sopra fosse inclusa in un ciclo di ripetizione infinito.
Puo' sembrare a prima vista che un linguaggio del genere sia meno potente di quello mostrato all'inizio, ma non e' cosi'. Ad esempio, un comune ciclo FOR...NEXT puo' essere prodotto in questo modo:

  SE variabile<max ALLORA
    BLOCCO
      aumenta valore variabile
    FINE BLOCCO

  SE variabile<max ALLORA
    BLOCCO
      fai quello che devi fare all'interno del ciclo FOR..NEXT
    FINE BLOCCO

Il ciclo puo' essere inizializzato da qualsiasi punto del programma settando la variabile di controllo ad un valore minore di max.
E questo e' un altro vantaggio di questo tipo di struttura: e' molto tollerante rispetto a disposizioni diverse dei blocchi all'interno del programma.


4)---- La notazione inversa polacca.

Ultima cosetta da sapere prima di iniziare con le cose divertenti.
Forse la conoscete, la notazione inversa polacca e' quella che si usava sulle vecchie calcolatrici (Texas Instruments, credo).
Il principio e' che tutto passa per uno stack, che sarebbe una pila, ovvero una serie di oggetti sovrapposti.
Immaginate una pila di piatti. Se volete prendere un piatto, siete costretti a prendere quello che sta in cima. Se volete mettere un piatto nella pila, dovete per forza metterlo sopra gli altri.

Le istruzioni del dna-programma agiscono sui dati come se fossero piatti in una pila. Se volete fare un'operazione fra due numeri (due piatti) - mettiamo una somma- dovete mettere i due numeri in cima alla pila, e poi chiamare l'istruzione somma.
L'istruzione togliera' i due piatti-numeri dalla cima della pila, fara' la somma, e mettera' il risultato in cima alla pila (dove i due numeri di partenza sono adesso spariti).

Sembra complicato, ma e' facilissimo: ad esempio, dove normalmente scrivereste

a+b oppure somma(a,b)

dovrete scrivere:  a b somma

Se volete scrivere:

c=a+b oppure c=somma(a,b)

dovrete scrivere: a b somma c memorizza

Infine, una condizione dl tipo

IF a<b THEN

si scrive: a b <

Semplice, vero?



5)-----------Scrivere i dna: la parte divertente

A questo punto mi pare che sappiate abbastanza cose da potermi seguire nella scrittura di qualche semplice dna, a mo' di esempio.

Prima di iniziare, le ultime due cose:

1) non e' necessario ricordarsi a memoria tutti i numeri delle celle da cui entrano ed escono segnali verso il robot, perche' dei codici mnemonici li sostituiscono
2) vi conviene ricalcare la sintassi che usero' io, con una sola istruzione per riga

Adesso proviamo a costruire una "pianta", cioe' uno di quegli organismi che non hanno bisogno di nutrirsi da se', ma vengono nutriti direttamente dal programma, e servono a formare la base della catena alimentare. In pratica, questi organismi devono solo pensare a riprodursi al momento opportuno.

Il momento opportuno puo' essere, semplicemente, quando hanno accumulato abbastanza energia.

Per riprodursi, l'organismo non deve copiare da se' il suo dna: ci pensa il programma, una volta che l'organismo gli avra' chiesto di farlo (nella copia verrano inserite delle mutazioni, in accordo con le probabilita' di mutazione settata nella simulazione).

Per chiedere al programma di riprodurlo, il robot non deve fare altro che inserire un valore  nella sua cella di memoria 300. Il valore stabilisce, in percentuale, la ripartizione dell'energia fra genitore e figlio: inserendo il valore 50 nella cella 300, si ottiene una riproduzione con divisione esatta dell'energia.

L'istruzione per inserire un valore in una locazione e' "store".

Quindi, il codice sara':

50
300
store

Ma non c'e' bisogno, come dicevo, di ricordarsi i numeri delle celle: ci sono i codici mnemonici per questo. Possiamo allora scrivere

50
.repro
store

Ma questo non e' ancora un gene. Un gene deve avere una condizione di attivazione (nel nostro caso, l'energia del robot che si vuole riprodurre). Diciamo che il nostro vegetale si debba riprodurre quando la sua energia e' arrivata al valore 6000. Il gene sara':

cond
  *.nrg
  6000
  >
start
  50
  .repro
  store
stop

Ecco questo e' un gene completo. La parte compresa fra le istruzioni "cond" e "start" e' la sezione di attivazione: solo quando le condizioni espresse in essa si verificano, viene eseguito il blocco successivo, quello compreso fra le istruzioni "start" e "stop".
L'asterisco * che precede il codice mnemonico ".nrg" indica al programma che il valore da utilizzare non e' quello di .nrg (che vale sempre 310, cioe' la cella che contiene il valore corrente dell'energia) ma bensi' il valore _contenuto_ nella cella  310, appunto l'energia corrente.

Infine, se vogliamo creare un organismo formato da questo solo gene, basta inserire l'istruzione "end" dopo l'ultimo stop. Ecco quindi il nostro organismo completo:

cond
  *.nrg
  6000
  >
start
  50
  .repro
  store
stop
end

Per vedere come funziona questo organismo, basta salvare il suo dna come file di testo nella cartella robots, e quindi caricarlo con DarwinBots e inserirlo nella lista degli organismi da utilizzare per la simulazione.


6)---------Qualcosa di piu': girarsi

In realta' il nostro organismo non funziona in modo soddisfacente. Infatti, si divide quando ha raggiunto la giusta quantita' di energia. Il figlio, pero', si trovera' di fronte a lui, e rivolto esattamente verso il genitore. Quando cercheranno di riprodursi di nuovo, il programma glielo impedira', perche' lo spazio in cui i figli dovrebbero comparire e' gia' occupato.
Allora, e' opportuno fare in modo che, ad ogni tentativo di riproduzione, i robots si voltino un poco: alla fine si troveranno nella posizione giusta per riprodursi.

Le variabili per voltarsi sono .aimdx e .aimsx, per la destra e la sinistra rispettivamente.
Ogni unita' corrisponde ad 1/200 di radiante (cioe' per mezzo giro bisogna immettere un valore di 628, per un giro intero 1256).

Quindi, senza aumentare il numero di geni, possiamo scrivere:

cond
  *.nrg
  6000
  >
start
  50
  .repro
  store
  10
  .aimdx
  store
stop
end



7)-------Un robot che caccia

Ora proviamo a scrivere il dna di un robot in grado di nutrirsi da se'.
Fondamentale, per la nutrizione, e' la cella 7 (codice .shoot)
Immettendo il valore -1 in questa cella, il robot spara davanti a se' un getto di particelle. Se queste particelle raggiungono un altro robot, lo costringono a rimandare indietro un secondo getto, contenente energia.

Per sapere se il robot ha qualcuno di fronte a se', usiamo il senso visivo.
Le celle da cui estrarre le informazioni visive vanno dalla 501 alla 509, ma si possono usare i codici .eye1, .eye2, .eye3 ... .eye9. In pratica, ad ogni cella corrisponde un certo angolo visivo, e .eye5 e' la cella centrale. Il valore contenuto nelle celle e' circa l'inverso della distanza dell'oggetto visto: cioe' un valore 0 indica nessun oggetto nel campo visivo della cella, e mano amano che questo valore aumenta significa che l'oggetto e' sempre piu' vicino.

Un gene per l'attacco potrebbe quindi essere questo:

cond
  *.eye5
  0
  >
start
  -1
  .shoot
  store
stop

Cioe' il robot inizia a sparare quando vede qualcosa di fronte a se'.

Ma... se per caso l'oggetto che vede non e' ne' una pianta ne' un altro robot, ma, diciamo, un muro? Sparare a un muro e' solo una perdita d'energia, no?


8)-------Le variabili ref...

Ci vorrebbe un modo per sapere qualche cosa di piu' su chi ci sta di fronte. E c'e'.
Ogni volta che un nuovo robot (o un muro) viene creato, viene compilata una lista delle occorrenze di certe variabili nel suo dna. Ad esempio, per il dna di un solo gene della pianta che abbiamo scritto prima, abbiamo una occorrenza della variabile .aimdx, usata per far voltare il robot.
Possiamo usare le celle ref... per conoscere i valori di questa lista in chi sta di fronte.

Per fare subito un esempio, ecco un gene che prescrive di attaccare piante:

cond
  *.eye5
  0
  >
  *.refaimdx
  1
  =
start
  -1
  .shoot
  store
stop

Bello, vero? Un robot che abbia questo gene in sostituzione di quello di prima attacchera' solo i robot che abbiano un solo aimdx nel loro dna, cioe', in genere, le piante.

Facciamo un altro esempio: un gene per attaccare robot che abbiano solo piu' di 4000 di energia:

cond
  *.eye5
  0
  >
  *.refnrg
  4000
  >
start
  -1
  .shoot
  store
stop


ecco di seguito tutte le variabili ref utilizzabili:

.refup    quante istruzioni di movimento verso l'alto in chi mi sta di fronte?

.refdn    quante... verso il basso?

.refsx    quante a sinistra?

.refdx    quante a destra?

.refaimdx    quante istruzioni per voltarsi a destra?

.refaimsx    quante per voltarsi a sinistra?

.refshoot    quante per sparare?

.refeye      quante per guardare (da eye1 fino a eye9)?

.refnrg      quanta energia ha?


9)-------I movimenti

Pero', e' anche utile che un robot possa avvicinarsi alle sue prede, no?
Ci sono quattro celle che controllano i movimenti: .up .dn .sx .dx
Scriviamo un gene per far avvicinare un robot alla preda:

cond
  *.eye5
  0
  >
start
  10
  .up
  store
stop

e ora un gene per farlo allontanare da un suo simile:

cond
  *.eye5
  0
  >
  *.refup
  0
  >
start
  10
  .dn
  store
stop

Attenzione: i movimenti sono espressi relativamente alla direzione in cui il robot sta guardando! Quindi .up = avanza, .dn=indietreggia, .sx di lato a sinistra ecc.



10)------E se vogliamo "centralizzare" la decisione su come comportarsi?

Finora abbiamo scritto una serie di geni ognuno dei quali fa compiere al robot un'operazione diversa indipendentemente dagli altri. Ma potremmo anche metterli in comunicazione fra loro, utilizzando delle variabili nuove. Basta semplicemente assicurarsi che la cella che usiamo non sia gia' occupata da una delle variabili di input/output.

Ad esempio, ecco un gruppo di geni che sceglie la preda e l'attacca:

cond
  *.eye5
  0
  >
  *.refaimdx
  1
  =
start
  1
  40
  store
stop

cond
  *.eye5
  0
  =
start
  0
  40
  store
stop

cond
  *40
  1
  =
start
  -1
  .shoot
  store
stop

cond
  *40
  1
  =
start
  10
  .up
  store
stop

Sembra lungo, ma in realta' sono solo quattro geni. I primi due agiscono sulla cella libera 40: il primo le assegna il valore 1 se viene visto qualche cosa di "interessante"; il secondo la azzera se non si vede piu' nulla. Il terzo e il quarto sono due geni indipendenti, che pero' si riferiscono alla cella 40, e si attivano se contiene il valore 1, attaccando e avanzando. Una mutazione sul primo gene modifica le condizioni di attivazione del terzo e del quarto.



11)----Seguire una preda

Adesso vediamo un possibile sfruttamento delle altre celle visive.
Ecco un gene che ci permette di seguire una preda se si sposta alla nostra sinistra:

cond
  *.eye4
  0
  >
start
  10
  .aimsx
  store
stop


Oppure tre geni che possono farci girare intorno alla preda:

cond
  *.eye6
  0
  >
start
  10
  .aimdx
  store
stop

cond
  *.eye7
  0
  >
start
  10
  .up
  store
stop

cond
  *.eye8
  0
  >
start
  10
  .aimsx
  store
stop

Il primo e il terzo cercano di mantenere la preda dentro la cella visiva eye7, il secondo ci fa avanzare se abbiamo la preda nella cella eye7.



12)------Rompere le scatole agli altri robot

Finora abbiamo visto soltanto un uso del getto di particelle che si ottiene usando la variabile .shoot (cioe' quallo di attaccare), ma in realta' ne sono possibili anche altri.

Ad esempio, inserendo il valore -2 in .shoot si spara un po' della propria energia.
Poco utile, pero'.

Piu' utile invece e' questo gene che scriviamo adesso:

cond
  *.eye5
  0
  >
start
  .aimsx
  .shoot
  store
  200
  .shootval
  store
stop

Non e' un errore quella sequenza di .aimsx e .shoot. Quello che questo gene fa, e' spedire verso un altro robot un getto di particelle contenenti due valori: uno e' quello della locazione .aimsx, l'altro e' il valore 200. Ricevendo questo getto, un robot sara' costretto da ogni particella ricevuta a girarsi di 200 verso sinistra!

Questo puo' servire a rompere le scatole agli altri robots, ma anche a scambiarsi dei segnali, stabilendo come cella usata per lo scambio la locazione 40, che abbiamo visto essere libera (a meno che non ci troviamo COME nell'esempio di prima, con la cella 40 che controlla l'attacco: allora sparandoci dentro il valore 0 possiamo inibire l'attacco dell'avversario).



13)----Altre celle di input

Vediamo per finire le celle di input che ho lasciato fuori dagli esempi:

.vel : fornisce la propria velocita' in modulo.

.pain : e' positivo se l'energia acquistata nell'ultimo ciclo e' inferiore a quella persa

.pleas : il contrario di .pain

.hitup : collisione con un altro robot nella direzione in cui si e' voltati

.hitdn : idem, da dietro

.hitdx, .hitsx : ancora idem, a destra e a sinistra

.shup, .shdn, .shdx, .shsx : colpo proveniente dalle rispettive direzioni; il valore e' quello immesso dall'avversario in .shoot

.tie: da migliorare: crea un legame fisso con un robot di fronte purche' abbastanza vicino



14)----Altre istruzioni

Ci sono altre istruzioni utilizzabili: add, sub, mult, div: mi pare che si spieghino da sole.



15)----Divertitevi

Divertitevi a provare questi geni e altri che potete inventare, e a creare organismi completamente nuovi: se create organismi interessanti speditemeli in email, li introdurro' nelle prossime versioni del programma o nel sito!


Carlo Comis
comis@libero.it
DNA TUTORIAL