singe/docs/it/lessons/02-numbers.adoc

431 lines
16 KiB
Text

[#_lesson_2_numbers_that_change]
== Lezione 2: Numeri che cambiano
image::learn/02-numbers.png[La lezione finita, 480]
Nella prima lezione hai messo una parola sullo schermo e lei è rimasta lì. Alla
fine di questa lezione la stessa parola scivolerà per la finestra, tornando
indietro quando arriva a un bordo, e lo farà grazie a quattro numeri che il
programma cambia sessanta volte al secondo.
Lungo la strada incontrerai l'idea che fa inciampare più principianti di
qualsiasi altra: dove metti una cosa decide quanto a lungo vive.
[#_start_a_new_folder]
=== Crea una nuova cartella
Crea una cartella chiamata `numbers` accanto alla cartella `hello`, e dentro un
file chiamato `numbers.singe`. Scrivici dentro il programma della prima
lezione, oppure copia `hello.singe` e rinominalo -- vanno bene tutti e due, e
riscriverlo non è tempo perso.
[source,lua]
----
function onOverlayUpdate()
overlayClear()
overlayPrint(2, 2, "Hello!")
return OVERLAY_UPDATED
end
----
Avvialo con `Singe -R numbers` e lascialo andare. Grazie al `-R` della fine
della prima lezione non dovrai più riavviarlo per il resto di questa lezione:
salva il file e la finestra si mette in pari da sola.
[#_count_the_frames]
=== Conta i fotogrammi
La prima lezione diceva che Singe chiama `onOverlayUpdate` circa sessanta volte
al secondo. È facile dirlo e difficile crederci, quindi contale.
[source,lua]
----
function onOverlayUpdate()
frames = 0
frames = frames + 1
overlayClear()
overlayPrint(2, 2, "Hello!")
overlayPrint(0, 0, "frame " .. frames)
return OVERLAY_UPDATED
end
----
Salvalo. Nell'angolo in alto a sinistra compare `frame 1`, e resta `frame 1`
per sempre.
È deludente, ed è anche la cosa più utile di questa lezione, quindi non
correggerlo ancora. Guarda quello che hai scritto e pensa all'ordine delle
righe, che secondo la prima lezione è la cosa più importante di un programma.
Singe chiama `onOverlayUpdate`. La prima riga mette `frames` a zero. La seconda
ci aggiunge uno, e fa uno. Lo stampi, fai il return, e la funzione è finita.
Poi Singe la chiama di nuovo, e la prima cosa che succede è che `frames` torna a
zero.
Non stai contando i fotogrammi. Stai contando fino a uno, sessanta volte al
secondo.
[#_move_the_line_out]
=== Porta fuori la riga
Togli la riga `frames = 0` dalla funzione e mettila sopra, da sola, prima della
parola `function`.
[source,lua]
----
frames = 0
function onOverlayUpdate()
frames = frames + 1
overlayClear()
overlayPrint(2, 2, "Hello!")
overlayPrint(0, 0, "frame " .. frames)
return OVERLAY_UPDATED
end
----
Salvalo. Il numero sale, veloce, e non si ferma.
Nient'altro è cambiato. L'unica differenza è *dove* sta la riga, e l'idea è
tutta qui. Le righe dentro una funzione avvengono ogni singola volta che la
funzione viene chiamata. Le righe fuori da tutte le funzioni avvengono una
volta sola, quando Singe legge il tuo file, prima che il gioco cominci.
`frames = 0` doveva avvenire una volta sola. Avveniva sessanta volte al secondo,
e cancellava il lavoro della riga sotto.
Ogni cosa in movimento che scriverai avrà questa forma. Prepara il numero
fuori, cambialo dentro.
[#_what_just_happened_2]
=== Cos'è appena successo
Lì dentro ci sono tre nuovi pezzi di codice. Eccoli da soli.
[source,lua]
----
frames = 0
----
Questo inventa un nome, `frames`, e ci mette dietro il numero zero. Un nome con
dietro un valore è una *variabile*, e si chiama così perché il valore può
variare -- cambiare mentre il programma gira.
Il `=` non è il segno di uguale che hai imparato a scuola. Non dice che `frames`
è uguale a zero: dice *metti zero in `frames`*, e funziona sempre in quella
direzione, da destra verso sinistra. I programmatori la chiamano *assegnazione*,
e vale la pena dire la riga ad alta voce in quel modo mentre la scrivi:
«frames riceve zero».
Il nome lo scegli tu. Può essere quasi qualsiasi cosa che cominci con una
lettera e non contenga spazi, e dovrebbe dire che cos'è la cosa. `frames` è un
buon nome. `f` è un nome pessimo, e fra una settimana non ti ringrazierai.
[source,lua]
----
frames = frames + 1
----
Questa è la riga che fa fermare la gente a fissarla, perché come affermazione
di aritmetica non ha senso: niente è uguale a se stesso più uno.
Non è un'affermazione di aritmetica. Ricorda che `=` mette il lato destro nel
nome a sinistra, e leggila in quell'ordine, prima la destra. Calcola
`frames + 1` usando quello che c'è in `frames` in questo momento. Poi rimetti il
risultato in `frames`. Se `frames` conteneva sei, il lato destro fa sette, e
sette va in `frames`.
Aggiungere uno a una variabile in questo modo è una cosa che scriverai migliaia
di volte.
Il `+` fa quello che ti aspetti, e così gli altri tre: `-` sottrae, `*`
moltiplica e `/` divide. Non c'è un tasto `x` per moltiplicare, ed è per questo
che quel lavoro lo fa l'asterisco.
[source,lua]
----
overlayPrint(0, 0, "frame " .. frames)
----
`overlayPrint` vuole del testo, e `frames` è un numero, non testo. I due puntini
uniscono le due cose e consegnano il risultato come un'unica stringa: i cinque
caratteri `frame`, uno spazio, e poi tutte le cifre che servono al numero.
Quei due puntini sono l'operatore di unione, e conviene conoscerne il vero nome
perché lo usano i messaggi di errore: unire stringhe si chiama
*concatenazione*.
Nota lo spazio dentro le virgolette, prima di quella di chiusura. Senza, ottieni
`frame6`. Lua unisce esattamente quello che gli dai e non aggiunge niente di
suo.
Quando unisci un numero a una stringa in questo modo, Lua lo trasforma in testo
per te, ed è per questo che non hai dovuto fare niente di speciale. Non lo fa
con tutto. Se provi a unire qualcosa che non è né testo né un numero, Lua si
ferma con un errore, e il modo per aggirarlo è `tostring`, che trasforma in
testo qualsiasi cosa:
[source,lua]
----
overlayPrint(0, 0, "frame " .. tostring(frames))
----
Quella riga fa esattamente la stessa cosa di quella prima. Per i numeri
`tostring` non ti serve. Ti servirà nella terza lezione.
[#_make_it_move]
=== Fallo muovere
Ora hai tutto quello che ti serve per muovere la parola. La parola viene
disegnata dove dici tu, hai un modo per conservare un numero da un fotogramma
all'altro, e hai un modo per cambiarlo.
Dai alla colonna una variabile tutta sua fuori dalla funzione, e aumentala
dentro.
[source,lua]
----
frames = 0
wordX = 0
function onOverlayUpdate()
frames = frames + 1
wordX = wordX + 1
overlayClear()
overlayPrint(wordX, 2, "Hello!")
overlayPrint(0, 0, "frame " .. frames)
return OVERLAY_UPDATED
end
----
Salvalo. La parola schizza fuori dal lato destro e non torna più.
Si è spostata di una colonna a ogni fotogramma, sessanta volte al secondo,
quindi ha attraversato tutta la finestra in circa un secondo. Rallentala
aggiungendo meno di una colonna intera:
[source,lua]
----
wordX = wordX + 0.25
----
Adesso le servono quattro fotogrammi per spostarsi di una colonna, e la parola
striscia. Quasi sempre `wordX` è un numero come `12.75`, che non è affatto una
colonna, e Singe se la cava buttando via tutto quello che c'è dopo il punto
quando disegna: `12.75` viene disegnato nella colonna dodici. Tenere la parte
frazionaria nella variabile è proprio ciò che ti permette di muoverti più
lentamente di una colonna a fotogramma. Il numero è più fine dello schermo.
[#_where_is_the_edge]
=== Dov'è il bordo?
La parola se ne va ancora. Per farla tornare indietro devi sapere dov'è il
bordo destro, e potresti scoprirlo contando: un gioco senza video ha per
impostazione predefinita un mondo di 720 per 480, l'overlay su cui disegni è la
metà, 360 per 240, e le lettere del font da console incorporato sono larghe sei
pixel, quindi ci sono sessanta colonne.
Non farlo. Chiedilo al motore.
[source,lua]
----
lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word)
----
`overlayGetWidth` e `overlayGetHeight` sono altre due funzioni fornite da Singe,
come `overlayPrint`. Non prendono niente e restituiscono un numero: quanto è
larga e quanto è alta la superficie su cui disegni. Nella prima lezione hai
visto `return` restituire un valore, e questo è l'altro capo del filo -- una
funzione che chiami può darti qualcosa, e qui fai dei calcoli con quello che ti
ha dato.
I numeri che ti danno sono in pixel, e `overlayPrint` conta in celle di
carattere, quindi devi dividere. `overlayGetFontWidth` ti dice quanti pixel è
larga una cella, e `overlayGetFontHeight` quanti è alta, così non devi mai
sapere tu la risposta. Le due barre vogliono dire *dividi e butta via la parte
frazionaria*, perché un terzo di cella non è un posto in cui si possa stampare.
La normale barra singola ti darebbe una frazione, e nemmeno una frazione di
cella è un posto.
`string.len` conta i caratteri di una stringa, ed è lì perché la parola torni
indietro quando è la sua *ultima* lettera a raggiungere il bordo, non la prima.
Prova senza e vedrai sei caratteri scivolare nel nulla.
Le maiuscole di `CELL_WIDTH`, che contiene quello che ti dà
`overlayGetFontWidth`, sono un'abitudine, non una regola. A Lua non importa,
ma un nome in maiuscolo è il modo in cui i programmatori dicono «questo è un
numero che imposto una volta in cima e non tocco mai più», ed è meglio di
un `6` buttato lì a metà file che fra un mese nessuno sa spiegare.
[#_turn_it_round]
=== Fallo tornare indietro
Muoversi nell'altro senso non costa nessuna fatica: invece di aggiungere `0.25`
a ogni fotogramma, aggiungi `-0.25`. Quindi tieni la quantità in una variabile
tutta sua e cambiale segno quando la parola arriva a un bordo.
[source,lua]
----
if wordX > lastColumn then
wordX = lastColumn
speedX = -speedX
end
----
Questa è una decisione, e le decisioni sono tutta la terza lezione, quindi per
una lezione fidati. Leggila ad alta voce: *se* `wordX` ha superato
`lastColumn`, *allora* fai le due righe rientrate; altrimenti saltale. A
chiuderla è `end`, nello stesso modo in cui `end` chiude una funzione.
La prima riga inchioda la parola esattamente sul bordo, così una parola veloce
non può oltrepassarlo. La seconda è il cambio di segno. `-speedX` è meno
quello che contiene `speedX`, quindi se conteneva `0.25` ora contiene `-0.25`,
e aggiungere `-0.25` a ogni fotogramma riporta la parola indietro da dove è
venuta. Quando raggiunge il bordo sinistro, il blocco `if` corrispondente
cambia di nuovo il segno, e torna a `0.25`.
Altri due blocchi così per il bordo in alto e quello in basso, e la parola
rimbalza per tutta la finestra.
[#_the_whole_thing]
=== Tutto insieme
[source,lua]
----
CELL_WIDTH = overlayGetFontWidth()
CELL_HEIGHT = overlayGetFontHeight()
TOP_ROW = 2
word = "Hello!"
wordX = 0
wordY = TOP_ROW
speedX = 0.25
speedY = 0.125
frames = 0
lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word)
lastRow = overlayGetHeight() // CELL_HEIGHT - 1
function onOverlayUpdate()
frames = frames + 1
wordX = wordX + speedX
wordY = wordY + speedY
if wordX < 0 then
wordX = 0
speedX = -speedX
end
if wordX > lastColumn then
wordX = lastColumn
speedX = -speedX
end
if wordY < TOP_ROW then
wordY = TOP_ROW
speedY = -speedY
end
if wordY > lastRow then
wordY = lastRow
speedY = -speedY
end
overlayClear()
overlayPrint(0, 0, "frame " .. frames)
overlayPrint(wordX, wordY, word)
return OVERLAY_UPDATED
end
----
`TOP_ROW` tiene la parola fuori dalle prime due righe in alto, così non
scarabocchia mai sopra il contatore dei fotogrammi. `lastRow` contiene un `- 1`
invece di uno `string.len`, perché una parola è larga molti caratteri ma è
sempre alta una riga sola.
Nota che tutta la metà superiore del file viene eseguita una volta e tutta la
metà inferiore sessanta volte al secondo, e che capisci quale è quale guardando
il rientro. Ecco a che cosa servivano gli spazi.
[#_try_it_2]
=== Prova tu
. *Cambia le velocità.* Prova `speedX = 1` e `speedY = 1`. Poi prova `0.05` per
entrambe. Poi rendine una molto più grande dell'altra.
. *Cambia la parola.* Metti tra le virgolette una parola molto più lunga --
abbastanza da riempire un terzo della finestra -- e guarda il rimbalzo a
destra avvenire ancora nel posto giusto. È `string.len` che fa il suo lavoro.
. *Togli la pulizia.* Cancella la riga `overlayClear()`. La prima lezione aveva
promesso che un giorno sarebbe contato, e quel giorno è oggi: la parola
spalma una scia per tutta la finestra e non ne cancella mai niente, perché
disegnare ha sempre disegnato sopra quello che c'era. Rimettila.
. *Impediscile di andare di lato.* Metti `speedX = 0` ed eseguilo. Scopri perché
ora la parola non lascia mai la colonna zero, e perché il blocco `if` del
bordo sinistro non scatta mai.
. *Rompi apposta il contatore dei fotogrammi.* Riporta la riga `frames = 0`
dentro la funzione, sopra `frames = frames + 1`. Guarda il contatore bloccarsi
a uno mentre la parola continua a muoversi perfettamente. Poi scopri perché la
parola non ne risente.
[#_break_it_on_purpose_2]
=== Rompilo apposta
Sostituisci i due puntini nella riga del contatore dei fotogrammi con un segno
più, così che diventi:
[source,lua]
----
overlayPrint(0, 0, "frame " + frames)
----
Salvalo. La finestra si chiude, Singe si ferma e il terminale dice:
----
Error executing function 'onOverlayUpdate': numbers/numbers.singe:42: attempt to add a 'string' with a 'number'
----
seguito da diverse righe che cominciano con `stack traceback:`, che per ora puoi
ignorare.
Leggilo come ti ha insegnato la prima lezione: il file, la riga, poi la
lamentela. La riga 42 è quella che hai cambiato. La lamentela dice che a Lua è
stato chiesto di *sommare* una stringa a un numero, e sommare è roba da numeri.
`+` e `..` sulla pagina si somigliano e fanno lavori completamente diversi: il più
è aritmetica, `..` è colla.
Vale la pena incontrare questo errore per il punto in cui è comparso. L'errore
della prima lezione ha fermato Singe prima ancora che il tuo gioco partisse,
perché Lua non riusciva proprio a leggere il file. Questo invece è una riga
perfettamente leggibile che va storta solo quando viene eseguita, quindi la
finestra si è aperta, il gioco è partito ed è morto al primo fotogramma. Tutti e
due i tipi ti dicono il file e la riga. In ogni caso, comincia da lì.
[#_what_you_learned_2]
=== Cosa hai imparato
* Una variabile è un nome con dietro un valore, e il valore può cambiare.
* `=` vuol dire «metti il lato destro nel nome a sinistra», non «è uguale a».
* `frames = frames + 1` calcola prima il lato destro, poi lo memorizza.
* `+`, `-`, `*` e `/` fanno i calcoli che ti aspetti; `//` divide e butta via
la parte frazionaria.
* `..` unisce il testo, e strada facendo trasforma un numero in testo.
* Una riga dentro `onOverlayUpdate` viene eseguita a ogni fotogramma. Una riga
fuori da tutte le funzioni viene eseguita una volta sola, prima che il gioco
cominci.
* Tutto ciò che deve ricordare qualcosa da un fotogramma all'altro deve vivere
fuori.
* `overlayGetWidth` e `overlayGetHeight` ti dicono le dimensioni della
superficie su cui disegni, così non devi mai tirare a indovinare.
* `overlayClear` conta dal momento in cui qualcosa si muove.
[#_next_time_2]
=== La prossima volta
La parola si muove, ma si muove da sola e niente di quello che fai cambia
qualcosa. Nella terza lezione prendi i quattro blocchi `if` che hai usato sulla
fiducia, impari che cosa dicono davvero e li rivolgi ai tasti freccia invece
che ai bordi dello schermo. Il riquadro che alla fine guiderai per la finestra
è il giocatore del gioco che costruirai nella settima lezione.