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