[#_lesson_2_numbers_that_change] == Lección 2: Números que cambian image::learn/02-numbers.png[La lección terminada, 480] En la lección uno pusiste una palabra en la pantalla y allí se quedó. Al final de esta lección, esa misma palabra irá deslizándose por la ventana, dará la vuelta cuando llegue a un borde, y lo hará gracias a cuatro números que el programa cambia sesenta veces por segundo. Por el camino conocerás la idea que hace tropezar a más principiantes que ninguna otra: dónde pones algo decide cuánto tiempo vive. [#_start_a_new_folder] === Empieza una carpeta nueva Crea una carpeta llamada `numbers` junto a tu carpeta `hello`, y dentro de ella un archivo llamado `numbers.singe`. Escribe en él el programa de la lección uno, o copia `hello.singe` y cámbiale el nombre -- cualquiera de las dos cosas vale, y escribirlo otra vez no es tiempo perdido. [source,lua] ---- function onOverlayUpdate() overlayClear() overlayPrint(2, 2, "Hello!") return OVERLAY_UPDATED end ---- Ejecútalo con `Singe -R numbers` y déjalo funcionando. El `-R` del final de la lección uno significa que no tendrás que volver a arrancarlo durante el resto de esta lección; guarda el archivo y la ventana se pone al día sola. [#_count_the_frames] === Cuenta los fotogramas La lección uno decía que Singe llama a `onOverlayUpdate` unas sesenta veces por segundo. Es fácil de decir y difícil de creer, así que cuéntalas. [source,lua] ---- function onOverlayUpdate() frames = 0 frames = frames + 1 overlayClear() overlayPrint(2, 2, "Hello!") overlayPrint(0, 0, "frame " .. frames) return OVERLAY_UPDATED end ---- Guárdalo. En la esquina superior izquierda aparece `frame 1`, y se queda en `frame 1` para siempre. Es decepcionante, y también es lo más útil de esta lección, así que no lo arregles todavía. Mira lo que has escrito y piensa en el orden de las líneas, que según la lección uno era lo más importante de un programa. Singe llama a `onOverlayUpdate`. La primera línea pone `frames` a cero. La segunda le suma uno, y queda en uno. Lo imprimes, devuelves la respuesta y la función se acaba. Luego Singe la llama otra vez, y lo primero que pasa es que `frames` vuelve a ponerse a cero. No estás contando fotogramas. Estás contando hasta uno, sesenta veces por segundo. [#_move_the_line_out] === Saca la línea Saca la línea `frames = 0` de la función y ponla encima, ella sola, antes de la palabra `function`. [source,lua] ---- frames = 0 function onOverlayUpdate() frames = frames + 1 overlayClear() overlayPrint(2, 2, "Hello!") overlayPrint(0, 0, "frame " .. frames) return OVERLAY_UPDATED end ---- Guárdalo. El número sube, deprisa, y no para. No ha cambiado nada más. La única diferencia es *dónde* está la línea, y esa es toda la idea. Las líneas que están dentro de una función ocurren cada vez que se llama a la función. Las líneas que están fuera de todas las funciones ocurren una vez, cuando Singe lee tu archivo, antes de que empiece el juego. `frames = 0` tenía que ocurrir una vez. Estaba ocurriendo sesenta veces por segundo, y borraba el trabajo de la línea de debajo. Todo lo que se mueva que escribas tendrá esta forma. Prepara el número fuera, cámbialo dentro. [#_what_just_happened_2] === Qué acaba de pasar Ahí dentro hay tres piezas de código nuevas. Aquí están por separado. [source,lua] ---- frames = 0 ---- Esto se inventa un nombre, `frames`, y pone el número cero detrás de él. Un nombre con un valor detrás es una *variable*, y se llama así porque el valor puede variar -- cambiar mientras el programa se ejecuta. El `=` no es el signo igual del colegio. No dice que `frames` sea igual a cero; dice *mete cero en `frames`*, y siempre funciona en esa dirección, de derecha a izquierda. Los programadores lo llaman *asignación*, y vale la pena decir la línea en voz alta de esa forma mientras la escribes: «frames recibe cero». El nombre lo eliges tú. Puede ser casi cualquier cosa que empiece por una letra y no tenga espacios, y debería decir qué es la cosa. `frames` es un buen nombre. `f` es uno malo, y dentro de una semana no te lo agradecerás. [source,lua] ---- frames = frames + 1 ---- Esta es la línea que hace que la gente se pare y se quede mirando, porque como afirmación aritmética no tiene sentido: nada es igual a sí mismo más uno. No es una afirmación aritmética. Recuerda que `=` mete el lado derecho en el nombre de la izquierda, y léela en ese orden, primero la derecha. Calcula `frames + 1` con lo que haya en `frames` ahora mismo. Luego vuelve a meter el resultado en `frames`. Si `frames` tenía seis, el lado derecho da siete, y el siete va a `frames`. Sumarle uno a una variable así es algo que escribirás miles de veces. El `+` es lo que esperas, y también los otros tres: `-` resta, `*` multiplica y `/` divide. No hay ninguna tecla `x` para multiplicar, y por eso el asterisco hace ese trabajo. [source,lua] ---- overlayPrint(0, 0, "frame " .. frames) ---- `overlayPrint` quiere texto, y `frames` es un número, no texto. Los dos puntos unen los dos y entregan el resultado como una sola cadena: los cinco caracteres `frame`, un espacio, y luego tantas cifras como necesite el número. Esos dos puntos son el operador de unión, y vale la pena conocer su nombre de verdad porque los mensajes de error lo usan: unir cadenas se llama *concatenación*. Fíjate en el espacio dentro de las comillas, antes de la de cierre. Sin él te sale `frame6`. Lua une exactamente lo que le das y no añade nada por su cuenta. Lua convierte un número en texto por ti cuando lo unes así a una cadena, y por eso no has tenido que hacer nada especial. No lo hace con todo. Si alguna vez intentas unir algo que no es ni texto ni número, Lua se detiene con un error, y la forma de evitarlo es `tostring`, que convierte cualquier cosa en texto: [source,lua] ---- overlayPrint(0, 0, "frame " .. tostring(frames)) ---- Esa línea hace exactamente lo mismo que la anterior. No necesitas `tostring` para los números. Lo necesitarás en la lección tres. [#_make_it_move] === Haz que se mueva Ya tienes todo lo que necesitas para mover la palabra. La palabra se dibuja donde tú digas, tienes una forma de guardar un número de un fotograma a otro, y tienes una forma de cambiarlo. Dale a la columna su propia variable fuera de la función, y súmale algo dentro. [source,lua] ---- frames = 0 wordX = 0 function onOverlayUpdate() frames = frames + 1 wordX = wordX + 1 overlayClear() overlayPrint(wordX, 2, "Hello!") overlayPrint(0, 0, "frame " .. frames) return OVERLAY_UPDATED end ---- Guárdalo. La palabra sale disparada por el lado derecho y no vuelve nunca. Se movía una columna en cada fotograma, sesenta veces por segundo, así que cruzó toda la ventana en más o menos un segundo. Hazla más lenta sumando menos de una columna entera: [source,lua] ---- wordX = wordX + 0.25 ---- Ahora tarda cuatro fotogramas en moverse una columna, y la palabra avanza despacito. `wordX` casi siempre es un número como `12.75`, que no es ninguna columna, y Singe lo resuelve tirando todo lo que va después del punto cuando dibuja: `12.75` se dibuja en la columna doce. Guardar la fracción en la variable es justo lo que te permite moverte más despacio que una columna por fotograma. El número es más fino que la pantalla. [#_where_is_the_edge] === ¿Dónde está el borde? La palabra sigue marchándose. Para darle la vuelta tienes que saber dónde está el borde derecho, y podrías averiguarlo contando: un juego sin vídeo recibe por defecto un mundo de 720 por 480, el overlay en el que dibujas es la mitad, 360 por 240, y las letras de la fuente de consola integrada miden seis píxeles de ancho, así que hay sesenta columnas. No lo hagas. Pregúntale al motor. [source,lua] ---- lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word) ---- `overlayGetWidth` y `overlayGetHeight` son otras dos funciones que proporciona Singe, como `overlayPrint`. No reciben nada y devuelven un número: el ancho y el alto de la superficie de dibujo. En la lección uno viste a `return` devolver un valor, y este es el otro extremo -- una función que llamas puede darte algo, y aquí estás haciendo cuentas con lo que te ha dado. Los números que te dan están en píxeles, y `overlayPrint` cuenta en celdas de carácter, así que tienes que dividir. `overlayGetFontWidth` te dice cuántos píxeles de ancho tiene una celda, y `overlayGetFontHeight` cuántos de alto, así que nunca tienes que saberte la respuesta. Las dos barras significan *divide y tira la fracción*, porque un tercio de celda no es un sitio donde se pueda imprimir. La barra simple de siempre te daría una fracción, y una fracción de celda tampoco es un sitio. `string.len` cuenta los caracteres de una cadena, y está ahí para que la palabra dé la vuelta cuando su *última* letra llegue al borde, y no la primera. Pruébalo sin él y verás seis caracteres deslizarse hacia la nada. Las mayúsculas de `CELL_WIDTH`, que guarda lo que te da `overlayGetFontWidth`, son una costumbre, no una regla. A Lua le da igual, pero un nombre en mayúsculas es como los programadores dicen «este es un número que pongo una vez arriba y no vuelvo a tocar», y es mucho mejor que tener un `6` suelto a mitad de un archivo que nadie sabe explicar un mes después. [#_turn_it_round] === Dale la vuelta Moverse en el otro sentido no cuesta nada: en lugar de sumar `0.25` en cada fotograma, suma `-0.25`. Así que guarda la cantidad en una variable propia y cámbiale el signo cuando la palabra llegue a un borde. [source,lua] ---- if wordX > lastColumn then wordX = lastColumn speedX = -speedX end ---- Eso es una decisión, y las decisiones son toda la lección tres, así que créetelo sin más durante una lección. Léelo en voz alta: *si* `wordX` ha pasado de `lastColumn`, *entonces* haz las dos líneas sangradas; si no, sáltatelas. El `end` lo cierra, igual que `end` cierra una función. La primera línea clava la palabra justo en el borde, para que una palabra rápida no se lo salte. La segunda es el cambio de signo. `-speedX` es menos lo que tenga `speedX`, así que si tenía `0.25` ahora tiene `-0.25`, y sumar `-0.25` en cada fotograma lleva la palabra de vuelta por donde vino. Cuando llega al borde izquierdo, el `if` correspondiente le vuelve a cambiar el signo, y vuelve a ser `0.25`. Dos más como esos para los bordes de arriba y de abajo, y la palabra rebota por toda la ventana. [#_the_whole_thing] === Todo junto [source,lua] ---- CELL_WIDTH = overlayGetFontWidth() CELL_HEIGHT = overlayGetFontHeight() TOP_ROW = 2 word = "Hello!" wordX = 0 wordY = TOP_ROW speedX = 0.25 speedY = 0.125 frames = 0 lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word) lastRow = overlayGetHeight() // CELL_HEIGHT - 1 function onOverlayUpdate() frames = frames + 1 wordX = wordX + speedX wordY = wordY + speedY if wordX < 0 then wordX = 0 speedX = -speedX end if wordX > lastColumn then wordX = lastColumn speedX = -speedX end if wordY < TOP_ROW then wordY = TOP_ROW speedY = -speedY end if wordY > lastRow then wordY = lastRow speedY = -speedY end overlayClear() overlayPrint(0, 0, "frame " .. frames) overlayPrint(wordX, wordY, word) return OVERLAY_UPDATED end ---- `TOP_ROW` mantiene la palabra fuera de las dos líneas de arriba, para que nunca garabatee encima del contador de fotogramas. `lastRow` lleva un `- 1` en lugar de un `string.len`, porque una palabra tiene muchos caracteres de ancho pero siempre una sola línea de alto. Fíjate en que toda la mitad de arriba del archivo se ejecuta una vez y toda la mitad de abajo sesenta veces por segundo, y en que puedes distinguir cuál es cuál mirando la sangría. Para eso eran los espacios. [#_try_it_2] === Pruébalo . *Cambia las velocidades.* Prueba `speedX = 1` y `speedY = 1`. Luego prueba `0.05` para las dos. Luego haz una mucho mayor que la otra. . *Cambia la palabra.* Pon una palabra mucho más larga entre las comillas -- lo bastante larga para llenar un tercio de la ventana -- y fíjate en que el rebote de la derecha sigue ocurriendo en el sitio correcto. Es `string.len` haciendo su trabajo. . *Quita la limpieza.* Borra la línea `overlayClear()`. La lección uno prometió que esto importaría algún día, y hoy es ese día: la palabra deja un rastro por toda la ventana y no borra nada, porque dibujar siempre ha dibujado encima de lo que había. Vuelve a ponerla. . *Que no vaya de lado.* Pon `speedX = 0` y ejecútalo. Averigua por qué ahora la palabra no sale nunca de la columna cero, y por qué el `if` del borde izquierdo no salta nunca. . *Rompe el contador de fotogramas a propósito.* Vuelve a meter la línea `frames = 0` dentro de la función, encima de `frames = frames + 1`. Mira cómo el contador se queda en uno mientras la palabra sigue moviéndose perfectamente. Luego averigua por qué a la palabra no le afecta. [#_break_it_on_purpose_2] === Rómpelo a propósito Cambia los dos puntos de la línea del contador de fotogramas por un signo más, para que quede así: [source,lua] ---- overlayPrint(0, 0, "frame " + frames) ---- Guárdalo. La ventana se cierra, Singe se detiene y la terminal dice: ---- Error executing function 'onOverlayUpdate': numbers/numbers.singe:42: attempt to add a 'string' with a 'number' ---- seguido de varias líneas que empiezan por `stack traceback:`, que de momento puedes ignorar. Léelo como te enseñó la lección uno: el archivo, la línea y luego la queja. La línea 42 es la que has cambiado. La queja dice que a Lua le pidieron *sumar* una cadena a un número, y sumar es cosa de números. `+` y `..` se parecen sobre el papel y hacen trabajos totalmente distintos: el signo más es aritmética, `..` es pegamento. Vale la pena conocer este por el sitio donde ha aparecido. El error de la lección uno detuvo Singe antes de que tu juego llegara a arrancar, porque Lua no podía ni leer el archivo. Este es una línea perfectamente legible que solo falla al ejecutarse, así que la ventana se abrió, el juego funcionó y murió en el primer fotograma. Los dos tipos te dicen el archivo y la línea. En cualquier caso, empieza por ahí. [#_what_you_learned_2] === Lo que has aprendido * Una variable es un nombre con un valor detrás, y el valor puede cambiar. * `=` significa «mete el lado derecho en el nombre de la izquierda», no «es igual a». * `frames = frames + 1` calcula primero el lado derecho y luego lo guarda. * `+`, `-`, `*` y `/` hacen la aritmética que esperas; `//` divide y tira la fracción. * `..` une texto, y de paso convierte un número en texto. * Una línea dentro de `onOverlayUpdate` se ejecuta en cada fotograma. Una línea fuera de todas las funciones se ejecuta una vez, antes de que empiece el juego. * Todo lo que tenga que recordar algo de un fotograma a otro tiene que vivir fuera. * `overlayGetWidth` y `overlayGetHeight` te dicen el tamaño de la superficie de dibujo, así que nunca tienes que adivinarlo. * `overlayClear` importa en cuanto algo se mueve. [#_next_time_2] === La próxima vez La palabra se mueve, pero se mueve sola y nada de lo que hagas le afecta. En la lección tres coges los cuatro bloques `if` que usaste sin más, aprendes lo que dicen de verdad y los apuntas a las teclas de flecha en lugar de a los bordes de la pantalla. La caja que manejas por la ventana al final de la lección es el jugador del juego que construyes en la lección siete.