«[Figlio dell'uomo] Porgi l'orecchio e ascolta le parole di KGB
e applica la tua mente alla SUA istruzione
» Pv. 22,17

Qui si straparla di vari argomenti:
1. Il genere dei pezzi è segnalato da varie immagini, vedi Legenda
2. Per contattarmi e istruzioni per i nuovi lettori (occasionali e non) qui
3. L'ultimo corto è questo
4. Molti articoli di questo blog fanno riferimento a definizioni e concetti che ho enunciato nella mia Epitome gratuitamente scaricabile QUI. Tali riferimenti sono identificati da una “E” fra parentesi quadre e uno o più capitoli. Per esempio: ([E] 5.1 e 5.4)

mercoledì 14 agosto 2019

Mappa e altro

In questi giorni, complice il caldo, sono rimasto praticamente fermo nelle mie letture e non ho programmato quanto pensavo in Python.
Però qualche progresso lo sto comunque facendo e, per questo, ho pensato di scrivere un pezzo diverso dal solito. Idealmente avrei voluto commentare il mio programma che genera mappe (v. Mappine) una volta terminato ma, dato che i tempi vanno per le lunghe, ho deciso di iniziare a commentarlo via via che ci sono novità: ecco, la particolarità sarà questa: invece di scrivere molti pezzi che si richiamano l’un l’altro terrò aggiornato solo questo. Certo gli aggiornamenti avranno molta meno visibilità ma sarà più comodo per il lettore interessato poter leggere tutto insieme in un unico collegamento.
Per questo motivo non scriverò nemmeno una conclusione o dei commenti finali e lascerò il tutto in sospeso in maniera da poter fare le mie aggiunte senza soluzione di continuità.

Dove eravamo:
Nel mio precedente aggiornamento (v. Mappine) spiegavo che avevo scritto un programmino molto semplice con l’idea di generare mappe di città, poi però mi ero “spostato in campagna” e fantasticavo di realizzare una specie di simulazione che facesse evolvere il piccolo mondo che andavo creando. L’ultimo codice che avevo scritto serviva per visualizzare graficamente, e non mediante l’interfaccia a caratteri, la mappa realizzata…
Come mai questo mio procedere ondivago? Perché lo scopo del progetto non è realizzare qualcosa di specifico ma, più semplicemente, impratichirmi per bene col Python per poterlo poi usare senza imbarazzo con le reti neurali: non mi importa quindi tanto di realizzare qualcosa di specifico quanto di programmare il più possibile, magari usando tecniche e librerie diverse…

E infatti la “direzione” del mio progetto è ulteriormente cambiata…
Avevo appena aggiunto dei boschetti casuali ma poi il fiume ha iniziato a irritarmi: il vederlo serpentare casualmente a metà circa della mappa non mi piaceva e ho iniziato a pensare che avrebbe invece dovuto tenere conto dei rilievi (che al momento non erano presenti).

Allora ho pensato di implementare in Python un vecchio programma per disegnare mappe casuali che avevo scritto negli anni ‘90 dopo aver sfogliato un libro sui frattali. Adesso credo sia un algoritmo piuttosto comune ma all’epoca non avevo visto niente di simile (anche se, suppongo, il libro sui frattali dovette avermi dato, almeno inconsciamente, l’ispirazione).
L’idea era semplice: determinare casualmente l’altezza dei 4 vertici di un quadrato e calcolare poi la media, con una variazione casuale, dei 4 punti a metà dei lati e poi del punto al centro del quadrato. Riapplicare la stessa procedura per i nuovi 4 quadrati di cui si è generato i vertici.
Insomma un programma ricorsivo che ha la condizione di uscita quando i vertici passati come parametri sono adiacenti.

Ecco qui il codice:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def generaAltezza(self):
        mv=max(self.dimX,self.dimY)
        mv=int(math.log2(mv))+1
        mv=int(math.pow(2,mv))+1
        #print("Debug:",mv)
        self.alt=np.zeros((mv,mv))-1
        self.alt[0,0]=altezza()
        self.alt[mv-1,0]=altezza()
        self.alt[0,mv-1]=altezza()
        self.alt[mv-1,mv-1]=altezza()
        self.genAlt(0,0,mv-1,mv-1,self.alt)
        self.trovaMare()
        self.aggMatrice()

    def genAlt(self,inX,inY,fiX,fiY,mat):
        #print("Debug:", inX, inY, fiX, fiY)        
        if fiY-inY<=1:
            #print("\tEsco")
            return        
        medX=int((inX+fiX)/2)
        medY=int((inY+fiY)/2)        
        if fiX-inX<6: #prima era 10
            p1=1
        else:
            p1=2/(fiX-inX)
        p2=1-p1    
        #Nord
        if mat[medX,inY]==-1:
            mat[medX,inY]=int(p2*altezza()+p1*(mat[inX,inY]+mat[fiX,inY])/2)
        #Est
        if mat[fiX,medY]==-1:
            mat[fiX,medY]=int(p2*altezza()+p1*(mat[fiX,inY]+mat[fiX,fiY])/2)
        #Sud
        if mat[medX,fiY]==-1:
            mat[medX,fiY]=int(p2*altezza()+p1*(mat[inX,fiY]+mat[fiX,fiY])/2)
        #Ovest
        if mat[inX,medY]==-1:
            mat[inX,medY]=int(p2*altezza()+p1*(mat[inX,inY]+mat[inX,fiY])/2)
        #Centro
        if mat[medX,medY]==-1:
            mat[medX,medY]=int(p2*altezza()+p1*(mat[medX,inY]+mat[fiX,medY]+mat[medX,fiY]+mat[inX,medY])/4)
        self.genAlt(inX,inY,medX,medY,mat)
        self.genAlt(medX,inY,fiX,medY,mat)
        self.genAlt(inX,medY,medX,fiY,mat)   
        self.genAlt(medX,medY,fiX,fiY,mat)  

La funzione “generaAltezza” è chiamata direttamente alla creazione della Mappa nella funzione __init__, effettua alcune inizializzazioni, fra cui il calcolo casuale dell’altezza dei 4 vertici della mappa quadrata e poi chiama la funzione ricorsiva vera e propria: la “genAlt”.
Degni di nota gli “strani calcoli” (l. 2-4) matematici iniziali: il problema è che la mia mappa originaria è un rettangolo mentre il mio algoritmo funziona bene con mappe quadrate e di lato pari a 2^n + 1 unità (cioè 5, 9, 17, 33 etc). Invece di complicarmi la vita cercando di adattare il mio algoritmo a rettangoli generici lo applico invece a un quadrato di lato 2^n + 1 e poi vi “ritaglio” la mia mappa rettangolare. I calcoli servono per trovare il quadrato di lato minimo capace di contenere la mappa voluta…
Alla linea 6 poi uso una matrice di numpy (np) che ho più volte menzionato nei pezzi sulle reti neurali: si tratta di una libreria matematica usatissima e incredibilmente potente. Sfortunatamente, essendo evidentemente scritta da matematici, ci litigo un po’ ma comunque ho già imparato moltissimo…
La linea 12 chiama una funzione per un’idea a cui sto lavorando adesso (ne parlerò in seguito) mentre la 13 effettua il “ritaglio” della mappa rettangolare da quella quadrata….

Invece la “genAlt” verifica la condizione di uscita (l. 17-19), calcola i pesi (l. 22-26) per bilanciare casualità con media, calcola i punti a metà dei lati del quadrato e quello centrale (l. 28-41) se non sono già stati calcolati e infine richiama se stessa 4 volte per ciascuno dei nuovi quadrati definiti (l. 42-45).

Il risultato di questo algoritmo però non mi soddisfa più come 30 anni fa!
Si ottiene infatti, soprattutto per mappe grandi, un aspetto di poggi e buche che ha un aspetto così casuale da apparire innaturale..
Ecco un esempio:

Allora ho pensato di “naturalizzare” la mappa applicandovi ripetutamente una specie di “pioggia”: scelgo casualmente un punto della mappa che non sia di acqua (altezza maggiore di 2) e calcolo un percorso che mi porti al “mare”: durante di esso della terra può essere portata via (diminuendo l’altezza) lungo il percorso e portata altrove (aumentando l’altezza).
Questa è il risultato della prima versione del nuovo algoritmo che applica la funzione “pioggia” 3000 volte.

Come si vede, il mio algoritmo iniziale tendeva un po’ troppo a “sollevare” il terreno. Ah! e le montagne si "consumano" poco: dipende da un parametro che devo decidermi ad aggiustare.
Così vi ho fatto una piccola modifica per risolvere il primo problema:

Rimangono delle fastidiose “pozze” qua e là ma l’effetto complessivo è già migliore.
Ci sono però dei particolari che non mi piacciono: io speravo si formassero delle specie di valli che che arrivassero al “mare” invece non riconosco niente di tutto questo; e poi tutta l’acqua tende a essere riempita di terra e anche ciò non mi va bene.

Il primo problema credo dipenda dai vari livelli di altezza che sono troppo ravvicinati: in tutto si varia da 0 a 9 (perché usavo la grafica a caratteri per visualizzare il risultato) ed è così tutto troppo “discreto”, i cambiamenti sono troppo bruschi. Potrei correggere il problema (se è veramente questo) ampliando i livelli di altezza da 0 a 100: probabilmente ci proverò…
Il secondo problema è che il mio algoritmo non prevede che l’acqua disperda la terra nell’oceano (che nella mappa non c’è) e quindi, tende a riempirsi. Ho pensato quindi di alternare all’algoritmo “pioggia” un algoritmo che, partendo dalle estremità “acquatiche” della mappa, mi vada a “mangiare” un pezzettino di terra. Ed è su questo che sto lavorando. Sono fiducioso che la mappa vada ad assumere un aspetto molto più naturale...

Nessun commento:

Posta un commento