singe/docs/it/lessons/03-decisions.adoc

527 lines
19 KiB
Text

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