singe/docs/es/lessons/02-numbers.adoc

426 lines
16 KiB
Text

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