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