[#_lesson_4_doing_it_again] == Lezione 4: Ancora una volta image::learn/04-repeating.png[La lezione finita, 480] Hai disegnato una parola e un riquadro. A un gioco le cose servono a centinaia: stelle, mattoni, proiettili, le righe di un menu. Scrivere cento righe non è la risposta, e se lo fosse, dovresti scriverne altre cento nel momento in cui ne volessi centouno. Questa lezione è la risposta. Alla fine un campo di stelle scenderà lentamente lungo la tua finestra, e ognuna sarà disegnata dalle stesse cinque righe di codice. [#_start_a_new_folder_3] === Crea una nuova cartella Una cartella chiamata `repeating`, dentro un file chiamato `repeating.singe`, e `Singe -R repeating` in esecuzione in un terminale. [#_forty_dots] === Quaranta punti Scrivi questo ed eseguilo. [source,lua] ---- function onOverlayUpdate() overlayClear() colorForeground(255, 255, 255) for star = 1, 40 do overlayPlot(star * 8, 100) end return OVERLAY_UPDATED end ---- Una fila di quaranta punti bianchi attraverso il centro della finestra. `overlayPlot` è la chiamata per disegnare più semplice che ci sia: colora un pixel con il colore di primo piano attuale, ed è esattamente quello che serve a una stella. Tutto ciò che c'è di interessante qui sono le tre righe intorno. [source,lua] ---- for star = 1, 40 do ---- Questo dice: fai quello che segue quaranta volte, e a ogni giro `star` contiene un numero diverso -- uno la prima volta, due la seconda, quaranta l'ultima. Entrambi gli estremi sono compresi, quindi `1, 40` sono davvero quaranta giri e non trentanove. Questa forma è un *ciclo*, e questo in particolare è un *ciclo for numerico*. `star` è un nome che inventi tu, come hai inventato `frames` nella seconda lezione, e il ciclo lo riempie per te. Non sei tu a impostarlo, e non dovresti cambiarlo dentro il ciclo. Esiste solo tra il `for` e il suo `end`; se lo chiedi dopo, ottieni `nil`. Il `do` è obbligatorio ed è facile dimenticarlo. Se lo togli Lua dice `'do' expected near`, seguito da quello che ha trovato al suo posto, che sarà la prima cosa della riga successiva. [source,lua] ---- overlayPlot(star * 8, 100) ---- Il corpo. È questo che viene eseguito quaranta volte, ed è sempre la stessa riga -- quello che cambia è `star`, quindi il primo punto cade a x 8, il secondo a 16 e l'ultimo a 320. Un ciclo serve solo se il corpo fa qualcosa con il numero del ciclo, e la prima cosa a cui ricorrerai ogni volta è farci dei calcoli. Il corpo è rientrato di un passo in più rispetto al `for`, per lo stesso motivo per cui finora è stato rientrato tutto il resto. [source,lua] ---- end ---- Chiude il ciclo. Ora hai incontrato tre cose che hanno bisogno di un `end`: `function`, `if` e `for`. Funzionano tutte allo stesso modo, si annidano una dentro l'altra, e il rientro è il modo in cui tieni traccia di quale `end` appartiene a che cosa. [#_counting_by_more_than_one] === Contare a passi più grandi di uno Il ciclo conta in avanti di uno alla volta, a meno che tu non gli dica altrimenti. Aggiungi un terzo numero e conterà invece a passi di quel numero. [source,lua] ---- for x = 0, overlayGetWidth() - 1, 10 do overlayPlot(x, 100) end ---- Leggilo come *da, fino a, a passi di*. Questo va 0, 10, 20 e così via, fermandosi sull'ultimo pixel della superficie o prima, e disegna una linea punteggiata da un lato all'altro, qualunque sia la dimensione della finestra. È la stessa fila di punti di prima, descritta in termini di dove vanno invece che di quanti sono. Il passo può essere negativo, e allora conta all'indietro. [source,lua] ---- for countdown = 10, 1, -1 do ---- Dieci, nove, otto, giù fino a uno. Con un passo negativo il secondo numero è il pavimento invece del soffitto, come ti aspetteresti, e sbagliare il segno dà un ciclo che non viene mai eseguito: `for i = 10, 1 do` senza passo conta in avanti da dieci verso uno, è già oltre la fine prima di cominciare e non fa niente. Nessun errore, nessun risultato. Se un tuo ciclo sembra non partire mai, controlla prima la direzione. Il passo può anche essere una frazione, anche se ti servirà meno spesso di quanto pensi. [#_one_loop_inside_another] === Un ciclo dentro un altro Il corpo di un ciclo è codice normale, e il codice normale può contenere un ciclo. [source,lua] ---- for row = 0, overlayGetHeight() - 1, 20 do for column = 0, overlayGetWidth() - 1, 20 do overlayPlot(column, row) end end ---- Questo disegna una griglia di punti su tutta la finestra. Mettilo nella tua `onOverlayUpdate` al posto della fila e guardalo, perché conviene chiarirti in testa adesso come viene eseguito, invece che nell'undicesima lezione, quando qualcosa dipenderà da questo. Il ciclo esterno viene eseguito una volta con `row` a 0. Lì dentro, tutto il ciclo interno viene eseguito dall'inizio alla fine -- diciotto colonne, diciotto punti, una riga della griglia. Solo quando il ciclo interno ha finito il ciclo esterno passa a `row` a 20 ed esegue di nuovo tutto il ciclo interno. Il corpo del ciclo interno viene quindi eseguito diciotto per dodici volte, cioè duecentosedici volte, per dodici giri del ciclo esterno. Quella moltiplicazione è il motivo per cui i cicli annidati sono il modo di riempire un rettangolo con qualsiasi cosa, e anche il motivo per cui dovresti dare un'occhiata ai numeri prima di annidarne tre. Nota che la posizione di ogni punto ha bisogno di entrambe le variabili dei cicli: `column` per l'orizzontale e `row` per il verticale. Lo schema è questo. Se il corpo interno usa solo la variabile interna, il ciclo interno disegna la stessa cosa più e più volte nello stesso posto, e hai scritto un modo lento per farla una volta sola. [#_when_you_do_not_know_how_many] === Quando non sai quante volte Un ciclo `for` ha bisogno di sapere quante volte prima di cominciare. A volte non lo sai, e allora ti serve l'altro tipo. [source,lua] ---- x = 0 while x < overlayGetWidth() do overlayPlot(x, 100) x = x + 10 end ---- Un ciclo `while` controlla una domanda prima di ogni giro, esattamente la domanda che farebbe un `if`, e continua finché la risposta è vera. Quando la risposta è falsa si ferma e prosegue con la riga che segue `end`. Se la risposta è falsa già la prima volta, il corpo non viene mai eseguito. Quell'esempio disegna la stessa linea punteggiata del `for` con passo dieci, e ci mette tre righe per fare quello che `for` faceva in una, quindi per quello usa `for`. Ecco la differenza che decide: * Usa `for` quando il numero di giri è noto prima di cominciare. Quaranta stelle. Un pixel ogni dieci da un lato all'altro. Ogni riga di una griglia. * Usa `while` quando aspetti che qualcosa diventi vero e non puoi dire in anticipo quanto ci vorrà. Distribuisci carte finché il mazzo non finisce. Continua a chiedere finché il giocatore non scrive qualcosa di valido. Scorri un elenco finché non trovi quello che cercavi. Un `while` ha tre parti e devi scriverle tutte e tre tu: preparare la variabile prima del ciclo, controllarla nel `while` e cambiarla dentro il corpo. Il ciclo `for` le fa tutte e tre per te, ed è per questo che è quello da scegliere quando va bene. Dimentica la terza parte in un `while` e la domanda non smette mai di essere vera, cosa che farai apposta alla fine di questa lezione. [#_a_star_made_out_of_its_own_number] === Una stella fatta del suo numero Ora il campo di stelle, e un problema che dà forma al resto della prima parte. Ogni stella ha bisogno di una x e di una y tutte sue, e non hai modo di tenere cento variabili separate -- scrivere `star1X`, `star2X` e così via fino a cento è peggio che scrivere le cento righe che stavi evitando. Quindi non memorizzarle. Calcolale una per una a partire dall'unica cosa che rende una stella diversa dalle sue vicine: il suo numero. [source,lua] ---- starX = (star * 37) % width ---- Il `%` è l'ultimo degli operatori aritmetici ed è il *resto*: quello che avanza dopo una divisione. Quindici diviso quattro fa tre con il resto di tre, quindi `15 % 4` fa 3. La sua proprietà utile è che il risultato non può mai essere grande quanto il numero a destra, quindi il resto della divisione per `width` è sempre una posizione da qualche parte sullo schermo, per quanto grande diventi il lato sinistro. La stella 1 va a 37, la stella 5 a 185, la stella 10 a 10 -- 370 ha fatto il giro oltre 360 ed è ricomparso a sinistra. Trentasette è scelto perché non ha fattori in comune con 360, ed è questo che impedisce alle stelle di disporsi in strisce ordinate; prova invece 36 e vedrai subito il problema. La discesa lenta è lo stesso trucco con un numero che cresce col tempo. [source,lua] ---- drift = 0 function onOverlayUpdate() drift = drift + 1 ... starY = (star * 61 + math.floor(drift * layer * 0.5)) % height ---- `drift` vive fuori dalla funzione e sale a ogni fotogramma, esattamente come `frames` nella seconda lezione. Aggiungerlo alla y di ogni stella sposta in basso tutto il campo insieme, e il `%` riporta in cima una stella che cade fuori dal fondo, per sempre, gratis. `math.floor` butta via la parte frazionaria di un numero e restituisce la parte intera sotto: `math.floor(7.9)` fa 7. La seconda lezione faceva notare che Singe lo fa per te quando disegna, ed è vero, ma farlo tu vuol dire che il numero con cui lavori è il numero che compare sullo schermo. Questo conterà nella settima lezione, quando due cose dovranno essere d'accordo sul fatto di toccarsi o no. Nota che lì dentro `layer` è una seconda variabile di ciclo. Le stelle sono disegnate da un ciclo dentro un ciclo, e quello esterno non è una riga di una griglia ma una *distanza*: tre strati di stelle, ognuno che scende alla sua velocità e con la sua luminosità, ed è questo che fa sembrare profondo un campo piatto di punti. [#_chance] === Il caso Le stelle scintillano. Scintillare vuol dire una luminosità un po' diversa a ogni fotogramma, e «un po' diversa» è `math.random`. [source,lua] ---- shade = math.random(60, 90) + layer * 55 colorForeground(shade, shade, shade) ---- `math.random(60, 90)` restituisce un numero intero da 60 a 90, entrambi compresi, scelto di nuovo ogni volta che la chiami. Chiamala senza nessun argomento e ottieni invece una frazione tra 0 e 1, che è la forma da usare quando vuoi che qualcosa succeda una volta su cinque. Non devi preparare il generatore: lo fa Lua quando Singe parte. Aggiungere `layer * 55` solleva gli strati vicini fuori dalla fascia scura, così le stelle lontane restano fioche e quelle vicine sono quasi bianche. Rosso, verde e blu uguali, come spiegava la terza lezione, danno una sfumatura di grigio. Siccome `shade` viene scelto di nuovo a ogni fotogramma per ogni stella, ogni stella tremola per conto suo, e a sessanta fotogrammi al secondo sembra uno scintillio e non un difetto. [#_the_whole_thing_3] === Tutto insieme [source,lua] ---- LAYERS = 3 STARS_PER_LAYER = 40 ACROSS_STEP = 37 DOWN_STEP = 61 LAYER_SHIFT = 13 LAYER_SPEED = 0.5 LAYER_BRIGHT = 55 width = overlayGetWidth() height = overlayGetHeight() drift = 0 function onOverlayUpdate() drift = drift + 1 overlayClear() for layer = 1, LAYERS do for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT colorForeground(shade, shade, shade) overlayPlot(starX, starY) end end return OVERLAY_UPDATED end ---- Centoventi stelle, disegnate da cinque righe, e il file è più corto di quello della terza lezione. È anche il limite di questo approccio, e vale la pena essere onesti sul perché mentre lo hai sotto gli occhi. Ogni stella lì dentro è una formula. Di una stella non viene *ricordato* niente: non puoi renderne una più luminosa della vicina e farla restare così, non puoi abbatterne una dal cielo e non puoi far sì che il giocatore ne raccolga una, perché non c'è nessun posto dove scrivere che le è successo qualcosa. La stella diciassette non è una cosa. È un numero che passa per un attimo in una variabile chiamata `star`. Nel momento in cui vuoi cento cose che si ricordino ognuna qualcosa di suo, ti serve un posto dove mettere cento gruppi di informazioni. Quella è la sesta lezione, e il campo di stelle è il motivo per cui esiste. [#_try_it_4] === Prova tu . *Di più e di meno.* Prova `STARS_PER_LAYER = 200`, poi `4`. Poi `LAYERS = 8` e guarda che cosa fa `layer * LAYER_BRIGHT` a `shade` quando il numero dello strato diventa grande. I numeri che accetta `colorForeground` si fermano a 255. . *Cambia la spaziatura.* Metti `ACROSS_STEP` a 36 ed eseguilo. Le stelle si mettono in strisce, perché 36 sta in 360 esattamente dieci volte. Rimettilo a 37 e prova 71 e 90. . *Ferma lo scintillio.* Sostituisci la riga con `math.random` con un semplice `shade = layer * 70` e confronta. Decidi quale preferisci; non c'è una risposta giusta, e accorgerti di avere una preferenza fa parte del lavoro. . *Falle cadere verso l'alto.* Un carattere. . *Disegna anche la griglia.* Rimetti il ciclo annidato della griglia di prima, dopo le stelle, in un colore scuro. Due cicli annidati in una sola funzione, che disegnano due cose diverse, e il secondo non disturba il primo. [#_break_it_on_purpose_4] === Rompilo apposta Finora ogni errore è stato un messaggio. Questo no, ed è proprio per questo che dovresti incontrarlo mentre te lo aspetti. Metti questo nella tua `onOverlayUpdate`, subito sotto `overlayClear()`, eseguilo e leggi il paragrafo successivo prima di fare qualsiasi altra cosa. [source,lua] ---- x = 0 while x < width do overlayPlot(x, 100) end ---- Manca la riga che fa avanzare `x`, quindi `x` resta a zero, quindi `x < width` non smette mai di essere vero. Il ciclo gira per sempre, disegnando lo stesso pixel, ed è dentro `onOverlayUpdate`, quindi `onOverlayUpdate` non ritorna mai. Singe sta aspettando la tua funzione. Non può disegnare, non può leggere la tastiera e non può uscire. Quello che vedi è una finestra che smette di muoversi. Esc non fa niente. Q non fa niente. E non viene stampato niente. Vai nel terminale da cui hai avviato Singe e premi *Ctrl* e *C* insieme. Singe ferma il tuo ciclo e ti dice dov'era: ---- Stopped: the script did not return, and Singe was asked to quit. stack traceback: repeating/repeating.singe:20: in function 'onOverlayUpdate' ---- Può dirlo due volte, a qualche secondo di distanza, prima di uscire. Una volta basta e avanza. La riga 20 è l’`end` del tuo `while`, che è dove il ciclo stava girando quando l'hai fermato. Togli il ciclo, salva e avvia di nuovo Singe. Questo è un resoconto, non un errore. Niente si è lamentato finché non l'hai chiesto, perché, per quanto riguarda il computer, non è andato storto niente. Hai chiesto un ciclo che gira finché `x` è minore della larghezza, ed è esattamente quello che hai ottenuto. Questo è l'unico tipo di errore con cui i messaggi di errore non possono aiutarti, e la cura è un'abitudine: ogni volta che scrivi un `while`, scrivi la riga che cambia la variabile prima di scrivere qualsiasi altra cosa nel corpo. [#_what_you_learned_4] === Cosa hai imparato * `for name = first, last do ... end` esegue il suo corpo una volta per ogni numero dal primo all'ultimo, entrambi compresi. * Un terzo numero è il passo, e può essere negativo per contare all'indietro. * La variabile del ciclo esiste solo dentro il ciclo, e il corpo dovrebbe usarla. * `for` ha bisogno di un `do`, e ogni ciclo ha bisogno di un `end`. * Un ciclo dentro un ciclo esegue tutto il ciclo interno per ogni giro di quello esterno, ed è così che riempi un rettangolo. * `while question do ... end` ripete finché la domanda è vera, e qualcosa nel corpo devi cambiarlo tu. * Usa `for` quando sai quante volte, e `while` quando aspetti che qualcosa diventi vero. * `%` è il resto, e tiene dentro un intervallo un numero che cresce. * `math.random(a, b)` dà un numero intero da a a b; `math.floor` butta via la parte frazionaria. * Un ciclo che non finisce mai blocca tutto il gioco senza nessun messaggio di errore, e *Ctrl* e *C* nel terminale sono il modo per uscirne. [#_next_time_4] === La prossima volta Guarda di nuovo quella `onOverlayUpdate`. Disegna stelle, calcola posizioni, sceglie colori, e sta cominciando a diventare una funzione che fa quattro lavori invece di uno. Nella quinta lezione scrivi funzioni tutte tue, passi loro argomenti come li ricevono le funzioni di Singe e ne ottieni delle risposte -- e il campo di stelle diventa una riga che dice `drawStars()`.