singe/docs/it/lessons/06-tables.adoc

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.