singe/docs/it/lessons/04-repeating.adoc

428 lines
17 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[#_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()`.