[#_lesson_4_doing_it_again] == Lección 4: Otra vez image::learn/04-repeating.png[La lección terminada, 480] Has dibujado una palabra y una caja. Un juego necesita cien cosas: estrellas, ladrillos, balas, las filas de un menú. Escribir cien líneas no es la respuesta, y si lo fuera, tendrías que escribir otras cien en cuanto quisieras ciento una. Esta lección es la respuesta. Al final de ella, un campo de estrellas irá bajando lentamente por tu ventana, y todas y cada una las dibujarán las mismas cinco líneas de código. [#_start_a_new_folder_3] === Empieza una carpeta nueva Una carpeta llamada `repeating`, dentro un archivo llamado `repeating.singe`, y `Singe -R repeating` ejecutándose en una terminal. [#_forty_dots] === Cuarenta puntos Escribe esto y ejecútalo. [source,lua] ---- function onOverlayUpdate() overlayClear() colorForeground(255, 255, 255) for star = 1, 40 do overlayPlot(star * 8, 100) end return OVERLAY_UPDATED end ---- Una fila de cuarenta puntos blancos que cruza el centro de la ventana. `overlayPlot` es la llamada de dibujo más sencilla que hay: pone un píxel en el color de primer plano actual, y es justo lo que pide una estrella. Todo lo interesante aquí son las tres líneas que la rodean. [source,lua] ---- for star = 1, 40 do ---- Eso dice: haz lo que sigue cuarenta veces, y en cada vuelta, `star` guarda un número distinto -- uno la primera vez, dos la segunda, cuarenta la última. Los dos extremos cuentan, así que `1, 40` son de verdad cuarenta vueltas y no treinta y nueve. Esta forma es un *bucle*, y este en concreto es un *bucle for numérico*. `star` es un nombre que te inventas, igual que te inventaste `frames` en la lección dos, y el bucle lo rellena por ti. No le das valor tú, y no deberías cambiarlo dentro del bucle. Solo existe entre el `for` y su `end`; pide su valor después y obtendrás `nil`. El `do` es obligatorio y es fácil olvidarlo. Si lo quitas, Lua dice `'do' expected near`, seguido de lo que haya encontrado en su lugar, que será lo primero de la línea siguiente. [source,lua] ---- overlayPlot(star * 8, 100) ---- El cuerpo. Esto es lo que se ejecuta cuarenta veces, y es la misma línea cada vez -- lo que cambia es `star`, así que el primer punto cae en una x de 8, el segundo en 16 y el último en 320. Un bucle solo sirve de algo cuando el cuerpo hace algo con el número del bucle, y lo primero que buscarás siempre es hacer cuentas con él. El cuerpo va sangrado un paso más que el `for`, por el mismo motivo por el que has sangrado todo lo demás hasta ahora. [source,lua] ---- end ---- Cierra el bucle. Ya conoces tres cosas que necesitan un `end`: `function`, `if` y `for`. Todas funcionan igual, se anidan unas dentro de otras, y la sangría es cómo llevas la cuenta de qué `end` va con qué. [#_counting_by_more_than_one] === Contar de más de uno en uno El bucle cuenta de uno en uno salvo que le digas otra cosa. Añade un tercer número y contará de ese en ese. [source,lua] ---- for x = 0, overlayGetWidth() - 1, 10 do overlayPlot(x, 100) end ---- Léelo como *desde, hasta, de cuánto en cuánto*. Este va 0, 10, 20, y así sucesivamente, y se para en el último píxel de la superficie o antes, lo que dibuja una línea de puntos de lado a lado sea cual sea el tamaño de la ventana. Es la misma fila de puntos de antes, descrita según dónde van y no según cuántos hay. El paso puede ser negativo, y entonces cuenta hacia atrás. [source,lua] ---- for countdown = 10, 1, -1 do ---- Diez, nueve, ocho, hasta uno. Con un paso negativo el segundo número es el suelo en vez del techo, que es lo que esperarías, y equivocarse de signo da un bucle que no se ejecuta nunca: `for i = 10, 1 do` sin paso cuenta hacia arriba desde diez hacia uno, ya ha pasado el final antes de empezar y no hace nada. Ningún error, ninguna salida. Si un bucle tuyo parece no ejecutarse nunca, comprueba primero la dirección. El paso también puede ser una fracción, aunque lo querrás menos a menudo de lo que crees. [#_one_loop_inside_another] === Un bucle dentro de otro El cuerpo de un bucle es código normal, y el código normal puede contener un bucle. [source,lua] ---- for row = 0, overlayGetHeight() - 1, 20 do for column = 0, overlayGetWidth() - 1, 20 do overlayPlot(column, row) end end ---- Eso dibuja una cuadrícula de puntos por toda la ventana. Ponlo en tu `onOverlayUpdate` en lugar de la fila y míralo, porque vale la pena tener claro cómo se ejecuta ahora, y no en la lección once, cuando algo dependa de ello. El bucle de fuera se ejecuta una vez con `row` a 0. Dentro de eso, todo el bucle de dentro se ejecuta de principio a fin -- dieciocho columnas, dieciocho puntos, una fila de la cuadrícula. Solo cuando el bucle de dentro ha terminado pasa el de fuera a `row` a 20 y vuelve a ejecutar entero el bucle de dentro. Así que el cuerpo del bucle de dentro se ejecuta dieciocho por doce veces, que son doscientas dieciséis, en doce vueltas del de fuera. Esa multiplicación es la razón de que los bucles anidados sean la forma de llenar un rectángulo de cualquier cosa, y también la razón de que debas echar un vistazo a los números antes de anidar tres. Fíjate en que la posición de cada punto necesita las dos variables del bucle: `column` para lo horizontal y `row` para lo vertical. Ese es el patrón. Si el cuerpo de dentro solo usa la variable de dentro, el bucle de dentro está dibujando lo mismo una y otra vez en el mismo sitio, y has escrito una forma lenta de hacerlo una vez. [#_when_you_do_not_know_how_many] === Cuando no sabes cuántas veces Un bucle `for` necesita saber cuántas veces antes de empezar. A veces no lo sabes, y entonces quieres el otro tipo. [source,lua] ---- x = 0 while x < overlayGetWidth() do overlayPlot(x, 100) x = x + 10 end ---- Un bucle `while` comprueba una pregunta antes de cada vuelta, exactamente la pregunta que haría un `if`, y sigue mientras la respuesta sea verdadera. Cuando la respuesta es falsa, se para y continúa con la línea que va después del `end`. Si la respuesta es falsa ya la primera vez, el cuerpo no se ejecuta nunca. Ese ejemplo dibuja la misma línea de puntos que el `for` con un paso de diez, y necesita tres líneas para hacer lo que `for` hacía en una, así que usa `for` para eso. Esta es la diferencia que decide: * Usa `for` cuando sabes el número de vueltas antes de empezar. Cuarenta estrellas. Uno de cada diez píxeles a lo ancho. Cada fila de una cuadrícula. * Usa `while` cuando estás esperando a que algo se haga verdadero y no puedes decir de antemano cuánto tardará. Repartir cartas hasta que se acabe la baraja. Seguir preguntando hasta que el jugador escriba algo válido. Recorrer una lista hasta encontrar lo que buscabas. Un `while` tiene tres partes y tienes que escribir las tres tú: preparar la variable antes del bucle, comprobarla en el `while` y cambiarla dentro del cuerpo. El bucle `for` hace las tres por ti, y por eso es el que debes usar cuando encaja. Olvida la tercera parte en un `while` y la pregunta no deja nunca de ser verdadera, que es lo que harás a propósito al final de esta lección. [#_a_star_made_out_of_its_own_number] === Una estrella hecha de su propio número Ahora el campo de estrellas, y un problema que da forma al resto de la primera parte. Cada estrella necesita su propia x y su propia y, y no tienes forma de guardar cien variables separadas -- escribir `star1X`, `star2X` y así hasta cien es peor que escribir las cien líneas que querías evitar. Así que no las guardes. Calcula cada una a partir de lo único que hace a una estrella distinta de sus vecinas: su número. [source,lua] ---- starX = (star * 37) % width ---- El `%` es el último de los operadores aritméticos y es el *resto*: lo que sobra después de dividir. Quince entre cuatro es tres y sobran tres, así que `15 % 4` es 3. Su propiedad útil es que el resultado nunca puede ser tan grande como el número de la derecha, así que el resto entre `width` siempre es una posición en algún sitio de la pantalla, por mucho que crezca el lado izquierdo. La estrella 1 va a 37, la 5 a 185, la 10 a 10 -- 370 ha pasado de 360, ha dado la vuelta y ha vuelto a aparecer por la izquierda. Se elige treinta y siete porque no comparte ningún factor con 360, que es lo que impide que las estrellas caigan en rayas ordenadas; prueba con 36 y verás el problema enseguida. La deriva hacia abajo es el mismo truco con un número que crece con el tiempo. [source,lua] ---- drift = 0 function onOverlayUpdate() drift = drift + 1 ... starY = (star * 61 + math.floor(drift * layer * 0.5)) % height ---- `drift` vive fuera de la función y sube en cada fotograma, exactamente igual que `frames` en la lección dos. Sumarlo a la y de cada estrella mueve todo el campo hacia abajo a la vez, y el `%` devuelve arriba una estrella que se cae por abajo, para siempre, gratis. `math.floor` tira la fracción de un número y devuelve la parte entera que queda por debajo: `math.floor(7.9)` es 7. La lección dos señaló que Singe hace eso por ti cuando dibuja, y es verdad, pero hacerlo tú significa que el número con el que trabajas es el número que aparece en pantalla. Eso importará en la lección siete, cuando dos cosas tengan que ponerse de acuerdo sobre si se están tocando. Fíjate en que ese `layer` es una segunda variable de bucle. Las estrellas se dibujan con un bucle dentro de otro, y el de fuera no es una fila de una cuadrícula sino una *distancia*: tres capas de estrellas, cada una con su propia velocidad y su propio brillo, que es lo que hace que un campo plano de puntos parezca tener profundidad. [#_chance] === El azar Las estrellas titilan. Titilar significa un brillo un poco distinto en cada fotograma, y «un poco distinto» es `math.random`. [source,lua] ---- shade = math.random(60, 90) + layer * 55 colorForeground(shade, shade, shade) ---- `math.random(60, 90)` devuelve un número entero de 60 a 90, los dos incluidos, elegido de nuevo cada vez que la llamas. Llámala sin ningún argumento y obtendrás en cambio una fracción entre 0 y 1, que es la forma que debes usar cuando quieres que algo pase una vez de cada cinco. No tienes que preparar el generador; Lua lo hace cuando arranca Singe. Sumar `layer * 55` saca las capas cercanas del rango oscuro, así que las estrellas lejanas siguen débiles y las cercanas son casi blancas. El mismo rojo, verde y azul, como explicaba la lección tres, es un tono de gris. Como `shade` se elige otra vez en cada fotograma para cada estrella, cada estrella parpadea por su cuenta, lo que a sesenta fotogramas por segundo se ve como un titileo y no como un fallo. [#_the_whole_thing_3] === Todo junto [source,lua] ---- LAYERS = 3 STARS_PER_LAYER = 40 ACROSS_STEP = 37 DOWN_STEP = 61 LAYER_SHIFT = 13 LAYER_SPEED = 0.5 LAYER_BRIGHT = 55 width = overlayGetWidth() height = overlayGetHeight() drift = 0 function onOverlayUpdate() drift = drift + 1 overlayClear() for layer = 1, LAYERS do for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT colorForeground(shade, shade, shade) overlayPlot(starX, starY) end end return OVERLAY_UPDATED end ---- Ciento veinte estrellas, dibujadas por cinco líneas, y el archivo es más corto que el de la lección tres. También es hasta donde llega este enfoque, y vale la pena ser sincero sobre el porqué mientras lo tienes delante. Cada estrella de ahí es una fórmula. De una estrella no se *recuerda* nada: no puedes hacer una más brillante que su vecina y que se quede así, no puedes derribar una del cielo y no puedes hacer que el jugador recoja una, porque no hay ningún sitio donde apuntar que le ha pasado algo. La estrella diecisiete no es una cosa. Es un número que pasa un momento por una variable llamada `star`. En cuanto quieres cien cosas que recuerden cada una algo propio, necesitas un sitio donde poner cien grupos de datos. Eso es la lección seis, y el campo de estrellas es la razón de que exista. [#_try_it_4] === Pruébalo . *Más y menos.* Prueba `STARS_PER_LAYER = 200`, luego `4`. Luego `LAYERS = 8` y fíjate en lo que hace `layer * LAYER_BRIGHT` con `shade` cuando el número de capa se hace grande. Los números que admite `colorForeground` llegan hasta 255. . *Cambia el espaciado.* Pon `ACROSS_STEP` a 36 y ejecútalo. Las estrellas caen en rayas, porque 36 cabe en 360 exactamente diez veces. Vuelve a ponerlo a 37 y prueba 71 y 90. . *Quita el titileo.* Cambia la línea de `math.random` por un simple `shade = layer * 70` y compara. Decide cuál te gusta más; no hay respuesta correcta, y darte cuenta de que tienes una preferencia es parte del trabajo. . *Haz que caigan hacia arriba.* Un carácter. . *Dibuja también la cuadrícula.* Vuelve a poner el bucle de la cuadrícula anidada de antes, después de las estrellas, en un color oscuro. Dos bucles anidados en una función, dibujando dos cosas distintas, y el segundo no molesta al primero. [#_break_it_on_purpose_4] === Rómpelo a propósito Hasta ahora, todos los errores han sido un mensaje. Este no, y precisamente por eso deberías conocerlo cuando lo estás esperando. Pon esto en tu `onOverlayUpdate`, justo debajo de `overlayClear()`, ejecútalo y lee el párrafo siguiente antes de hacer nada más. [source,lua] ---- x = 0 while x < width do overlayPlot(x, 100) end ---- Falta la línea que hace avanzar `x`, así que `x` se queda en cero, así que `x < width` no deja nunca de ser verdadero. El bucle da vueltas para siempre, pintando el mismo píxel, y está dentro de `onOverlayUpdate`, así que `onOverlayUpdate` no vuelve nunca. Singe está esperando a tu función. No puede dibujar, no puede leer el teclado y no puede salir. Lo que ves es una ventana que deja de moverse. Esc no hace nada. Q no hace nada. Tampoco se imprime nada. Ve a la terminal desde la que arrancaste Singe y pulsa *Ctrl* y *C* a la vez. Singe detiene tu bucle y te dice dónde estaba: ---- Stopped: the script did not return, and Singe was asked to quit. stack traceback: repeating/repeating.singe:20: in function 'onOverlayUpdate' ---- Puede que lo diga dos veces, con unos segundos de diferencia, antes de salir. Con una basta. La línea 20 es el `end` de tu `while`, que es donde el bucle estaba dando vueltas cuando lo detuviste. Quita el bucle, guarda y vuelve a arrancar Singe. Eso es un informe, no un error. Nada se quejó hasta que lo pediste, porque, para el ordenador, no ha ido nada mal. Pediste un bucle que se ejecutara mientras `x` fuera menor que el ancho, y eso es exactamente lo que tienes. Este es el único tipo de error en el que los mensajes de error no te pueden ayudar, y la cura es una costumbre: cada vez que escribas un `while`, escribe la línea que cambia la variable antes de escribir nada más en el cuerpo. [#_what_you_learned_4] === Lo que has aprendido * `for name = first, last do ... end` ejecuta su cuerpo una vez por cada número desde el primero hasta el último, los dos incluidos. * Un tercer número es el paso, y puede ser negativo para contar hacia atrás. * La variable del bucle solo existe dentro del bucle, y el cuerpo debería usarla. * `for` necesita un `do`, y todo bucle necesita un `end`. * Un bucle dentro de otro ejecuta el bucle de dentro entero en cada vuelta del de fuera, y así es como llenas un rectángulo. * `while question do ... end` repite mientras la pregunta sea verdadera, y tienes que cambiar algo en el cuerpo tú mismo. * Usa `for` cuando sabes cuántas veces, y `while` cuando estás esperando a que algo se haga verdadero. * `%` es el resto, y mantiene un número que crece dentro de un rango. * `math.random(a, b)` da un número entero de a a b; `math.floor` tira la fracción. * Un bucle que no termina nunca congela todo el juego sin ningún mensaje de error, y *Ctrl* y *C* en la terminal es como sales. [#_next_time_4] === La próxima vez Vuelve a mirar ese `onOverlayUpdate`. Dibuja estrellas, calcula posiciones, elige colores, y empieza a ser una función que hace cuatro trabajos en lugar de uno. En la lección cinco escribes tus propias funciones, les das argumentos igual que los reciben las funciones de Singe y les sacas respuestas -- y el campo de estrellas se convierte en una línea que dice `drawStars()`.