775 lines
26 KiB
Text
775 lines
26 KiB
Text
[#_lesson_6_lists_of_things]
|
|
== Lezione 6: Elenchi di cose
|
|
|
|
image::learn/06-tables.png[La lezione finita, 480]
|
|
|
|
Hai centoventi stelle e nessuna di loro esiste. Nel tuo programma non c'è una
|
|
stella da nessuna parte: c'è un ciclo che gira centoventi volte, e a ogni giro
|
|
ricava due numeri dal numero della stella e dal suo livello, disegna un pixel e
|
|
dimentica tutto. I tre livelli servono a simulare l'unica cosa che quella
|
|
sistemazione non sa fare, cioè lasciare che una stella sia diversa dalla sua
|
|
vicina.
|
|
|
|
Questa lezione ti dà il pezzo mancante. Imparerai a tenere un elenco di
|
|
elementi, a creare un elemento che contiene diversi valori con un nome tutti
|
|
insieme, e poi a mettere insieme le due cose in un elenco di elementi che
|
|
ricordano ciascuno la propria posizione, velocità e luminosità. È la lezione più
|
|
lunga di questa parte del libro, e quella su cui si appoggia tutto ciò che viene
|
|
dopo. Prenditela con calma.
|
|
|
|
[#_start_a_new_folder_5]
|
|
=== Crea una nuova cartella
|
|
|
|
Una cartella chiamata `tables`, dentro un file chiamato `tables.singe`, e
|
|
`Singe -R tables` in esecuzione in un terminale. Copiaci dentro il programma
|
|
finito della quinta lezione: il campo stellare, l'astronave e tutto il resto.
|
|
|
|
[#_a_list]
|
|
=== Un elenco
|
|
|
|
Aggiungi questa riga in cima a `tables.singe`, sotto gli altri valori, e le tre
|
|
chiamate `overlayPrint` a `onOverlayUpdate`, sopra il `return`.
|
|
|
|
[source,lua]
|
|
----
|
|
local names = { "Ada", "Grace", "Alan" }
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(2, 2, names[1])
|
|
overlayPrint(2, 3, names[3])
|
|
overlayPrint(2, 4, "There are " .. #names .. " names.")
|
|
----
|
|
|
|
Avvialo. `Ada` su una riga, `Alan` sulla successiva e `There are 3 names.`
|
|
sotto.
|
|
|
|
Le parentesi graffe della prima riga creano una *tabella*. Una tabella è l'unico
|
|
contenitore che Lua abbia, e tutto ciò di cui conserverai più di uno ci finisce
|
|
dentro. Qui contiene tre stringhe in fila, che è la forma che tutti chiamano
|
|
*elenco*.
|
|
|
|
Le parentesi quadre delle righe successive tirano fuori un elemento. `names[1]`
|
|
è il primo elemento, `names[3]` è il terzo, e il numero tra le parentesi quadre
|
|
si chiama *indice*.
|
|
|
|
Quel numero è il punto in cui Lua si separa da quasi tutti gli altri linguaggi
|
|
che incontrerai. *Lua conta da uno.* C, Java, Python, JavaScript e quasi tutti
|
|
gli altri contano da zero, quindi il loro primo elemento è l'elemento zero e il
|
|
loro terzo elemento ha indice due. Il primo elemento di Lua è l'elemento uno, ed
|
|
è questa l'unica cosa che fa inciampare i programmatori che arrivano da altrove.
|
|
Tu non arrivi da nessuna parte, quindi hai il compito più facile: il primo è il
|
|
numero uno.
|
|
|
|
`names[0]`, tra l'altro, non è un errore. È `nil` -- il valore «niente» della
|
|
terza lezione -- perché lì non c'è niente. Leggere un indice che non è mai stato
|
|
riempito dà sempre `nil`, il che è una gentilezza fino al momento in cui provi a
|
|
usarlo.
|
|
|
|
Il `#` davanti al nome di una tabella è l'ultimo segno di punteggiatura nuovo
|
|
per un bel po'. Ti dà quanti elementi ci sono nell'elenco, quindi `#names` vale
|
|
`3`. Siccome il conteggio e l'ultimo indice sono lo stesso numero,
|
|
`names[#names]` è sempre l'ultimo elemento, per quanto l'elenco si allunghi.
|
|
|
|
[#_adding_and_taking_away]
|
|
=== Aggiungere e togliere
|
|
|
|
Un elenco che devi scrivere per intero non serve a molto in un gioco. Queste due
|
|
chiamate sono il modo in cui un elenco cambia mentre il programma gira.
|
|
|
|
[source,lua]
|
|
----
|
|
table.insert(names, "Katherine")
|
|
----
|
|
|
|
Questa mette `Katherine` in fondo, e ora `#names` vale `4`. `table.insert` è una
|
|
funzione come tutte le altre, e il punto in mezzo al suo nome vuol dire che vive
|
|
dentro una tabella chiamata `table` fornita da Lua -- una tabella di funzioni
|
|
per lavorare sulle tabelle. Non devi crearla né caricarla. C'è già.
|
|
|
|
Dai a `table.insert` tre argomenti e quello in mezzo dice dove mettere il nuovo
|
|
elemento.
|
|
|
|
[source,lua]
|
|
----
|
|
table.insert(names, 1, "Edsger")
|
|
----
|
|
|
|
`Edsger` entra in testa, e tutto ciò che c'era già scivola avanti per fargli
|
|
posto: `Ada` era l'elemento uno e ora è l'elemento due. Niente viene
|
|
sovrascritto e niente va perso.
|
|
|
|
Togliere un elemento è l'immagine speculare.
|
|
|
|
[source,lua]
|
|
----
|
|
table.remove(names, 2)
|
|
----
|
|
|
|
L'elemento due se ne va, e tutto ciò che viene dopo scivola indietro per
|
|
chiudere il vuoto, così l'elenco non ha mai un buco in mezzo. `table.remove`
|
|
restituisce anche l'elemento che ha tolto, quindi
|
|
`local gone = table.remove(names, 2)` se ne libera e ti dice che cos'era in una
|
|
riga sola. Togli del tutto il numero -- `table.remove(names)` -- e toglie
|
|
l'ultimo elemento, che è il modo più rapido di usare un elenco come una pila.
|
|
|
|
Metti queste tre chiamate nel tuo script, una alla volta, avvialo dopo ognuna e
|
|
guarda cambiare il conteggio sulla terza riga.
|
|
|
|
[#_walking_a_list]
|
|
=== Percorrere un elenco
|
|
|
|
Conosci già un modo per visitare ogni elemento, perché lo usi dalla quarta
|
|
lezione:
|
|
|
|
[source,lua]
|
|
----
|
|
for i = 1, #names do
|
|
overlayPrint(2, i + 1, names[i])
|
|
end
|
|
----
|
|
|
|
Funziona. Comincia da uno, fermati al conteggio, usa il contatore come indice.
|
|
C'è un modo migliore, ed è quello che scriverai d'ora in poi:
|
|
|
|
[source,lua]
|
|
----
|
|
for i, name in ipairs(names) do
|
|
overlayPrint(2, i + 1, name)
|
|
end
|
|
----
|
|
|
|
`ipairs` percorre un elenco dall'elemento uno finché non finisce, e a ogni giro
|
|
passa al tuo ciclo due cose: l'indice e l'elemento stesso. Il punto è la
|
|
seconda. Con il `for` numerico ottieni un numero e devi tornare alla tabella con
|
|
`names[i]` per scoprire che cos'è; con `ipairs` l'elemento ce l'hai già in mano,
|
|
con qualunque nome tu abbia messo per secondo.
|
|
|
|
È anche più difficile sbagliarlo. `for i = 1, #names` è un posto perfetto per
|
|
sbagliare di uno -- comincia da zero e stampi un `nil`, fermati a `#names - 1`
|
|
e perdi l'ultimo -- e in `ipairs` non c'è proprio nessun numero da sbagliare.
|
|
|
|
Il più delle volte non vuoi l'indice, solo l'elemento. Scrivi l'indice come un
|
|
singolo trattino basso e non pensarci più:
|
|
|
|
[source,lua]
|
|
----
|
|
for _, name in ipairs(names) do
|
|
----
|
|
|
|
`_` è un nome perfettamente normale, e niente in Lua lo tratta in modo speciale.
|
|
È un'abitudine che i programmatori condividono per dire «qui arriva qualcosa e
|
|
non ho intenzione di usarlo». Chiunque legga il tuo ciclo capisce a colpo
|
|
d'occhio che lì l'indice non conta.
|
|
|
|
Ora cancella gli esperimenti con `names`, e con loro le righe `overlayPrint`.
|
|
Sai che cos'è un elenco. È ora di metterci dentro qualcosa di meglio di una
|
|
stringa.
|
|
|
|
[#_a_table_can_also_be_a_record]
|
|
=== Una tabella può anche essere un record
|
|
|
|
Una stella deve ricordare quattro cose: dove si trova in orizzontale, dove si
|
|
trova in verticale, quanto velocemente cade e quanto è luminosa. Quattro elenchi
|
|
separati, tenuti allineati a mano, sarebbero un tormento. Invece crea una
|
|
tabella e mettici dentro dei nomi.
|
|
|
|
[source,lua]
|
|
----
|
|
local star = {}
|
|
star.x = 40
|
|
star.y = 0
|
|
star.speed = 2
|
|
star.shade = 180
|
|
----
|
|
|
|
Le graffe vuote creano una tabella vuota. Le quattro righe dopo ci mettono
|
|
dentro dei valori sotto dei nomi invece che sotto dei numeri, e il punto è il
|
|
modo in cui dici quale nome intendi. `star.x` si legge la `x` di `star`, e si
|
|
comporta esattamente come qualsiasi altra variabile: leggila, assegnale un
|
|
valore, aggiungici qualcosa.
|
|
|
|
[source,lua]
|
|
----
|
|
star.y = star.y + star.speed
|
|
----
|
|
|
|
Una tabella usata così -- un insieme fisso di valori con un nome che descrivono
|
|
una cosa sola -- è un *record*, e i nomi al suo interno sono i suoi *campi*. È
|
|
lo stesso tipo di tabella dell'elenco qui sopra. L'unica differenza è che gli
|
|
elementi di un elenco si trovano per numero e i campi di un record si trovano
|
|
per nome, e una tabella può fare entrambe le cose insieme, se mai ti servisse.
|
|
|
|
Il punto è un'abbreviazione. `star.x` e `star["x"]` vogliono dire esattamente la
|
|
stessa cosa, e vale la pena vederlo una volta: le parentesi quadre accettano un
|
|
nome volentieri quanto un numero, quindi un elenco e un record sono davvero
|
|
un'idea sola con due cappelli diversi. Usa il punto. Si legge meglio.
|
|
|
|
Un campo che non hai mai impostato è `nil`, come un indice che non hai mai
|
|
riempito. Non c'è da nessuna parte un elenco dei nomi di campo permessi, e
|
|
niente controlla come scrivi, quindi `star.sped` non è un errore -- è `nil`, e
|
|
quello che succede dopo lo vedrai alla fine di questa lezione.
|
|
|
|
Scrivere quattro righe per riempire quattro campi stanca presto. Puoi mettere i
|
|
campi dentro le graffe quando crei la tabella:
|
|
|
|
[source,lua]
|
|
----
|
|
local star = {
|
|
x = 40,
|
|
y = 0,
|
|
speed = 2,
|
|
shade = 180
|
|
}
|
|
----
|
|
|
|
Stessa tabella, un'istruzione sola. Le virgole separano i campi, l'ultimo non
|
|
vuole una virgola dopo, e le righe sono distribuite così solo perché si leggano
|
|
facilmente.
|
|
|
|
[#_a_list_of_records]
|
|
=== Un elenco di record
|
|
|
|
Ecco l'idea intera di questa lezione in due righe:
|
|
|
|
[source,lua]
|
|
----
|
|
local stars = {}
|
|
table.insert(stars, star)
|
|
----
|
|
|
|
Un elenco può contenere qualsiasi cosa, comprese le tabelle. Quindi un elenco di
|
|
record è un elenco di cose che ricordano ciascuna tutto di sé. `stars[1]` è una
|
|
stella intera, e `stars[1].y` è la distanza di quella stella dall'alto dello
|
|
schermo. Quasi ogni gioco che scriverai è una manciata di elenchi come questo: i
|
|
nemici, i proiettili, gli oggetti da raccogliere e i blocchi che cadono.
|
|
|
|
C'è una cosa sulle tabelle che devi sapere prima di scrivere il codice, e prima
|
|
o poi frega tutti, una volta. Passare una tabella a una funzione non la copia.
|
|
Un numero viene copiato -- è per questo che nella quinta lezione `half` poteva
|
|
fare a pezzi il suo `number` senza disturbare `drift` -- ma una tabella è
|
|
condivisa. Entrambi i nomi si riferiscono alla stessa tabella, e una modifica
|
|
fatta attraverso uno si vede attraverso l'altro.
|
|
|
|
[source,lua]
|
|
----
|
|
local star = stars[3]
|
|
star.y = star.y + 1
|
|
----
|
|
|
|
Questo sposta la terza stella dell'elenco. Non una sua copia: proprio lei. È
|
|
esattamente ciò che vuoi qui, ed è ciò che fa funzionare la prossima sezione, ma
|
|
ricordatelo quando una funzione che hai scritto cambia qualcosa che non ti
|
|
aspettavi.
|
|
|
|
[#_rebuild_the_star_field]
|
|
=== Ricostruisci il campo stellare
|
|
|
|
Fuori l'aritmetica. Una stella sta per diventare una cosa.
|
|
|
|
Cancella `starAt`, `shadeFor`, `drawLayer` e il vecchio `drawStar`, e con loro
|
|
le costanti che usavano: `LAYERS`, `STARS_PER_LAYER`, `ACROSS_STEP`,
|
|
`DOWN_STEP`, `LAYER_SHIFT` e `LAYER_SPEED`. Se ne va anche la variabile
|
|
`drift`, e con lei la riga di `drift` in cima a `onOverlayUpdate`.
|
|
`LAYER_BRIGHT` resta, con un nome che non parla più di livelli, e lo
|
|
raggiungono due valori:
|
|
|
|
[source,lua]
|
|
----
|
|
local STAR_COUNT = 120
|
|
local STAR_SHADE = 55
|
|
local BURST_COUNT = 10
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
local stars = {}
|
|
----
|
|
|
|
Centoventi è quello che avevi: tre livelli da quaranta. L'elenco parte vuoto, e
|
|
questo è ciò che lo riempie:
|
|
|
|
[source,lua]
|
|
----
|
|
local function newStar(y)
|
|
local speed = math.random(1, 3)
|
|
local star = {
|
|
x = math.random(0, width - 1),
|
|
y = y,
|
|
speed = speed,
|
|
shade = math.random(60, 90) + speed * STAR_SHADE
|
|
}
|
|
return star
|
|
end
|
|
----
|
|
|
|
`newStar` crea una stella e te la restituisce. Guarda l'ultimo campo e
|
|
confrontalo con `shadeFor` nella quinta lezione: la stessa somma, con la
|
|
velocità della stella al posto del numero del livello. I livelli non sono
|
|
spariti, si sono piuttosto dissolti. Ogni stella ora si sceglie la propria
|
|
velocità, uno, due o tre pixel a fotogramma, e una stella che cade più veloce è
|
|
più luminosa, il che è tutta l'illusione della profondità e costa una riga.
|
|
|
|
La `y` di partenza è un parametro perché i due chiamanti vogliono cose diverse.
|
|
Le stelle create quando il gioco parte devono essere sparse su tutto lo schermo;
|
|
quelle create dopo entrano dall'alto.
|
|
|
|
[source,lua]
|
|
----
|
|
local function makeStars()
|
|
for number = 1, STAR_COUNT do
|
|
table.insert(stars, newStar(math.random(0, height - 1)))
|
|
end
|
|
end
|
|
----
|
|
|
|
Per ora niente chiama `makeStars`. Aggiungi una riga proprio in fondo al file,
|
|
dopo tutto il resto e fuori da qualsiasi funzione:
|
|
|
|
[source,lua]
|
|
----
|
|
makeStars()
|
|
----
|
|
|
|
C'è altro da dire su quella riga, e aspetta la fine della lezione. Ora il
|
|
disegno, che si accorcia ogni volta che lo tocchi:
|
|
|
|
[source,lua]
|
|
----
|
|
local function drawStar(star)
|
|
colorForeground(star.shade, star.shade, star.shade)
|
|
overlayPlot(star.x, star.y)
|
|
end
|
|
|
|
|
|
local function drawStars()
|
|
for _, star in ipairs(stars) do
|
|
drawStar(star)
|
|
end
|
|
end
|
|
----
|
|
|
|
Nella quinta lezione `drawStar` prendeva tre numeri, e ora prende una stella.
|
|
Tutto ciò che deve sapere è dentro la stella, ed è proprio questa la ragione per
|
|
cui esistono i record: una cosa sola da passare in giro invece di quattro, e
|
|
nessuna possibilità di consegnarle nell'ordine sbagliato. E `drawStars` ha perso
|
|
il suo ciclo sui livelli, insieme a qualsiasi interesse per quante stelle ci
|
|
sono o da dove sono venute.
|
|
|
|
[#_stars_that_come_and_go]
|
|
=== Stelle che vanno e vengono
|
|
|
|
Ora la parte che il vecchio campo stellare non sapeva proprio fare. Le stelle
|
|
cadono ognuna alla propria velocità, escono dal fondo e vengono sostituite in
|
|
cima.
|
|
|
|
[source,lua]
|
|
----
|
|
local function moveStars()
|
|
for i = #stars, 1, -1 do
|
|
local star = stars[i]
|
|
star.y = star.y + star.speed
|
|
if star.y >= height then
|
|
table.remove(stars, i)
|
|
end
|
|
end
|
|
while #stars < STAR_COUNT do
|
|
table.insert(stars, newStar(0))
|
|
end
|
|
end
|
|
----
|
|
|
|
Chiamala da `onOverlayUpdate`, sulla riga dove prima c'era
|
|
`drift = drift + 1`.
|
|
|
|
Ogni stella scende della sua `speed`, ed è la riga che la quinta lezione non
|
|
poteva scrivere. Il `while` in fondo è il rabbocco: finché ci sono meno di
|
|
centoventi stelle, creane un'altra in cima allo schermo. Gira tutte le volte che
|
|
serve e poi si ferma, ed è lo strumento giusto quando non sai quante ne mancano.
|
|
|
|
La riga del `for` in cima conta all'indietro, usando il passo che hai incontrato
|
|
nella quarta lezione. `for i = #stars, 1, -1` visita prima l'ultimo elemento e
|
|
torna indietro fino all'elemento uno. Non è un vezzo. È l'unico modo sicuro di
|
|
fare ciò che fa questo ciclo, e la prossima sezione spiega perché.
|
|
|
|
[#_never_remove_going_forwards]
|
|
=== Mai togliere andando avanti
|
|
|
|
Scrivi lo stesso ciclo in avanti ed è rotto:
|
|
|
|
[source,lua]
|
|
----
|
|
for i = 1, #stars do
|
|
local star = stars[i]
|
|
star.y = star.y + star.speed
|
|
if star.y >= height then
|
|
table.remove(stars, i)
|
|
end
|
|
end
|
|
----
|
|
|
|
Seguilo a mano con quattro stelle, e supponi che le stelle due e tre abbiano
|
|
raggiunto entrambe il fondo.
|
|
|
|
* `i` vale `1`. La stella uno va bene.
|
|
* `i` vale `2`. La stella due è uscita dal fondo, quindi viene tolta. Tutto
|
|
scivola giù: la stella che era la tre ora è la due, e quella che era la
|
|
quattro ora è la tre.
|
|
* `i` vale `3`. È la stella che prima era la quattro. *La stella tre non è mai
|
|
stata guardata.* Togliere l'elemento due l'ha fatta scendere nella posizione
|
|
due, e il ciclo dalla posizione due era già passato.
|
|
* `i` vale `4`. Non c'è più nessun elemento quattro. `stars[4]` è `nil`.
|
|
|
|
Due guasti distinti da un piccolo errore. Alcuni elementi vengono saltati,
|
|
quindi una stella che aveva raggiunto il fondo questa volta non viene nemmeno
|
|
guardata. Una stella che sopravvive un fotogramma in più non fa danni, ma lo
|
|
stesso ciclo su un elenco di nemici, dove essere guardati è ciò che li uccide, è
|
|
un bug che inseguiresti per un'ora. Il secondo guasto fa più rumore. L'elenco
|
|
ora è più corto del conteggio con cui il ciclo è partito, perché `#stars` è
|
|
stato calcolato una volta sola, prima di qualsiasi rimozione, quindi gli ultimi
|
|
giri del ciclo vanno oltre la fine dell'elenco:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': tables/tables.singe:60: attempt to index a nil value (local 'star')
|
|
stack traceback:
|
|
tables/tables.singe:60: in upvalue 'moveStars'
|
|
tables/tables.singe:124: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
«Attempt to index a nil value» vuol dire che hai messo un punto dopo qualcosa
|
|
che non conteneva niente. Il `(local 'star')` alla fine dice di chi si tratta:
|
|
`star` era `nil`, quindi `star.y` non aveva dove guardare.
|
|
|
|
Andare all'indietro risolve entrambi i guasti in un colpo solo, e vale la pena
|
|
vedere perché invece di crederci sulla parola. Quando togli l'elemento `i`,
|
|
tutto ciò che viene dopo `i` scende di una posizione -- e andando all'indietro,
|
|
tutto ciò che viene dopo `i` è tutto ciò che hai già visitato. Ciò che deve
|
|
ancora venire, gli elementi da `i - 1` giù fino a `1`, non si è mosso affatto.
|
|
Nemmeno l'elenco che si accorcia alle tue spalle può farti male, perché stai
|
|
camminando verso l'elemento uno e l'elemento uno c'è sempre.
|
|
|
|
La regola, che userai per tutta la vita: *quando un ciclo potrebbe togliere
|
|
elementi dall'elenco che sta percorrendo, percorrilo all'indietro.*
|
|
|
|
`ipairs` non fa eccezione. Togliere elementi in mezzo a un ciclo `ipairs` va
|
|
storto nello stesso modo. `ipairs` serve a guardare, e il `for` all'indietro
|
|
serve a cambiare.
|
|
|
|
[#_add_some_yourself]
|
|
=== Aggiungine qualcuna tu
|
|
|
|
Un ramo in più in `onInputPressed` e puoi guardare l'elenco crescere mentre il
|
|
gioco gira. Va insieme agli altri due:
|
|
|
|
[source,lua]
|
|
----
|
|
elseif what == SWITCH_BUTTON1 then
|
|
addBurst()
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
local function addBurst()
|
|
for number = 1, BURST_COUNT do
|
|
table.insert(stars, newStar(0))
|
|
end
|
|
end
|
|
----
|
|
|
|
`addBurst` va sopra le callback, con le tue altre funzioni. E perché tu possa
|
|
vedere cambiare la lunghezza dell'elenco, metti il conteggio sullo schermo in
|
|
`onOverlayUpdate`:
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(0, 0, "Stars: " .. #stars)
|
|
----
|
|
|
|
Premi il pulsante -- la barra spaziatrice, a meno che tu non abbia cambiato da
|
|
dove arriva `SWITCH_BUTTON1` -- una dozzina di volte. Il conteggio sale di dieci
|
|
a ogni pressione e il cielo si infittisce, e se lo tieni premuto non si ripete:
|
|
`onInputPressed` viene avvisata una volta sola, quando il pulsante va giù. Poi
|
|
guarda il conteggio tornare da solo a centoventi man mano che le stelle in più
|
|
raggiungono il fondo e vengono tolte, perché il rabbocco riempie l'elenco solo
|
|
fino a `STAR_COUNT` e non lo accorcia mai.
|
|
|
|
Niente nel disegno, nel movimento o nella rimozione ha dovuto sapere che il
|
|
numero delle stelle era cambiato. Lavorano tutti a partire dall'elenco.
|
|
|
|
[#_the_whole_thing_5]
|
|
=== Tutto insieme
|
|
|
|
[source,lua]
|
|
----
|
|
local STAR_COUNT = 120
|
|
local STAR_SHADE = 55
|
|
local BURST_COUNT = 10
|
|
|
|
local PLAYER_WIDTH = 20
|
|
local PLAYER_HEIGHT = 6
|
|
local PLAYER_SPEED = 3
|
|
local PLAYER_MARGIN = 6
|
|
|
|
local width = overlayGetWidth()
|
|
local height = overlayGetHeight()
|
|
|
|
local stars = {}
|
|
local playerX = width // 2
|
|
local playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN
|
|
local movingLeft = false
|
|
local movingRight = false
|
|
|
|
|
|
local function clamp(value, low, high)
|
|
if value < low then
|
|
return low
|
|
end
|
|
if value > high then
|
|
return high
|
|
end
|
|
return value
|
|
end
|
|
|
|
|
|
local function newStar(y)
|
|
local speed = math.random(1, 3)
|
|
local star = {
|
|
x = math.random(0, width - 1),
|
|
y = y,
|
|
speed = speed,
|
|
shade = math.random(60, 90) + speed * STAR_SHADE
|
|
}
|
|
return star
|
|
end
|
|
|
|
|
|
local function makeStars()
|
|
for number = 1, STAR_COUNT do
|
|
table.insert(stars, newStar(math.random(0, height - 1)))
|
|
end
|
|
end
|
|
|
|
|
|
local function addBurst()
|
|
for number = 1, BURST_COUNT do
|
|
table.insert(stars, newStar(0))
|
|
end
|
|
end
|
|
|
|
|
|
local function moveStars()
|
|
for i = #stars, 1, -1 do
|
|
local star = stars[i]
|
|
star.y = star.y + star.speed
|
|
if star.y >= height then
|
|
table.remove(stars, i)
|
|
end
|
|
end
|
|
while #stars < STAR_COUNT do
|
|
table.insert(stars, newStar(0))
|
|
end
|
|
end
|
|
|
|
|
|
local function drawStar(star)
|
|
colorForeground(star.shade, star.shade, star.shade)
|
|
overlayPlot(star.x, star.y)
|
|
end
|
|
|
|
|
|
local function drawStars()
|
|
for _, star in ipairs(stars) do
|
|
drawStar(star)
|
|
end
|
|
end
|
|
|
|
|
|
local function drawPlayer()
|
|
colorForeground(80, 255, 120)
|
|
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
|
|
end
|
|
|
|
|
|
local function movePlayer()
|
|
if movingLeft then
|
|
playerX = playerX - PLAYER_SPEED
|
|
end
|
|
if movingRight then
|
|
playerX = playerX + PLAYER_SPEED
|
|
end
|
|
playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1)
|
|
end
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = true
|
|
elseif what == SWITCH_BUTTON1 then
|
|
addBurst()
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = false
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = false
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
overlayClear()
|
|
movePlayer()
|
|
moveStars()
|
|
drawStars()
|
|
drawPlayer()
|
|
overlayPrint(0, 0, "Stars: " .. #stars)
|
|
return OVERLAY_UPDATED
|
|
end
|
|
|
|
|
|
makeStars()
|
|
----
|
|
|
|
[#_what_just_happened_3]
|
|
=== Cos'è appena successo
|
|
|
|
Il programma è più corto di quello della quinta lezione e fa qualcosa che la
|
|
quinta lezione non poteva nemmeno cominciare a fare. Tre pezzi meritano una
|
|
seconda occhiata.
|
|
|
|
[source,lua]
|
|
----
|
|
makeStars()
|
|
----
|
|
|
|
Quella singola riga in fondo al file non sta dentro nessuna funzione, ed è
|
|
facile non notarla. Lua legge il tuo file dall'alto in basso quando Singe lo
|
|
carica, e una riga al livello esterno del file viene eseguita lì per lì, una
|
|
volta, prima che il motore abbia chiamato qualsiasi cosa. Tutto ciò che sta
|
|
sopra sono definizioni -- ecco un valore, ecco una funzione -- e questa è
|
|
l'unica istruzione che avviene al caricamento. Deve stare in fondo, perché
|
|
`makeStars` non esiste finché non è stata letta la riga che la crea.
|
|
|
|
[source,lua]
|
|
----
|
|
local star = stars[i]
|
|
star.y = star.y + star.speed
|
|
----
|
|
|
|
`star` è una variabile locale creata da capo a ogni giro del ciclo, e contiene
|
|
la stessa tabella che sta nell'elenco -- non una sua copia. Cambiare `star.y`
|
|
sposta la stella che sta nell'elenco. Se le tabelle venissero copiate, questo
|
|
ciclo sposterebbe centoventi copie a fotogramma e sullo schermo non cambierebbe
|
|
mai niente.
|
|
|
|
[source,lua]
|
|
----
|
|
for _, star in ipairs(stars) do
|
|
drawStar(star)
|
|
end
|
|
----
|
|
|
|
Confrontalo con la quarta lezione: due cicli annidati, due calcoli per stella,
|
|
un resto per tenere ciascuna sullo schermo e tre livelli per simulare la varietà
|
|
che ora ottieni gratis. Il disegno non sa più, e non gli interessa, come le
|
|
stelle siano arrivate dove sono, quante siano o che cosa succederà loro dopo.
|
|
Ogni parte del programma fa un solo lavoro, e l'elenco è ciò su cui sono tutte
|
|
d'accordo.
|
|
|
|
[#_try_it_6]
|
|
=== Prova tu
|
|
|
|
. *Rendilo più profondo.* Cambia `math.random(1, 3)` in `math.random(1, 6)` e
|
|
`STAR_SHADE` in `25`. Più velocità, più sfumature, un cielo più profondo.
|
|
. *Meno stelle, raffica più grande.* Imposta `STAR_COUNT` a `10` e
|
|
`BURST_COUNT` a `100`, poi premi il pulsante tre o quattro volte. Guarda
|
|
salire il conteggio sullo schermo, e cronometra quanto ci mette a svuotarsi.
|
|
. *Dai a una stella un altro campo.* Aggiungi `age = 0` a `newStar`, aggiungi
|
|
uno a `star.age` in `moveStars` e metti `stars[1].age` sullo schermo accanto
|
|
al conteggio. Per portarsi dietro un fatto nuovo su ogni stella, nel resto del
|
|
programma non serve cambiare niente.
|
|
. *Smetti di toglierle.* Cancella le tre righe del blocco
|
|
`if star.y >= height` da `moveStars` ed eseguilo. Aspetta dieci secondi, poi
|
|
leggi il conteggio sullo schermo e guarda il cielo. Scopri come possono essere
|
|
vere entrambe le cose insieme.
|
|
. *Fallo in avanti.* Cambia `for i = #stars, 1, -1` in `for i = 1, #stars` ed
|
|
eseguilo finché non cade. Poi rileggi la sezione qui sopra con l'errore vero
|
|
davanti agli occhi.
|
|
|
|
[#_break_it_on_purpose_6]
|
|
=== Rompilo apposta
|
|
|
|
Scrivi male il nome di un campo. In `moveStars`, cambia `star.speed` in
|
|
`star.sped`:
|
|
|
|
[source,lua]
|
|
----
|
|
star.y = star.y + star.sped
|
|
----
|
|
|
|
Eseguilo:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': tables/tables.singe:60: attempt to perform arithmetic on a nil value (field 'sped')
|
|
stack traceback:
|
|
tables/tables.singe:60: in upvalue 'moveStars'
|
|
tables/tables.singe:124: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
I tuoi numeri di riga non saranno esattamente questi, a meno che il tuo file non
|
|
corrisponda riga per riga a quello qui sopra. Quello che leggi sono il nome del
|
|
file, il numero che lo segue e la lamentela.
|
|
|
|
Leggi prima la fine della prima riga. `field 'sped'` è Lua che ti dice quale
|
|
nome è risultato vuoto, e ti dice che era un campo -- qualcosa dopo un punto --
|
|
e non una tua variabile. «Attempt to perform arithmetic on a nil value» è ciò
|
|
che stava facendo quando se n'è accorto: sommare `nil` a un numero, cosa che non
|
|
si può fare.
|
|
|
|
Non ci sono state lamentele quando la tabella è stata creata senza un campo
|
|
`sped`, e nessuna nel momento in cui l'hai chiesto. Una tabella restituisce
|
|
`nil` per qualsiasi nome che non ha, senza commenti. L'errore arriva dopo, alla
|
|
prima riga che prova a *usare* il niente che ti è stato dato, e che può essere
|
|
molto lontana dall'errore di battitura. Quando vedi
|
|
`nil value (field 'something')`, cerca il punto in cui quel nome è stato scritto
|
|
in modo diverso.
|
|
|
|
[#_what_you_learned_6]
|
|
=== Cosa hai imparato
|
|
|
|
* Le graffe creano una tabella, l'unico contenitore che Lua abbia. Una tabella
|
|
usata come elenco contiene elementi in fila, e le parentesi quadre con un
|
|
indice ne tirano fuori uno.
|
|
* Lua conta da uno. Quasi tutti gli altri linguaggi contano da zero.
|
|
* `#list` è quanti elementi contiene, e `list[#list]` è l'ultimo.
|
|
* `table.insert` aggiunge un elemento, in fondo o nella posizione che indichi;
|
|
`table.remove` ne toglie uno e chiude il vuoto.
|
|
* Un indice o un campo mai riempito è `nil`, e niente ti avvisa finché non lo
|
|
usi.
|
|
* `ipairs` percorre un elenco e ti passa l'indice e l'elemento; `_` è
|
|
l'abitudine per un indice che non ti serve.
|
|
* Una tabella usata come record contiene campi con un nome, che raggiungi con
|
|
un punto.
|
|
* Un elenco di record è il modo in cui un gioco tiene i suoi nemici, i suoi
|
|
proiettili e tutto ciò di cui ha più di uno.
|
|
* Le tabelle, quando le passi in giro, sono condivise, non copiate.
|
|
* Quando un ciclo può togliere elementi dall'elenco che sta percorrendo,
|
|
percorrilo all'indietro.
|
|
|
|
[#_next_time_6]
|
|
=== La prossima volta
|
|
|
|
Ora hai tutti i pezzi di un gioco: cose che si muovono, un'astronave che
|
|
risponde ai comandi, decisioni, cicli, funzioni e un elenco in cui le cose
|
|
possono entrare e da cui possono uscire mentre il gioco gira.
|
|
|
|
La settima lezione li mette insieme in un gioco in cui puoi perdere. Ti sposti
|
|
lungo il fondo dello schermo, dei blocchi cadono dall'alto e tu stai alla larga
|
|
-- e i blocchi che cadono sono un elenco di record, creati, spostati e tolti
|
|
esattamente come le tue stelle.
|