431 lines
16 KiB
Text
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.
|