----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 |