[#_lesson_3_making_decisions] == Lezione 3: Prendere decisioni image::learn/03-decisions.png[La lezione finita, 480] Tutto quello che hai scritto finora fa la stessa cosa a ogni fotogramma. La parola della seconda lezione si muoveva che tu lo volessi o no, e l'unico motivo per cui tornava indietro erano quattro blocchi di codice che ti era stato detto di prendere sulla fiducia. Questa lezione è quei quattro blocchi, spiegati per bene. Alla fine il tuo programma farà domande, agirà in base alle risposte e -- visto che la domanda più interessante che un gioco possa fare è «che cosa sta facendo il giocatore?» -- guiderai un riquadro per la finestra con i tasti freccia. Tieniti quel riquadro. È il giocatore del gioco che costruirai nella settima lezione. [#_start_a_new_folder_2] === Crea una nuova cartella Una cartella chiamata `decisions`, dentro un file chiamato `decisions.singe`, e `Singe -R decisions` in esecuzione in un terminale. Come l'ultima volta. [#_what_an_if_really_says] === Che cosa dice davvero un `if` Ecco il blocco della seconda lezione, da solo. [source,lua] ---- if wordX > lastColumn then wordX = lastColumn end ---- Quattro parti. La parola `if`. Una domanda. La parola `then`. E un corpo di righe che vengono eseguite solo quando la risposta alla domanda è sì, chiuso da `end`. La domanda è `wordX > lastColumn`, e il vero lavoro lo fa il `>`. Confronta i due numeri ai suoi lati e produce una risposta che è o *vero* o *falso*. Quelle due parole non sono testo e non sono numeri: sono valori a tutti gli effetti, chiamati *booleani*, e un booleano è l'unica cosa che interessa a un `if`. Il rientro è la stessa idea della prima lezione: le righe interne sono rientrate così vedi a colpo d'occhio che cosa appartiene al blocco `if`, e la riga `end` è allineata con la riga `if` che ha aperto il blocco. Lua lo eseguirebbe anche senza spazi. Tu non riusciresti a leggerlo. Questi confronti sono sei, e li userai tutti. [cols="1,4"] |=== | `a < b` | a è minore di b | `a > b` | a è maggiore di b | `a <= b` | a è minore o uguale a b | `a >= b` | a è maggiore o uguale a b | `a == b` | a è uguale a b | `a ~= b` | a non è uguale a b |=== Gli ultimi due sono quelli a cui fare attenzione, per motivi diversi. `~=` vuol dire «non è uguale a». La maggior parte dei linguaggi lo scrive `!=`, e se hai già visto un programma, le tue dita scriveranno `!=` e Lua lo rifiuterà. La tilde sulla tua tastiera sta accanto al tasto `1` o sotto il `#`, a seconda di dove vivi. `==` sono due segni di uguale, e non è un errore di battitura per uno solo. È uno degli errori più comuni nella programmazione, e vale la pena fermarcisi un momento. Nella seconda lezione hai imparato che un segno di uguale vuol dire *metti il lato destro nel nome a sinistra*. È una cosa che fai. Cambia qualcosa. Due segni di uguale vogliono dire *queste due cose sono uguali?* È una domanda che fai. Non cambia niente, e restituisce vero o falso. [source,lua] ---- lives = 3 if lives == 3 then ---- La prima riga mette `lives` a tre. La seconda chiede se vale tre. Lavori diversi, simboli diversi, a un carattere di distanza. Se li scambi Lua ti coglie in fallo, e alla fine di questa lezione incontrerai l'errore che usa. [#_when_the_answer_is_no] === Quando la risposta è no Da solo, un `if` non fa proprio niente quando la risposta è falsa. Molto spesso vuoi che succeda invece l'altra cosa, e quello è `else`. [source,lua] ---- if lives > 0 then message = "Ready" else message = "Game Over" end ---- Viene eseguito esattamente uno dei due corpi, mai tutti e due, mai nessuno, quindi alla fine `message` contiene sempre una delle due stringhe e mai niente. Quando le possibilità sono più di due, `elseif` le mette in catena. [source,lua] ---- if score >= 10000 then rank = "ace" elseif score >= 5000 then rank = "pilot" elseif score >= 1000 then rank = "rookie" else rank = "passenger" end ---- Lua prova le domande dall'alto e si ferma alla prima che è vera. Un punteggio di 6000 non è maggiore di 10000, quindi prova la successiva, che è vera, quindi `rank` diventa `pilot` e le altre due domande non vengono mai fatte. Quest'ultimo punto conta: anche il ramo `elseif` per 1000 è vero per un punteggio di 6000, e non viene mai raggiunto, ed è per questo che l'ordine va dal più grande al più piccolo. Tutta la catena ha bisogno di un solo `end`, per quante parti `elseif` contenga. È una sola decisione, non quattro. [#_true_false_and_nothing_at_all] === Vero, falso e niente del tutto Non devi per forza confrontare due cose per ottenere un booleano. Puoi tenerne uno. [source,lua] ---- paused = false ---- È una variabile come tutte le altre, e il valore che contiene è il booleano `false` in persona, scritto senza virgolette perché non è la parola «false», è l'idea. Lo stesso vale per `true`. Una variabile che ne contiene uno può andare dritta in un `if` senza nessun confronto: [source,lua] ---- if paused then ---- Leggilo come «se in pausa». Si legge meglio di `if paused == true`, che fa la stessa cosa facendo il giro lungo, e i programmatori esperti ti guarderanno strano se lo scrivi. Tre parole uniscono e capovolgono queste risposte. [source,lua] ---- if alive and not shielded then if coins > 0 or freePlay then ---- `and` è vero quando lo sono entrambi i lati. `or` è vero quando lo è uno dei due lati, o tutti e due. `not` trasforma vero in falso e falso in vero. Si leggono come l'inglese e, cosa insolita in programmazione, vogliono dire proprio quello che dicono in inglese. C'è un altro valore da conoscere, ed è quello che produce gli errori più confusi del tuo primo anno. [source,lua] ---- overlayPrint(0, 0, "lives " .. tostring(lives)) ---- Se in `lives` non hai mai messo niente, quella riga stampa `lives nil`. Lua non si lamenta di un nome che non ha mai visto: ti dà `nil`, che è la sua parola per *qui non c'è niente*. Non è zero e non è una stringa vuota. Zero è un numero che volevi memorizzare. `nil` è l'assenza di un valore. Hai già incontrato `nil` due volte senza saperlo. `frames = frames + 1` senza la riga `frames = 0` sopra fallisce perché hai chiesto a Lua di aggiungere uno a niente. E anche una callback scritta male è `nil`, ed è per questo che una `onOverlayUpdate` scritta male ti dà una finestra nera e nessun errore: Singe cerca il nome, non ci trova niente e se ne va tranquillo per la sua strada. `tostring` è in quella riga perché `..` non unisce a niente un booleano o un `nil`. I numeri li converte per te; questi no. Era la promessa della fine della seconda lezione, e ora sai a che cosa serve. Infine, la regola che tiene insieme tutta la sezione, perché `if` accetta qualsiasi cosa gli dai, non solo vero e falso: [quote] In Lua tutto è vero tranne `false` e `nil`. Zero è vero. Una stringa vuota è vera. Solo quei due non lo sono, e una volta che lo sai, `if movingLeft then` si legge benissimo sia che `movingLeft` contenga `true`, sia che contenga `false`, sia che non sia mai stata impostata. [#_reading_the_controls] === Leggere i comandi È ora di chiedere qualcosa al giocatore. Per questo serve una nuova callback. [source,lua] ---- function onInputPressed(what) if what == SWITCH_RIGHT then boxX = boxX + 10 end end ---- `onInputPressed` è la seconda callback che scrivi, e funziona esattamente come descritto nella prima lezione: tu la scrivi, Singe la chiama. `onOverlayUpdate` viene chiamata a tempo. Questa viene chiamata quando succede qualcosa -- quando il giocatore preme qualsiasi cosa il motore riconosca. La differenza rispetto a `onOverlayUpdate` è la parola dentro le parentesi. Singe deve dirti *quale* comando è stato premuto, quindi passa un valore alla funzione, e `what` è il nome con cui hai scelto di riceverlo. La prima lezione chiamava argomenti le cose che passi a una funzione; visto da dentro, il nome che ne riceve uno è un *parametro*. Il nome è tuo: chiamalo `what`, chiamalo `button`, chiamalo `k`. Singe lo riempie in ogni caso. `SWITCH_RIGHT` è un nome che il motore ha già dato a un valore, come `OVERLAY_UPDATED` nella prima lezione. Per usarlo non devi caricare niente: Singe li definisce tutti prima che il tuo script parta. Ce n'è uno per ogni cosa che ha un cabinato: `SWITCH_LEFT`, `SWITCH_RIGHT`, `SWITCH_UP`, `SWITCH_DOWN`, da `SWITCH_BUTTON1` a `SWITCH_BUTTON4`, `SWITCH_START1`, `SWITCH_COIN1` e altri ancora. La tabella Costanti del motore del riferimento copre tutta la famiglia, che va da `SWITCH_UP` a `SWITCH_RECORD`. Di proposito non si chiamano «il tasto freccia destra», perché lo stesso `SWITCH_RIGHT` arriva sia che il giocatore abbia premuto un tasto freccia, sia che abbia spinto un joystick o la levetta di un controller. Scrivi il gioco una volta e funziona con tutti e tre. Aggiungi quella callback, dai a `boxX` un valore iniziale fuori dalle funzioni e disegna qualcosa in `boxX`. Premi la freccia destra. Il riquadro salta di dieci pixel ogni volta che la premi. [#_it_happened_and_it_is_happening] === È successo, e sta succedendo Tieni premuto il tasto freccia. Non succede nient'altro. `onInputPressed` viene chiamata una volta, mentre il tasto scende, e non di nuovo finché non lo lasci e lo premi ancora. Non c'è ripetizione automatica. Per certe cose è esattamente giusto -- sparare un colpo, inserire un gettone, scegliere una voce di menu -- e per camminare è esattamente sbagliato, perché camminare deve continuare a succedere finché il tasto resta premuto. Sono davvero due domande diverse. *È successo* è un evento, e lo vieni a sapere una volta. *Sta succedendo* è uno stato, e dura. Singe ti informa degli eventi, e il modo per trasformare gli eventi in uno stato è ricordarli da te. Due callback, con una variabile in mezzo. [source,lua] ---- movingRight = false function onInputPressed(what) if what == SWITCH_RIGHT then movingRight = true end end function onInputReleased(what) if what == SWITCH_RIGHT then movingRight = false end end ---- `onInputReleased` è la terza callback, ed è lo specchio della seconda: Singe la chiama quando il tasto risale, con lo stesso valore `SWITCH_`. Insieme fanno in modo che `movingRight` dica sempre la verità su quello che sta facendo in questo momento il dito del giocatore. Non si è ancora mosso niente, ed è proprio questo il punto. Premere e muoversi ora sono due lavori separati. Il movimento va dove va tutto il lavoro da fare a ogni fotogramma. [source,lua] ---- function onOverlayUpdate() if movingRight then boxX = boxX + BOX_SPEED end ... ---- Ora il riquadro scivola fluido finché il tasto resta premuto, a una velocità che decidi tu, e i calcoli sono tornati dove devono stare, nell'unica funzione che gira sessanta volte al secondo. Questa forma -- gli eventi impostano dei flag, il fotogramma agisce sui flag -- è il modo in cui quasi ogni gioco gestisce i comandi, e la userai di nuovo in ogni lezione in cui c'è un giocatore. [#_drawing_a_box] === Disegnare un riquadro `overlayBox` prende due angoli: il lato sinistro e quello in alto del rettangolo, poi il destro e quello in basso. [source,lua] ---- colorForeground(255, 220, 0) overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE) ---- Disegna un *contorno*, quattro linee spesse un pixel, e non un rettangolo pieno. Tra le chiamate per disegnare sull'overlay un rettangolo pieno proprio non c'è. Quando più avanti vorrai un blocco pieno ci sono modi per ottenerlo, e la nona lezione ti mostra quello solito. I suoi numeri sono pixel, non celle di carattere. Questo trae in inganno, perché `overlayPrint` nella seconda lezione contava in celle da sei pixel per tredici e tutto il resto di Singe conta in pixel. `overlayPrint` è l'eccezione; le altre chiamate per disegnare -- `overlayBox`, `overlayLine`, `overlayCircle`, `overlayEllipse` e `overlayPlot` -- lavorano tutte negli stessi pixel che riporta `overlayGetWidth`. `colorForeground` imposta il colore, con tre numeri da 0 a 255: quanto rosso, quanto verde, quanto blu. Tutti e tre a 255 è bianco, tutti a 0 è nero, e `(255, 220, 0)` è un giallo caldo. Nota che non è un argomento di `overlayBox`. È un'impostazione: la cambi, e tutto quello che disegni dopo esce nel nuovo colore finché non la cambi di nuovo. `overlayPrint` è ancora una volta l'eccezione -- il font da console arriva con i suoi colori già incorporati e ti ignora. [#_the_whole_thing_2] === Tutto insieme [source,lua] ---- BOX_SIZE = 20 BOX_SPEED = 2 width = overlayGetWidth() height = overlayGetHeight() maxX = width - BOX_SIZE - 1 maxY = height - BOX_SIZE - 1 boxX = width // 2 boxY = height // 2 movingLeft = false movingRight = false movingUp = false movingDown = false function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true elseif what == SWITCH_UP then movingUp = true elseif what == SWITCH_DOWN then movingDown = true end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false elseif what == SWITCH_UP then movingUp = false elseif what == SWITCH_DOWN then movingDown = false end end function onOverlayUpdate() if movingLeft and not movingRight then boxX = boxX - BOX_SPEED elseif movingRight and not movingLeft then boxX = boxX + BOX_SPEED end if movingUp and not movingDown then boxY = boxY - BOX_SPEED elseif movingDown and not movingUp then boxY = boxY + BOX_SPEED end if boxX < 0 then boxX = 0 elseif boxX > maxX then boxX = maxX end if boxY < 0 then boxY = 0 elseif boxY > maxY then boxY = maxY end overlayClear() if movingLeft or movingRight or movingUp or movingDown then colorForeground(255, 220, 0) else colorForeground(80, 160, 255) end overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE) overlayPrint(0, 0, "x " .. boxX .. " y " .. boxY) return OVERLAY_UPDATED end ---- Lì dentro ci sono quattro cose che meritano una seconda occhiata. `movingLeft and not movingRight` c'è perché un giocatore può tenere premute tutte e due le frecce insieme. Senza il `not`, le due litigherebbero e il riquadro tremolerebbe o andrebbe alla deriva; se invece c'è, tenerle premute tutte e due vuol dire che non vince nessuna e il riquadro sta fermo, che è quello che un giocatore si aspetta. `maxX` viene calcolato una volta sola in cima, usando `overlayGetWidth` come nella seconda lezione, e il `- 1` c'è perché l'ultimo pixel della superficie è uno in meno della sua larghezza. Togli il `- 1` e il lato destro del riquadro cade fuori dalla superficie e sparisce, perché Singe butta via senza lamentarsi tutto quello che disegni fuori dall'overlay. I blocchi di contenimento usano `elseif` invece di due blocchi `if` separati. Il riquadro non può essere oltre il bordo sinistro e oltre il bordo destro nello stesso momento, quindi fare la seconda domanda quando la prima era vera è lavoro sprecato. E la decisione sul colore è un `if` che ha il solo compito di scegliere un valore. Giallo mentre il giocatore fa qualcosa, blu quando non fa niente: un modo gratuito per vedere che la tua gestione dei comandi funziona davvero. [#_try_it_3] === Prova tu . *Cambia la velocità.* `BOX_SPEED = 1` e `BOX_SPEED = 8`. A otto, guarda che cosa fa il contenimento quando spingi il riquadro in un angolo. . *Mostra i tuoi flag.* Aggiungi una riga a `onOverlayUpdate`: `overlayPrint(0, 1, "left " .. tostring(movingLeft))`. Premi e rilascia la freccia sinistra e guardalo cambiare. Poi togli il `tostring` e leggi l'errore che ti dà Lua. . *Fallo andare più veloce quando un pulsante è premuto.* Aggiungi un flag `running` comandato da `SWITCH_BUTTON1` nelle due callback dei comandi, e usa un `if` in `onOverlayUpdate` per muoverti del doppio di `BOX_SPEED` finché è vero. . *Segna il centro.* Disegna un secondo riquadro, piccolo, al centro della finestra, che cambi colore quando il riquadro del giocatore ci si sovrappone. Ti servirà `and` per unire quattro confronti, ed è più difficile di quanto sembri. Sbagliarlo un po' è normale; l'undicesima lezione lo fa per bene. . *Togli i rilasci.* Cancella tutta la funzione `onInputReleased` ed eseguilo. Il riquadro fa un passo e poi non si ferma più. Scopri quale riga ora non viene mai raggiunta. [#_break_it_on_purpose_3] === Rompilo apposta Trova il blocco di contenimento e cambia il `<` del suo primo confronto in un segno di uguale singolo, così che diventi: [source,lua] ---- if boxX = 0 then ---- Salvalo. La finestra resta lì vuota e il terminale dice: ---- Error running script: decisions/decisions.singe:57: 'then' expected near '=' ---- È l'errore da cui metteva in guardia tutta la parte su `==`, e il messaggio è un buon esempio di errore del tutto esatto e del tutto inutile se lo prendi alla lettera. Lua non ti sta chiedendo di aggiungere la parola `then`. Lua è arrivato fino a `if boxX`, subito dopo si aspettava un confronto o un `then`, e ha trovato `=`, che non può essere né l'uno né l'altro. Ogni volta che vedi `'then' expected` dopo un `if`, cerca un `=` singolo che dovrebbe essere doppio. È quasi sempre quello. Nota dove è comparso questo: il gioco non è mai partito, come per la stringa non chiusa della prima lezione, perché Lua non riusciva a leggere il file. L'errore del contatore dei fotogrammi nella seconda lezione è successo durante l'esecuzione. Gli errori nella forma del tuo codice ti fermano prima che il gioco cominci. Gli errori in quello che il tuo codice fa aspettano che venga eseguito. [#_what_you_learned_3] === Cosa hai imparato * `if question then ... end` esegue un corpo solo quando la risposta è vera. * `else` dà l'altro caso, e `elseif` ne mette in catena altri; vince la prima domanda vera e le altre non vengono mai fatte. * I confronti sono `<`, `>`, `<=`, `>=`, `==` e `~=`. * `=` mette un valore in un nome. `==` chiede se due cose sono uguali. * `true` e `false` sono valori, e puoi tenerli in delle variabili. * `and`, `or` e `not` uniscono e capovolgono quelle risposte. * `nil` è quello che ottieni da un nome che non hai mai impostato, e non è zero. * Tutto è vero tranne `false` e `nil`. * `onInputPressed` e `onInputReleased` sono callback che Singe chiama con un valore `SWITCH_` quando il giocatore preme o lascia andare un comando. * Una pressione è un evento e succede una volta; tieni lo stato in una variabile e agisci in base a quello a ogni fotogramma. * `overlayBox` disegna un contorno in pixel, e `colorForeground` è un'impostazione, non un argomento. [#_next_time_3] === La prossima volta Un riquadro è facile. Cento stelle no, almeno non nel modo in cui hai scritto le cose finora, perché scrivere cento righe `overlayPlot` non è l'idea di bella serata di nessuno. La quarta lezione è il ciclo: dire al computer di fare la stessa cosa molte volte, con qualcosa di diverso a ogni giro, ed è questo che rende possibile uno schermo pieno di qualsiasi cosa.