singe/docs/es/lessons/05-functions.adoc

715 lines
24 KiB
Text

[#_lesson_5_your_own_functions]
== Lección 5: Tus propias funciones
image::learn/05-functions.png[La lección terminada, 480]
La lección cuatro terminó con un aviso sobre tu propio programa:
`onOverlayUpdate` calcula dónde van ciento veinte estrellas, decide lo brillante
que es cada una, las dibuja y cuenta la deriva, y es una sola función haciendo
cuatro trabajos. Y está a punto de recibir un quinto, porque un campo de
estrellas sin nada delante no es un juego.
En esta lección lo desmontas. Escribirás tus propias funciones -- funciones de
las que Singe nunca ha oído hablar y que nunca llamará -- y les pasarás el
trabajo. Al final, todo el campo de estrellas es una línea que dice
`drawStars()`, vuelve a haber una nave en la parte de abajo de la pantalla, y el
programa se lee mejor que cuando hacía menos.
[#_start_a_new_folder_4]
=== Empieza una carpeta nueva
Una carpeta llamada `functions`, dentro un archivo llamado `functions.singe`, y
`Singe -R functions` ejecutándose en una terminal.
Copia en él el programa terminado de la lección cuatro. Aquí lo tienes, sin
cambios, y vale la pena ejecutarlo una vez antes de tocar nada:
[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
----
Tres capas de estrellas bajando por la ventana a tres velocidades. Nada de la
primera mitad de esta lección cambia lo que ves. Todo cambia cómo se lee el
programa.
[#_one_job_one_name]
=== Un trabajo, un nombre
Empieza por las dos líneas que ponen una estrella en la pantalla. Encima de
`onOverlayUpdate`, añade esto:
[source,lua]
----
function drawStar(x, y, shade)
colorForeground(shade, shade, shade)
overlayPlot(x, y)
end
----
Luego, en el bucle de dentro, borra esas dos líneas y llama a la función en su
lugar:
[source,lua]
----
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
drawStar(starX, starY, shade)
end
----
Guarda. La imagen es la misma.
Ya habías escrito funciones antes -- `onOverlayUpdate` es una, y también lo son
`onInputPressed` y `onInputReleased` de la lección tres -- pero son callbacks. Tú
las escribes y Singe las llama. `drawStar` es distinta. Singe no ha oído hablar
de ella nunca. Se ejecuta porque *tú* has escrito su nombre y los paréntesis,
igual que llamas a `overlayPlot`, y nada en el mundo la ejecutará si no lo haces
tú.
Ese es todo el truco. A partir de aquí, cuando te descubras pensando «y ahora
dibuja una estrella», puedes escribir «dibuja una estrella» y decirlo en serio.
[#_a_parameter_is_a_name_not_a_thing]
=== Un parámetro es un nombre, no una cosa
Vuelve a mirar la primera línea de `drawStar`.
[source,lua]
----
function drawStar(x, y, shade)
----
Los tres nombres que hay entre los paréntesis son *parámetros*. Un parámetro es
un nombre que la función usa para lo que le haya dado quien la llama. Cuando
escribes `drawStar(starX, starY, shade)`, Lua pone en `x` lo que haya en
`starX`, en `y` lo que haya en `starY`, en `shade` lo que haya en `shade`, y
luego ejecuta la función.
Los nombres no tienen nada que ver entre sí. El número de quien llama se llama
`starX` ahí fuera y `x` aquí dentro, y funcionaría exactamente igual si lo
llamaras `across` o `banana`. Dentro de la función solo existen los nombres que
hay entre los paréntesis, y guardan copias de lo que ha entrado.
Aquí es donde por fin tienen sentido los paréntesis vacíos de la lección uno.
[source,lua]
----
function onOverlayUpdate()
----
Nada entre los paréntesis significa que no hay parámetros: el motor no tiene
nada que decirle a esta función, así que no hay nada a lo que poner nombre.
Compárala con el callback que escribiste en la lección tres.
[source,lua]
----
function onInputPressed(what)
----
Un parámetro, llamado `what`. El motor tiene algo que decirle a esta -- qué
control se ha pulsado -- así que hay un nombre donde recogerlo. Llevas usando un
parámetro desde la lección tres.
Un parámetro es de verdad la copia propia de la función, y vale la pena
comprobarlo por ti mismo una vez. Escribe esto, llámalo desde
`onOverlayUpdate` y quítalo otra vez después:
[source,lua]
----
function half(number)
number = number // 2
return number
end
----
Llámala como `half(drift)`. La función divide entre dos su `number` y devuelve
el resultado, y `drift`, ahí fuera en `onOverlayUpdate`, sigue contando como si
no hubiera pasado nada. Una función no puede alargar la mano y cambiar las
variables de quien la llama a través de un parámetro. Recibe una copia, y lo que
haga con la copia es asunto suyo.
[#_handing_an_answer_back]
=== Devolver una respuesta
`half` ha hecho algo que no hacían las funciones que has escrito hasta ahora: ha
dado una respuesta. Es la última línea.
[source,lua]
----
return number
----
`return` devuelve un valor a quien llamó a la función, y la llamada se convierte
en ese valor. Escribe `smaller = half(drift)` y Lua ejecuta `half`, coge lo que
`half` ha devuelto y lo mete en `smaller`. Conociste `return` en la lección uno,
donde `onOverlayUpdate` devuelve `OVERLAY_UPDATED` para responder a la pregunta
de Singe sobre si ha cambiado algo. Es la misma palabra haciendo el mismo
trabajo, solo que ahora el que pregunta eres tú.
La línea del brillo está pidiendo a gritos ser una función con respuesta. Añade
esta:
[source,lua]
----
function shadeFor(layer)
return math.random(60, 90) + layer * LAYER_BRIGHT
end
----
Todo el cuerpo es un `return`, lo que es normal en una función pequeña. Ahora el
bucle de dentro puede darle la respuesta directamente a `drawStar` sin un nombre
por medio:
[source,lua]
----
drawStar(starX, starY, shadeFor(layer))
----
Una llamada dentro de otra llamada. Lua calcula primero la de dentro, recibe un
número de `shadeFor` y le pasa ese número a `drawStar` como tercer argumento. Ya
has escrito algo así sin darte cuenta: el
`math.floor(drift * layer * LAYER_SPEED)` de la lección cuatro es una llamada
cuya respuesta se usa en el acto, en medio de una cuenta más grande.
[#_two_answers_at_once]
=== Dos respuestas a la vez
Las dos líneas de la posición son más difíciles, porque son dos números que se
calculan juntos y tendrías que elegir uno para devolverlo. En la mayoría de los
lenguajes una función puede devolver un valor, y sacarle dos es un engorro. Lua
te deja devolver tantos como quieras.
[source,lua]
----
function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
----
Recoges las respuestas poniendo dos nombres a la izquierda, separados por una
coma:
[source,lua]
----
local x, y = starAt(star, layer)
----
El primer valor va al primer nombre, el segundo al segundo. Si pides menos
nombres de los que devuelve la función, los valores que sobran se tiran:
`local x = starAt(star, layer)` te da el número horizontal y pierde el vertical,
sin ninguna queja. Si pides más nombres que valores hay, los que sobran son
`nil`.
Olvídate de la palabra `local` de esos dos bloques un minuto más. Tiene una
sección propia más adelante, y es la importante.
[#_the_star_field_in_one_line]
=== El campo de estrellas en una línea
El bucle de dentro tiene ahora tres líneas cortas, y todas tratan de una capa de
estrellas. Eso es un trabajo con nombre.
[source,lua]
----
function drawLayer(layer)
for star = 1, STARS_PER_LAYER do
local x, y = starAt(star, layer)
drawStar(x, y, shadeFor(layer))
end
end
----
Y el bucle de fuera también es un trabajo con nombre:
[source,lua]
----
function drawStars()
for layer = 1, LAYERS do
drawLayer(layer)
end
end
----
Funciones que llaman a funciones que llaman a funciones, tres niveles, y cada
una son tres o cuatro líneas que puedes tener en la cabeza de una vez. Ahora
`onOverlayUpdate` puede decir lo que *es* un fotograma:
[source,lua]
----
function onOverlayUpdate()
drift = drift + 1
overlayClear()
drawStars()
return OVERLAY_UPDATED
end
----
Esa es la promesa que hizo la lección cuatro al final, cumplida.
[#_put_the_ship_back]
=== Vuelve a poner la nave
Ahora el quinto trabajo, el que habría puesto patas arriba el antiguo
`onOverlayUpdate`. Recupera la caja que manejabas en la lección tres, esta vez
como una nave cerca de la parte de abajo de la pantalla que solo se mueve a
izquierda y derecha.
Necesita algunos valores propios, arriba con los demás, y los dos callbacks de
entrada de la lección tres sin las ramas de arriba y abajo:
[source,lua]
----
PLAYER_WIDTH = 20
PLAYER_HEIGHT = 6
PLAYER_SPEED = 3
PLAYER_MARGIN = 6
playerX = width // 2
playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN
movingLeft = false
movingRight = false
----
[source,lua]
----
function onInputPressed(what)
if what == SWITCH_LEFT then
movingLeft = true
elseif what == SWITCH_RIGHT then
movingRight = true
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
movingLeft = false
elseif what == SWITCH_RIGHT then
movingRight = false
end
end
----
Dibujarla son dos líneas, así que es una función:
[source,lua]
----
function drawPlayer()
colorForeground(80, 255, 120)
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
end
----
Moverla es donde la lección da sus frutos. En la lección tres, mantener la caja
en la pantalla costaba cuatro líneas de `if` y `elseif` por cada dirección. Esa
idea -- mantener un número entre un mínimo y un máximo -- tiene nombre, y el
nombre es *clamp*:
[source,lua]
----
function clamp(value, low, high)
if value < low then
return low
end
if value > high then
return high
end
return value
end
----
Tres returns, y como mucho ocurre uno, porque `return` no solo devuelve una
respuesta: detiene la función en seco. No se ejecuta nada de lo que va después.
Si el valor está por debajo del mínimo, devuelve el mínimo y para. Si está por
encima del máximo, devuelve el máximo y para. Si no, devuelve lo que ha entrado.
[source,lua]
----
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
----
Una función que termina sin un `return` no devuelve nada, que es `nil`. Eso es
lo correcto para `drawPlayer` y `movePlayer`, a las que se llama para hacer algo
y no para responder algo. Es un fallo en una función como `clamp`: quita ese
último `return value` y todas las posiciones del medio del rango vuelven como
`nil`.
[#_where_names_live]
=== Dónde viven los nombres
Todos los nombres que has creado desde la lección dos han sido *globales*. Un
nombre global pertenece a todo el programa: `drift`, `playerX` y `LAYERS` los
puede leer y escribir cualquier función del archivo, en cualquier momento, y
siguen ahí mientras dure el juego.
Eso suena cómodo, y con seis nombres lo es. El problema empieza con sesenta.
Cada nombre global es un nombre que todo el programa ha acordado reservar, así
que cuando alguna otra función quiere un sitio donde guardar una posición
horizontal y también lo llama `starX`, los dos son la misma caja, y cada uno
escribe encima del número del otro. Cuando un nombre global guarda algo que está
mal, cualquier cosa del archivo podría haberlo puesto ahí, y averiguar cuál
significa leerlo entero.
La cura es un nombre *local*. Pon la palabra `local` delante de un nombre la
primera vez que lo uses, y el nombre solo existirá dentro de la `function`, el
bucle o el `if` donde se escribió.
[source,lua]
----
function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
----
Aquí, `x` e `y` no son asunto de nadie más. Otra función puede tener su propia
`x` y las dos no se encuentran nunca. Cuando la `x` de `starAt` está mal, las
tres líneas de arriba son las únicas tres que pueden haberlo hecho. Sin la
palabra `local`, esas dos líneas estarían creando sin hacer ruido dos nombres
globales más, que es exactamente lo que han sido `starX` y `starY` todo este
tiempo.
Ahora viene la parte que sorprende a todo el mundo. Escribe esta función y pon
una línea en `onOverlayUpdate` que te enseñe lo que devuelve:
[source,lua]
----
function countUp()
local counted = 0
counted = counted + 1
return counted
end
----
[source,lua]
----
overlayPrint(0, 0, "Count: " .. countUp())
----
`Count: 1`, y se queda en `Count: 1` mientras dura el juego, aunque a esa
función se la llama sesenta veces por segundo y suma uno cada vez. Un nombre
local se crea de nuevo cuando empieza la función y desaparece cuando la función
termina, así que `counted` nace valiendo `0`, pasa a `1` y muere, sesenta veces
por segundo, para siempre. Un nombre local no recuerda. Vuelve a quitar las dos
cosas cuando lo hayas visto.
No es un defecto, es justo la idea: a un nombre local no lo puede envenenar la
llamada anterior, porque de la llamada anterior no queda nada. Cuando sí
necesitas recordar algo entre llamadas -- `drift` y `playerX` tienen que
recordar -- el nombre tiene que vivir fuera de todas las funciones. Ponlo arriba
del archivo con `local` delante:
[source,lua]
----
local drift = 0
local playerX = width // 2
----
Un `local` escrito arriba del archivo, fuera de todas las funciones, vive lo
mismo que el script y lo pueden ver todas las funciones que hay debajo. Esa
última palabra importa: *debajo*. Un nombre local existe desde su propia línea
hacia abajo, así que una función escrita encima de la línea que lo crea no lo
puede ver. Lo mismo pasa con las propias funciones en cuanto las marcas como
`local`, y por eso el script terminado está en el orden en que está: primero los
valores, luego tus funciones, y los callbacks al final.
Recorre ahora todo el archivo y pon `local` delante de cada nombre que hayas
creado -- las constantes en mayúsculas, `width` y `height`, `drift`, los cuatro
valores del jugador y cada una de tus funciones. Tres nombres no lo llevan:
`onInputPressed`, `onInputReleased` y `onOverlayUpdate` tienen que seguir siendo
globales, porque Singe los encuentra por su nombre y un nombre local es
invisible desde fuera del archivo.
Sé sincero sobre lo que los nombres locales no arreglan. Si escribes mal un
nombre al que le estás asignando algo -- `movinLeft = true` en lugar de
`movingLeft = true` -- Lua no se queja. Crea un nombre global nuevecito con tu
errata como nombre, le da valor y deja en paz el de verdad. No se mueve nada, no
sale ningún error, y te pasas diez minutos mirándolo. Los nombres locales no te
salvarán de esa. Leer el nombre dos veces, sí.
[#_the_whole_thing_4]
=== Todo junto
Aquí está el programa terminado, con todas las piezas de la lección.
[source,lua]
----
local LAYERS = 3
local STARS_PER_LAYER = 40
local ACROSS_STEP = 37
local DOWN_STEP = 61
local LAYER_SHIFT = 13
local LAYER_SPEED = 0.5
local LAYER_BRIGHT = 55
local PLAYER_WIDTH = 20
local PLAYER_HEIGHT = 6
local PLAYER_SPEED = 3
local PLAYER_MARGIN = 6
local width = overlayGetWidth()
local height = overlayGetHeight()
local drift = 0
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 shadeFor(layer)
return math.random(60, 90) + layer * LAYER_BRIGHT
end
local function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
local function drawStar(x, y, shade)
colorForeground(shade, shade, shade)
overlayPlot(x, y)
end
local function drawLayer(layer)
for star = 1, STARS_PER_LAYER do
local x, y = starAt(star, layer)
drawStar(x, y, shadeFor(layer))
end
end
local function drawStars()
for layer = 1, LAYERS do
drawLayer(layer)
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
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
movingLeft = false
elseif what == SWITCH_RIGHT then
movingRight = false
end
end
function onOverlayUpdate()
drift = drift + 1
overlayClear()
movePlayer()
drawStars()
drawPlayer()
return OVERLAY_UPDATED
end
----
Hay dos cosas que vale la pena señalar antes de seguir.
`local function drawStar(x, y, shade)` marca la propia función como local. Tus
funciones son nombres como cualquier otro, y pertenecen solo a este archivo. Los
tres callbacks siguen siendo globales para que Singe pueda encontrarlos.
Y `onOverlayUpdate` son ahora seis líneas que dicen lo que es un fotograma:
contar la deriva, limpiar la pantalla, mover al jugador, dibujar el cielo,
dibujar al jugador, listo. Hace cinco trabajos nombrando cinco cosas que hacen
un trabajo cada una, que es la forma de todos los bucles de juego que escribirás.
[#_a_function_is_a_name_for_an_idea]
=== Una función es el nombre de una idea
A `clamp` se la llama una vez. Sacarla no ha ahorrado nada de escritura --
salieron cuatro líneas, entró una, y en otro sitio apareció una función de nueve
líneas. Aun así valía la pena.
Una función no es solo una forma de no repetirte. Es una forma de darle nombre a
una idea, para que el código que usa la idea pueda decir el nombre y seguir
adelante. «Mantén al jugador entre el borde izquierdo y el borde derecho» es un
pensamiento que tuviste una vez. `clamp(playerX, 0, width - PLAYER_WIDTH - 1)`
es ese pensamiento, por escrito. Cuatro sentencias `if` son el mismo pensamiento
desmontado en piezas que tienes que volver a montar en la cabeza cada vez que
las lees.
Cuando estés decidiendo si algo merece ser una función, no te preguntes cuántas
veces la llamarás. Pregúntate si tiene nombre.
[#_try_it_5]
=== Pruébalo
. *Cambia el nombre de un parámetro.* En `drawStar`, cambia `x` e `y` por
`across` y `down`, en todos los sitios de dentro de la función. No toques nada
de fuera. Ejecútalo. Averigua por qué no se ha roto nada.
. *Dibújalos al revés.* Intercambia `drawStars()` y `drawPlayer()` en
`onOverlayUpdate` y mira la nave. Luego vuelve a dejarlos como estaban. Cada
una de esas cinco líneas es un trabajo, y su orden en la pantalla es su orden
en la función.
. *Usa `clamp` con un tono.* Cambia `shadeFor` por
`return clamp(math.random(60, 90) + layer * 90, 0, 200)` y mira las tres
capas. Luego quita el `clamp` y vuelve a mirar: `colorForeground` te estaba
conteniendo los números sin decir nada todo este tiempo, y ahora puedes ver
dónde.
. *Llama a `drawStar` con dos argumentos.* Escribe `drawStar(100, 100)` en algún
sitio de `drawLayer` y ejecútalo. El error nombra exactamente lo que falta.
Léelo antes de volver a poner el tercer argumento.
. *Mueve una variable local.* Coge la línea `local drift = 0` y llévala al final
del archivo, debajo de todo. Ejecútalo, lee el error y piensa en la palabra
«debajo» de la sección anterior.
[#_break_it_on_purpose_5]
=== Rómpelo a propósito
El orden de un archivo con nombres locales importa, y el error que obtienes
cuando rompes ese orden no es el que esperas.
Coge la función `drawStar` entera y llévala al *final* del script, debajo de
`onOverlayUpdate`. Todo sigue ahí, escrito igual. Ejecútalo:
----
Error executing function 'onOverlayUpdate': functions/functions.singe:50: attempt to call a nil value (global 'drawStar')
stack traceback:
functions/functions.singe:50: in upvalue 'drawLayer'
functions/functions.singe:57: in upvalue 'drawStars'
functions/functions.singe:101: in function 'onOverlayUpdate'
----
Esto tiene un aspecto distinto del error de la lección uno porque es otro tipo
de error. El fallo de la lección uno se detectó antes de que se ejecutara el
script. Este se detectó mientras se ejecutaba, y por eso Singe nombra la función
en la que estaba a medias, y por eso hay un *traceback* debajo: la lista de
quién llamó a quién, lo más reciente primero. La línea 50 está dentro de
`drawLayer`, a la que se llamó desde la línea 57 dentro de `drawStars`, a la que
se llamó desde la línea 101 dentro de `onOverlayUpdate`. Tus números de línea
serán otros si tu archivo no coincide exactamente con el de arriba, y puede que
el motor añada debajo una línea o dos por su cuenta. La palabra `upvalue` es
como llama Lua a un nombre local que una función ha tomado prestado del archivo
que la rodea; de momento, sáltatela.
Vale la pena aprenderse de memoria la queja en sí. «Attempt to call a nil value»
significa que has puesto paréntesis después de un nombre que no guardaba nada.
La palabra `global` delante de `drawStar` es la parte que lo delata: marcaste `drawStar` como local,
así que cuando Lua leyó `drawLayer` y no encontró ningún nombre local así por
encima, lo único que le quedaba por probar era un nombre global -- y tampoco hay
ningún nombre global llamado `drawStar`. Vuelve a poner la función encima del
código que la llama y el error se va con ella.
[#_what_you_learned_5]
=== Lo que has aprendido
* Puedes escribir tus propias funciones, y nada las ejecuta salvo tus propias
llamadas.
* Los nombres que hay entre los paréntesis de una función son parámetros, y son
las copias propias de la función de lo que le pasó quien la llamó.
* `return` devuelve un valor y detiene la función ahí mismo, y una función que
termina sin él devuelve `nil`.
* Una llamada puede ser el argumento de otra llamada, y la de dentro ocurre
primero.
* Lua puede devolver más de un valor, y los recoges con varios nombres separados
por comas.
* `local` crea un nombre que solo existe donde se escribió.
* Un nombre local dentro de una función se crea de nuevo en cada llamada y no
recuerda nada de la anterior.
* Un nombre local solo se ve por debajo de la línea que lo crea, así que los
valores y tus funciones van encima de los callbacks que los usan.
* Los callbacks siguen siendo globales, porque Singe los encuentra por su
nombre.
* Vale la pena escribir una función cuando lo que hace tiene nombre, aunque la
llames una sola vez.
[#_next_time_5]
=== La próxima vez
Tus ciento veinte estrellas siguen siendo ciento veinte copias de tres ideas. En
el programa no hay nada que sea *una estrella*: hay un número y algo de
aritmética que convierte el número en una posición. No puedes decir «esta está
más cerca, así que cae más deprisa y brilla más», porque no hay ninguna «esta»
de la que decirlo, y no puedes dejar que una estrella se caiga por abajo y la
sustituya otra nueva, porque ahí no hay nada que quitar.
La lección seis te da lo que guarda una estrella, y luego una forma de guardar
ciento veinte y añadir la número ciento veintiuno mientras el juego se ejecuta.
Es la lección más útil de esta parte del libro.