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