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