Comportamento casuale
Pensiamo qui alla tartaruga LOGO come ad un animale (o robot) cui assegnamo un comportamento tramite programmi. Cominciamo studiando un movimento casuale ovvero diamo una lunghezza random al passo e/o alla direzione con cui esso si muove nel mondo a sua disposizione. Siamo sicuri che i comportamenti non sono guidati da "intelligenza" ma ci sorprenderemo a notare come, con particolari scelte dei parametri, sembrino invece determinati da un obiettivo.
La funzione LOGO che fornisce numeri casuali è la random che restituisce come risultato un numero non negativo minore del suo unico input. Scriviamo allora la seguente funzione che restituisce un numero a caso tra :x1 e :x2 nell'ipotesi che :x1 < :x2.
to randomizza :x1 :x2
op ((random (1+ abs ( :x2 - :x1 ))) + :x1 )
end
Costruiamo poi un mondo rotondo con
to mondo :raggio
pd
circle :raggio
end
e permettiamo il movimento solo in esso. Imponiamo che, quando l’insetto urterebbe contro la parete, giri di 180 gradi. La casualità dell'angolo di rotazione è limitata da un adattamento all'ambiente: ha una direzione privilegiata e non è eccessiva. La scia dopo un po' riempe tutto il cerchio in cui è costretto l’animale.
to ronza :raggio
rt randomizza -10 5
pu
fd 5
ifelse not (sqrt ((xcor*xcor) + (ycor*ycor)))< :raggio ~
[bk 5 lt 180 pd] [bk 5 pd fd 5]
ronza :raggio
end
In effetti questa casualità nella procedura scritta è limitata ad un’incertezza fissata della direzione ma possiamo facimente modificare la procedura per renderla parametrica nella direzione e nella distanza. Inoltre non è difficile rendere il mondo quadrato e ottenere lo stesso risultato. Lasciamo questo per esercizio e vediamo altre tecniche di guida al movimento. Una semplicissima consiste semplicemente nel non fare la mossa se non è possibile. La procedura seguente testa solo se la mossa è possibile o meno in un mondo di raggio 50.
to possibile :quanto
local "posso
make "posso "true
ht pu fd :quanto
if (sqrt(( xcor*xcor) +(ycor*ycor))) > 50 [make "posso "false]
bk :quanto
pd st
wait 1
if :posso [ fd :quanto]
end
to ronza
rt randomizza -10 5
possibile 5
ronza
end
Il comportamento sembra davvero "intelligente" alla ricerca dell'uscita dal mondo. Ma al solito, nello studiare l'intelligenza, dobbiamo avere un briciolo d'attenzione. Proviamo a cambiare possibile 5 con possibile 1 o anche a ingrandire la scatola in cui vive l'insetto e ci accorgiamo come il caso precedente potrebbe essere semplicemente un caso particolare di adattamento all'ambiente. Variazioni casuali sulla lunghezza del passo non mi sono sembrate significative.
Una guida istintiva migliore
È invece interessante un altro modo di evitare l'ostacolo: se sto per urtare mi giro di poco nella direzione contraria.
to ronza :raggio
local "so
make "so heading
rt randomizza -10 20
pu
fd 5
ifelse not (sqrt ((xcor*xcor) + (ycor*ycor)))< :raggio ~
[bk 5 setheading :so raddrizza :raggio] [bk 5 pd fd 5]
ronza :raggio
end
to raddrizza :raggio
lt 1
pu fd 5
ifelse not (sqrt ((xcor*xcor) + (ycor*ycor))) > :raggio ~
[bk 5 pd fd 5] [bk 5]
end
Vediamo come basta questo affinché l'insetto arrivato alle pareti del suo mondo, quali che siano le sue dimensioni, continui a percorrerle quasi indicando una intenzione di uscire. Sarà proprio come un mosca contro i vetri delle nostre finestre?
È divertente notare come si tratti proprio di una "raddrizzata": cambiando lt 1 con rt 1 nella prima riga della procedura raddrizza il comportamento è molto diverso.
L'olfatto
Vediamo adesso di simulare il movimento di un animale dotandolo via via dei sensi necessari. Una procedura fondamentale in questa classe di algoritmi è quella che ci fornisce la distanza tra due punti. Scriviamo dunque una funzione che calcola la distanza secondo la regola euclidea.
to dista :x1 :x2
op sqrt ((xcor - :x1)* (xcor - :x1)+ ~
(ycor - :x2)* (ycor - :x2) )
end
Proponiamoci adesso di simulare l’olfatto. Pensiamo ad un animale che si avvicina alla sorgente del cibo guidato esclusivamente da esso nel senso che sia in grado di percepirne le variazioni e cammini in modo da raggiungerlo. Supponiamo allora che l’odore che può captare sia proporzionale alla sua distanza dal cibo che poniamo sullo schermo alle coordinate cartesiane :x1 :x2 tramite la seguente procedura.
to cibo :x1 :x2
pu setpos list :x1 :x2 pd
circle 5 pu
home pd st
end
Nella nostra prima simulazione l'animale può muoversi di un passo costante fissato e girare di un angolo :rot che invece gli forniamo in ingresso. Così se l’odore diminuisce rispetto a quello precedente, l’animale torna sui suoi passi e si gira. Le due variabili :x1 :x2 danno invece la posizione del cibo sullo schermo. Ragionevolmente fermiamo la tartaruga quando la sua distanza dal cibo è inferiore al passo che compie. Per mettere insieme tutta la nostra costruzione scriviamo anche la procedura annusa che sistema il cibo e chiama poi la procedura chiave della nostra simulazione. Si vede come, nonostante la semplicità dell’algoritmo la tartaruga arrivi quasi sempre alla meta a meno di non assegnarle angoli particolarissimi. In particolare è interessante notare come il cammino è più mirato se l’angolo è più deciso.
to annusa :x1 :x2 :rot
local "mem
make "mem dista :x1 :x2
if :mem < 2 [stop]
fd 2
if (dista :x1 :x2 )> :mem ~
[bk 2 rt :rot]
annusa :x1 :x2 :rot
end
to segugio :x1 :x2 :rot
cibo :x1 :x2
annusa :x1 :x2 :rot
end
Eseguire la procedura con le chiamate annusa 100 100 15, annusa 100 100 2, annusa 100 100 90, come esempi di comportamento.
La vista
Simuliamo adesso la vista nel caso che l’animale la usi per mantenere costante la sua inclinazione rispetto alla fonte luminosa. Questo meccanismo consentirebbe il volo rettilineo degli insetti notturni che mantengono un angolo costante rispetto alla luna ma che cadono sulle nostre lampade. La procedura di base fa uso della funzione towards che restituisce la direzione verso cui occorre puntare la tartaruga per raggiungere in linea retta la posizione le cui coordinate sono assegnate come argomento.
to unocchio :angolo :x1 :x2
setheading norm towards list :x1 :x2
lt :angolo
fd 1
unocchio :angolo :x1 :x2
end
Il comportamento a spirale diventa sempre più mirato man mano che l’angolo è più piccolo.
Comportamento sociale (?!)
Scriviamo adesso il comportamento di due animali interagenti. Esaminiamo il caso preda-predatore in cui ipotiziamo inizialmente una preda che corre secondo una regola fissata
to mossapreda :vel :rot
fd :vel
rt :rot
end
e un predatore che si muove attratto dall’odore della preda ovvero secondo il principio del gradiente. Se il valore :prec rappresenta l’odore al passo precedente la seguente procedura serve per regolare il movimento del predatore.
to odore :x1 :y1
ifelse (distanza :x1 :y1) > :prec [make "prec distanza :x1 :y1 op "true]~
[make "prec distanza :x1 :y1 op "false]
end
La tartaruga assumerà la parte di entrambi i contendenti lavorando in time-sharing. Dovranno esistere variabili globali "statopreda e "statopredatore dove vengono memorizzati lo stato della preda e del predatore in modo da poter riprendere la situazione proprio dal punto in cui si era lasciata. Così la mossa del predatore avviene con la seguente procedura
to mossapredatore :vel :rot
make "x1 first item 2 :statopreda
make "y1 item 2 item 2 :statopreda
fd :vel
if odore : x1 :y1 [ rt :rot]
end
La posizione iniziale dei due animali viene assegnata in lettura
to iniziapreda
local "x1 local "y1
pr [scrivi l’ascissa della preda]
make "x1 rw
pr [scrivi l’ordinata della preda]
make "y1 rw
pu setpos list :x1 :y1
make "statopreda list heading pos
end
to iniziapredatore
local "x1 local "y1
local "x2 local "y2
pr [scrivi l'ascissa del predatore]
make "x2 rw
pr [scrivi l'ordinata del predatore]
make "y2 rw
make "x1 first item 2 :statopreda
make "y1 item 2 item 2 :statopreda
pu setpos list :x2 :y2
make "statopredatore list heading pos
make "prec distanza :x1 :y1
end
Veniamo adesso alla procedura essenziale. Qui sfruttiamo le caratteristiche del LOGO. I parametri di una procedura possono essere a loro volta nomi di procedure. Ci vuole una procedura che porta la tartaruga allo stato di partenza (mettendo prima su la penna per non lasciare traccia ), e le faccia eseguire il suo passo.
to esegui.passo :processo :j :k :stato
pu
setheading first :stato setpos first bf :stato
pd run (list :processo :j :k)
end
La procedura fondamentale che permette l’esecuzione in time-sharing dei due processi, in quanto sarà chiamata con le due procedure munite dei valori dei loro parametri.
to caccia :proc.predatore :a :s :proc.preda :d :f
esegui.passo :proc.predatore :a :s :statopredatore
make "statopredatore list heading pos
esegui.passo :proc.preda :d :f :statopreda
make "statopreda list heading pos
caccia :proc.predatore :a :s :proc.preda :d :f
end
Una possibile chiamata è offerta dalla procedura seguente che, con le scelte effettuate, mette la povera preda in balia del predatore
to inizia
iniziapreda iniziapredatore
caccia "mossapredatore 2 45 "mossapreda 1 2
end