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

775 lines
26 KiB
Text

[#_lesson_6_lists_of_things]
== Lección 6: Listas de cosas
image::learn/06-tables.png[La lección terminada, 480]
Tienes ciento veinte estrellas y ninguna de ellas existe. No hay ninguna
estrella en ningún sitio de tu programa: hay un bucle que da ciento veinte
vueltas, y en cada vuelta calcula dos números a partir del número de la estrella
y de su capa, pinta un píxel y lo olvida todo. Las tres capas están ahí para
fingir lo único que ese montaje no puede hacer, que es dejar que una estrella
sea distinta de su vecina.
Esta lección te da lo que falta. Aprenderás a guardar una lista de elementos, a
hacer un elemento que guarda varios valores con nombre a la vez, y luego a
juntar las dos cosas en una lista de elementos que recuerdan cada uno su propia
posición, su velocidad y su brillo. Es la lección más larga de esta parte del
libro y la que sostiene todo lo que viene después. Tómatela con calma.
[#_start_a_new_folder_5]
=== Empieza una carpeta nueva
Una carpeta llamada `tables`, dentro un archivo llamado `tables.singe`, y
`Singe -R tables` ejecutándose en una terminal. Copia dentro el programa
terminado de la lección cinco: el campo de estrellas, la nave y todo lo demás.
[#_a_list]
=== Una lista
Añade esta línea al principio de `tables.singe`, debajo de los otros valores, y
las tres llamadas a `overlayPrint` a `onOverlayUpdate`, encima del `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.")
----
Ejecútalo. `Ada` en una línea, `Alan` en la siguiente, y `There are 3 names.`
debajo.
Las llaves de la primera línea crean una *tabla*. Una tabla es el único
contenedor que tiene Lua, y todo lo que guardes alguna vez en cantidad de más de
uno va en una. Aquí guarda tres cadenas en fila, que es la forma a la que la
gente llama *lista*.
Los corchetes de las líneas siguientes sacan un elemento. `names[1]` es el
primer elemento, `names[3]` es el tercero, y el número entre corchetes se llama
*índice*.
Ese número es donde Lua se separa de casi todos los demás lenguajes que te vas a
encontrar. *Lua cuenta desde uno.* C, Java, Python, JavaScript y casi todos los
demás cuentan desde cero, así que su primer elemento es el elemento cero y su
tercer elemento tiene el índice dos. El primer elemento de Lua es el elemento
uno, y esto es lo único que hace tropezar a los programadores que llegan de otro
sitio. Tú no llegas de ningún sitio, así que lo tienes más fácil: el primero es
el número uno.
Por cierto, `names[0]` no es un error. Es `nil` -- el valor de nada de la
lección tres -- porque ahí no hay nada guardado. Leer un índice que nunca se ha
llenado siempre da `nil`, lo cual es todo un detalle justo hasta que intentas
usarlo.
El `#` delante del nombre de una tabla es el último signo de puntuación nuevo
en una temporada. Te da cuántos elementos hay en la lista, así que `#names` es
`3`. Como la cuenta y el último índice son el mismo número, `names[#names]` es
siempre el último elemento, por larga que se haga la lista.
[#_adding_and_taking_away]
=== Añadir y quitar
Una lista que tienes que escribir entera no le sirve de mucho a un juego. Estas
dos llamadas son como cambia una lista mientras se ejecuta el programa.
[source,lua]
----
table.insert(names, "Katherine")
----
Eso pone `Katherine` al final, y `#names` ahora es `4`. `table.insert` es una
función como cualquier otra, y el punto en medio de su nombre significa que vive
dentro de una tabla llamada `table` que pone Lua -- una tabla de funciones para
trabajar con tablas. No tienes que crearla ni cargarla. Está ahí.
Dale a `table.insert` tres argumentos y el del medio es dónde poner el elemento
nuevo.
[source,lua]
----
table.insert(names, 1, "Edsger")
----
`Edsger` entra por delante, y todo lo que ya estaba se corre para hacerle sitio:
`Ada` era el elemento uno y ahora es el elemento dos. No se sobrescribe nada y
no se pierde nada.
Sacar un elemento es lo mismo, pero al revés.
[source,lua]
----
table.remove(names, 2)
----
El elemento dos se va, y todo lo que había detrás se corre hacia atrás para
cerrar el hueco, así que la lista nunca tiene un agujero en medio.
`table.remove` también te devuelve el elemento que ha sacado, así que
`local gone = table.remove(names, 2)` lo quita y te dice qué era en una sola
línea. Si no pones el número -- `table.remove(names)` -- saca el último
elemento, que es la forma más rápida de usar una lista como una pila.
Pon esas tres llamadas en tu script, de una en una, ejecútalo después de cada
una y mira cómo cambia la cuenta de la tercera línea.
[#_walking_a_list]
=== Recorrer una lista
Ya conoces una forma de visitar cada elemento, porque la usas desde la lección
cuatro:
[source,lua]
----
for i = 1, #names do
overlayPrint(2, i + 1, names[i])
end
----
Funciona. Empieza en uno, para en la cuenta, usa el contador como índice. Hay
una forma mejor, y es la que escribirás de ahora en adelante:
[source,lua]
----
for i, name in ipairs(names) do
overlayPrint(2, i + 1, name)
end
----
`ipairs` recorre una lista desde el elemento uno hasta que se acaba, y le da a
tu bucle dos cosas en cada vuelta: el índice y el propio elemento. Lo que
importa es lo segundo. Con el `for` numérico obtienes un número y tienes que
volver a la tabla con `names[i]` para saber qué es; con `ipairs` el elemento ya
lo tienes en la mano, con el nombre que hayas puesto en segundo lugar.
También es más difícil equivocarse. `for i = 1, #names` es un sitio donde
equivocarte por uno -- empieza en cero e imprimes un `nil`, para en
`#names - 1` y pierdes el último -- y `ipairs` no tiene ni un número con el que
equivocarse.
La mayoría de las veces no quieres el índice, solo el elemento. Escribe el
índice como un solo guion bajo y listo:
[source,lua]
----
for _, name in ipairs(names) do
----
`_` es un nombre perfectamente normal, y nada en Lua lo trata de forma
especial. Es una costumbre que comparten los programadores para decir «aquí
llega algo y no lo voy a usar». Cualquiera que lea tu bucle sabe de un vistazo
que en él el índice no importa.
Borra ahora los experimentos con `names`, y con ellos las líneas de
`overlayPrint`. Ya sabes lo que es una lista. Es hora de meter en una algo mejor
que una cadena.
[#_a_table_can_also_be_a_record]
=== Una tabla también puede ser un registro
Una estrella necesita recordar cuatro cosas: dónde está en horizontal, dónde
está en vertical, lo rápido que cae y lo brillante que es. Cuatro listas
separadas, todas sincronizadas a mano, serían un suplicio. En lugar de eso, crea
una tabla y pon nombres dentro.
[source,lua]
----
local star = {}
star.x = 40
star.y = 0
star.speed = 2
star.shade = 180
----
Las llaves vacías crean una tabla vacía. Las cuatro líneas que siguen meten
valores en ella con nombres en lugar de números, y el punto es como dices a qué
nombre te refieres. `star.x` se lee «la `x` de `star`», y se comporta
exactamente como cualquier otra variable: léela, dale un valor, súmale algo.
[source,lua]
----
star.y = star.y + star.speed
----
Una tabla usada así -- un conjunto fijo de valores con nombre que describen una
cosa -- es un *registro*, y los nombres que tiene dentro son sus *campos*. Es el
mismo tipo de tabla que la lista de antes. La única diferencia es que los
elementos de una lista se encuentran por número y los campos de un registro se
encuentran por nombre, y una misma tabla puede hacer las dos cosas a la vez si
algún día lo quieres.
El punto es una abreviatura. `star.x` y `star["x"]` significan exactamente lo
mismo, y vale la pena verlo una vez: los corchetes aceptan un nombre con la
misma alegría que un número, así que una lista y un registro son de verdad una
sola idea con dos sombreros. Usa el punto. Se lee mejor.
Un campo al que nunca has dado valor es `nil`, igual que un índice que nunca has
llenado. No hay en ningún sitio una lista de nombres de campo permitidos, y nada
comprueba cómo los escribes, así que `star.sped` no es un error -- es `nil`, y
verás lo que pasa después al final de esta lección.
Escribir cuatro líneas para rellenar cuatro campos acaba cansando. Puedes poner
los campos dentro de las llaves al crear la tabla:
[source,lua]
----
local star = {
x = 40,
y = 0,
speed = 2,
shade = 180
}
----
La misma tabla, una sola instrucción. Las comas separan los campos, el último no
necesita coma detrás, y las líneas están repartidas solo para que se lean con
facilidad.
[#_a_list_of_records]
=== Una lista de registros
Aquí está toda la idea de esta lección en dos líneas:
[source,lua]
----
local stars = {}
table.insert(stars, star)
----
Una lista puede guardar cualquier cosa, y eso incluye una tabla. Así que una
lista de registros es una lista de cosas, y cada una recuerda todo sobre sí
misma. `stars[1]` es una estrella entera, y `stars[1].y` es lo que ha bajado esa
estrella por la pantalla. Casi todos los juegos que escribas serán un puñado de
listas como esta: los enemigos, las balas, los objetos que se recogen y los
bloques que caen.
Hay una cosa sobre las tablas que tienes que saber antes de escribir el código,
y pilla a todo el mundo una vez. Pasarle una tabla a una función no la copia. Un
número sí se copia -- por eso `half` pudo trocear su `number` en la lección
cinco sin molestar a `drift` -- pero una tabla se comparte. Los dos nombres se
refieren a la misma tabla, y un cambio hecho a través de uno se ve a través del
otro.
[source,lua]
----
local star = stars[3]
star.y = star.y + 1
----
Eso mueve la tercera estrella de la lista. No una copia de ella: a ella. Esto es
exactamente lo que quieres aquí, y es lo que hace que funcione la sección
siguiente, pero recuérdalo cuando una función que has escrito cambie algo que no
esperabas que cambiara.
[#_rebuild_the_star_field]
=== Reconstruye el campo de estrellas
Fuera la aritmética. Una estrella está a punto de convertirse en una cosa.
Borra `starAt`, `shadeFor`, `drawLayer` y el viejo `drawStar`, y con ellos las
constantes que usaban: `LAYERS`, `STARS_PER_LAYER`, `ACROSS_STEP`,
`DOWN_STEP`, `LAYER_SHIFT` y `LAYER_SPEED`. La variable `drift` también se va,
y con ella la línea de `drift` del principio de `onOverlayUpdate`.
`LAYER_BRIGHT` se queda, con un nombre que ya no habla de capas, y se le unen
dos valores:
[source,lua]
----
local STAR_COUNT = 120
local STAR_SHADE = 55
local BURST_COUNT = 10
----
[source,lua]
----
local stars = {}
----
Ciento veinte es lo que tenías: tres capas de cuarenta. La lista empieza vacía,
y esto es lo que la llena:
[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 estrella y te la devuelve. Mira el último campo y compáralo
con `shadeFor` de la lección cinco: la misma suma, con la velocidad de la propia
estrella donde antes estaba el número de capa. Las capas no han desaparecido;
más bien se han disuelto. Ahora cada estrella elige su propia velocidad, uno,
dos o tres píxeles por fotograma, y una estrella que cae más rápido es más
brillante, que es toda la ilusión de profundidad y cuesta una línea.
La `y` inicial es un parámetro porque los dos sitios que llaman a la función
quieren cosas distintas. Las estrellas que se crean al empezar el juego deben
quedar repartidas por toda la pantalla; las que se crean después entran por
arriba.
[source,lua]
----
local function makeStars()
for number = 1, STAR_COUNT do
table.insert(stars, newStar(math.random(0, height - 1)))
end
end
----
Nada llama todavía a `makeStars`. Añade una línea al final del todo del archivo,
después de todo lo demás y fuera de cualquier función:
[source,lua]
----
makeStars()
----
Hay más que decir sobre esa línea, y tendrá que esperar al final de la lección.
Ahora el dibujo, que se acorta cada vez que lo tocas:
[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
----
`drawStar` recibía tres números en la lección cinco y ahora recibe una estrella.
Todo lo que necesita saber está dentro de la estrella, que es la razón de tener
registros: una sola cosa que pasar de un lado a otro en lugar de cuatro, y
ninguna posibilidad de entregarlas en el orden equivocado. Y `drawStars` ha
perdido su bucle sobre las capas, junto con cualquier interés en cuántas
estrellas hay o de dónde han salido.
[#_stars_that_come_and_go]
=== Estrellas que vienen y van
Ahora la parte que el viejo campo de estrellas no podía hacer de ninguna manera.
Las estrellas caen cada una a su velocidad, se van por abajo y otras las
sustituyen por arriba.
[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
----
Llámala desde `onOverlayUpdate`, en la línea donde antes estaba
`drift = drift + 1`.
Cada estrella baja según su propia `speed`, que es la línea que la lección cinco
no podía escribir. El `while` de abajo es el relleno: mientras haya menos de
ciento veinte estrellas, crea otra en la parte de arriba de la pantalla. Se
ejecuta tantas veces como haga falta y luego se para, que es la herramienta
adecuada cuando no sabes cuántas faltan.
La línea `for` de arriba cuenta hacia atrás, con el paso que viste en la lección
cuatro. `for i = #stars, 1, -1` visita primero el último elemento y va hacia
atrás hasta el elemento uno. No es un adorno. Es la única forma segura de hacer
lo que hace este bucle, y la sección siguiente explica por qué.
[#_never_remove_going_forwards]
=== Nunca quites hacia delante
Escribe el mismo bucle hacia delante y está roto:
[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
----
Síguelo a mano con cuatro estrellas, y supón que las estrellas dos y tres han
llegado las dos abajo.
* `i` es `1`. La estrella uno está bien.
* `i` es `2`. La estrella dos se ha salido por abajo, así que fuera. Todo se
corre hacia abajo: la estrella que era la tres ahora es la dos, y la que era
la cuatro ahora es la tres.
* `i` es `3`. Esa es la estrella que antes era la cuatro. *La estrella tres no
se ha mirado nunca.* Quitar el elemento dos la ha bajado al hueco dos, y el
bucle ya había pasado por el hueco dos y se había ido.
* `i` es `4`. Ya no hay elemento cuatro. `stars[4]` es `nil`.
Dos fallos distintos por un error pequeño. Se saltan elementos, así que una
estrella que había llegado abajo ni siquiera se mira en esta vuelta. Que una
estrella sobreviva un fotograma de más no hace daño, pero el mismo bucle sobre
una lista de enemigos, donde lo que los mata es que el bucle los mire, es un
fallo que perseguirías durante una hora. El segundo fallo hace más ruido. La lista ahora
es más corta que la cuenta con la que empezó el bucle, porque `#stars` se
calculó una vez, antes de quitar nada, así que las últimas vueltas del bucle se
pasan del final de la lista:
----
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» significa que has puesto un punto detrás de algo
que no guardaba nada. El `(local 'star')` del final lo nombra: `star` era `nil`,
así que `star.y` no tenía dónde mirar.
Ir hacia atrás arregla los dos fallos a la vez, y vale la pena ver por qué en
lugar de creértelo sin más. Cuando quitas el elemento `i`, todo lo que va
después de `i` baja un hueco -- y yendo hacia atrás, todo lo que va después de
`i` es todo lo que ya has visitado. Lo que aún está por venir, los elementos
desde `i - 1` hasta `1`, no se ha movido en absoluto. Que la lista encoja a tu
espalda tampoco te puede hacer daño, porque vas hacia el elemento uno y el
elemento uno siempre está ahí.
La regla, que usarás el resto de tu vida: *cuando un bucle pueda quitar
elementos de la lista que está recorriendo, recórrela hacia atrás.*
`ipairs` no es una excepción. Quitar elementos en medio de un bucle `ipairs`
sale mal de la misma forma. `ipairs` es para mirar, y el `for` hacia atrás es
para cambiar.
[#_add_some_yourself]
=== Añade algunas tú
Una rama más en `onInputPressed` y podrás ver cómo crece la lista mientras se
ejecuta el juego. Va con las otras dos:
[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 encima de los callbacks, con tus otras funciones. Y para que
puedas ver cómo cambia la longitud de la lista, pon la cuenta en pantalla en
`onOverlayUpdate`:
[source,lua]
----
overlayPrint(0, 0, "Stars: " .. #stars)
----
Pulsa el botón -- la barra espaciadora, a menos que hayas cambiado de dónde
viene `SWITCH_BUTTON1` -- una docena de veces. La cuenta sube de diez en diez
con cada pulsación y el cielo se llena, y no se repite mientras lo mantienes
pulsado: a `onInputPressed` se le avisa una vez, cuando el botón baja. Luego
mira cómo la cuenta vuelve sola a ciento veinte a medida que las estrellas de
más llegan abajo y se quitan, porque el relleno solo completa la lista hasta
`STAR_COUNT` y nunca la recorta.
Ni el dibujo, ni el movimiento, ni el código que las quita han tenido que
enterarse de que el número de estrellas había cambiado. Todos trabajan a partir
de la lista.
[#_the_whole_thing_5]
=== Todo junto
[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]
=== Qué acaba de pasar
El programa es más corto que el de la lección cinco y hace algo que la lección
cinco ni siquiera podía empezar a hacer. Tres partes merecen una segunda mirada.
[source,lua]
----
makeStars()
----
Esa única línea al final del todo del archivo no está dentro de ninguna función,
y es fácil pasarla por alto. Lua lee tu archivo de arriba abajo cuando Singe lo
carga, y una línea en el nivel exterior del archivo se ejecuta en ese mismo
momento, una vez, antes de que el motor haya llamado a nada. Todo lo que hay
encima son definiciones -- aquí hay un valor, aquí hay una función -- y esta es
la única instrucción que ocurre al cargar. Tiene que ir al final, porque
`makeStars` no existe hasta que se ha leído la línea que la crea.
[source,lua]
----
local star = stars[i]
star.y = star.y + star.speed
----
`star` es una variable local que se crea de nuevo en cada vuelta del bucle, y
guarda la misma tabla que está en la lista -- no una copia. Cambiar `star.y`
mueve la estrella que está en la lista. Si las tablas se copiaran, este bucle
movería ciento veinte copias por fotograma y nada cambiaría nunca en pantalla.
[source,lua]
----
for _, star in ipairs(stars) do
drawStar(star)
end
----
Compáralo con la lección cuatro: dos bucles anidados, dos cuentas por estrella,
un resto para mantener cada una en la pantalla, y tres capas para fingir la
variedad que ahora tienes gratis. El dibujo ya no sabe ni le importa cómo
llegaron las estrellas adonde están, cuántas hay ni qué les pasa después. Cada
parte del programa hace un trabajo, y la lista es en lo que todas se ponen de
acuerdo.
[#_try_it_6]
=== Pruébalo
. *Hazlo más profundo.* Cambia `math.random(1, 3)` por `math.random(1, 6)` y
`STAR_SHADE` por `25`. Más velocidades, más tonos, un cielo más profundo.
. *Menos estrellas, ráfaga más grande.* Pon `STAR_COUNT` a `10` y `BURST_COUNT`
a `100`, y luego pulsa el botón tres o cuatro veces. Mira cómo sube la cuenta
en pantalla, y cronometra cuánto tarda en vaciarse.
. *Dale a una estrella otro campo.* Añade `age = 0` a `newStar`, suma uno a
`star.age` en `moveStars` y pon `stars[1].age` en pantalla junto a la cuenta.
No hace falta cambiar nada más del programa para que cada estrella lleve un
dato nuevo.
. *Deja de quitarlas.* Borra las tres líneas del bloque `if star.y >= height`
de `moveStars` y ejecútalo. Espera diez segundos, luego lee la cuenta en
pantalla y mira el cielo. Averigua cómo pueden ser ciertas las dos cosas a la
vez.
. *Hazlo hacia delante.* Cambia `for i = #stars, 1, -1` por `for i = 1, #stars`
y ejecútalo hasta que se caiga. Luego vuelve a leer la sección de antes con el
error de verdad delante.
[#_break_it_on_purpose_6]
=== Rómpelo a propósito
Escribe mal el nombre de un campo. En `moveStars`, cambia `star.speed` por
`star.sped`:
[source,lua]
----
star.y = star.y + star.sped
----
Ejecútalo:
----
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'
----
Tus números de línea no serán exactamente estos a menos que tu archivo coincida
línea a línea con el de arriba. Lo que lees es el nombre del archivo, el número
que va detrás y la queja.
Lee primero el final de la primera línea. `field 'sped'` es Lua diciéndote qué
nombre ha salido vacío, y te está diciendo que era un campo -- algo detrás de
un punto -- y no una variable tuya. «Attempt to perform arithmetic on a nil
value» es lo que estaba haciendo cuando se dio cuenta: sumar `nil` a un número,
cosa que no se puede hacer.
No hubo ninguna queja cuando la tabla se creó sin un campo `sped`, ni en el
momento en que lo pediste. Una tabla te da `nil` por cualquier nombre que no
tenga, sin decir nada. El error llega más tarde, en la primera línea que intenta
*usar* la nada que te dieron, y eso puede estar muy lejos de la errata. Cuando
veas `nil value (field 'something')`, busca el sitio donde ese nombre está
escrito de otra forma.
[#_what_you_learned_6]
=== Lo que has aprendido
* Las llaves crean una tabla, el único contenedor que tiene Lua. Una tabla usada
como lista guarda elementos en fila, y los corchetes con un índice sacan uno.
* Lua cuenta desde uno. Casi todos los demás lenguajes cuentan desde cero.
* `#list` es cuántos elementos tiene, y `list[#list]` es el último.
* `table.insert` añade un elemento, al final o en la posición que digas;
`table.remove` saca uno y cierra el hueco.
* Un índice o un campo que nunca se ha llenado es `nil`, y nada te avisa hasta
que lo usas.
* `ipairs` recorre una lista y te da el índice y el elemento; `_` es la
costumbre para un índice que no quieres.
* Una tabla usada como registro guarda campos con nombre, a los que se llega con
un punto.
* Una lista de registros es como un juego guarda sus enemigos, sus balas y todo
lo demás de lo que hay más de uno.
* Las tablas se comparten, no se copian, cuando las pasas de un lado a otro.
* Cuando un bucle pueda quitar elementos de la lista que recorre, recórrela
hacia atrás.
[#_next_time_6]
=== La próxima vez
Ya tienes todas las piezas de un juego: cosas que se mueven, una nave que
responde a los controles, decisiones, bucles, funciones y una lista en la que
las cosas pueden entrar y de la que pueden salir mientras el juego se ejecuta.
La lección siete las junta en un juego en el que puedes perder. Te mueves por la
parte de abajo de la pantalla, caen bloques desde arriba y tú te apartas de su
camino -- y los bloques que caen son una lista de registros, creados, movidos y
quitados exactamente igual que tus estrellas.