singe/docs/es/lessons/04-repeating.adoc

429 lines
16 KiB
Text

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