508 lines
21 KiB
Text
508 lines
21 KiB
Text
[#_lesson_8_when_it_goes_wrong]
|
|
== Lección 8: Cuando algo sale mal
|
|
|
|
image::learn/08-when-it-goes-wrong.png[La lección terminada, 480]
|
|
|
|
Has escrito un juego. De aquí en adelante pasarás más tiempo arreglando
|
|
programas que escribiéndolos, y eso no es señal de que te pase nada malo. Es el
|
|
trabajo. La diferencia entre alguien que disfruta programando y alguien que lo
|
|
deja casi nunca es el talento; es si aprendió a leer lo que le dice el
|
|
ordenador.
|
|
|
|
Así que en esta lección no hay nada nuevo que construir. Son los cinco mensajes
|
|
que más verás durante el resto de tu vida, lo que significa de verdad cada uno,
|
|
y cuatro formas de dar caza a la equivocación que hay detrás. Ten abierto
|
|
`dodge.singe` de la lección siete, porque vas a romperlo a propósito varias
|
|
veces.
|
|
|
|
[#_two_kinds_of_stop]
|
|
=== Dos tipos de parada
|
|
|
|
Singe se para por dos razones distintas, y lo dice de dos formas distintas.
|
|
|
|
Cuando el script ni siquiera llega a arrancar, te sale algo con esta forma:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
|
|
----
|
|
|
|
Lua lee el archivo entero antes de ejecutar ni una palabra, y esto significa
|
|
que no ha podido entender lo que ha leído. No se ha ejecutado nada en absoluto.
|
|
|
|
Cuando el script arrancó bien pero algo salió mal después, te sale en cambio
|
|
esta forma:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped')
|
|
stack traceback:
|
|
dodge/dodge.singe:98: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
El juego estaba funcionando. Singe llamó a tu `onOverlayUpdate`, como hace
|
|
sesenta veces por segundo, y esta vez la llamada no volvió.
|
|
|
|
Las dos formas contienen las mismas tres cosas, y son las tres cosas que
|
|
quieres: un archivo, una línea y una queja. Léelas en ese orden, siempre.
|
|
|
|
Los números de línea de esta lección son los del listado terminado de la
|
|
lección siete. Si los tuyos están desviados una o dos líneas, también lo está
|
|
tu archivo, y nada de lo que hace falta para leer un error depende de que el
|
|
número coincida.
|
|
|
|
[#_the_ones_that_stop_it_before_it_starts]
|
|
=== Los que lo paran antes de empezar
|
|
|
|
Estos son los fáciles, y también son los que peor sientan, porque la pantalla
|
|
se queda negra y no hay nada que mirar.
|
|
|
|
Ve a `updatePlaying` y borra la palabra `then` de la línea
|
|
`if spawnTimer <= 0 then`. Guarda:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
|
|
----
|
|
|
|
Lua llegó al final de la línea del `if`, quería la palabra `then` y en su lugar
|
|
encontró el principio de la línea siguiente -- que es `spawnBlock()`, y esa es
|
|
la parte `near 'spawnBlock'`. «Near» significa «aquí es donde me he dado
|
|
cuenta», y suele ser lo primero que va *después* de lo que te has dejado.
|
|
|
|
Ahora uno más feo. Vuelve a poner `then`, y en su lugar borra el `end` de la
|
|
última línea de `spawnBlock`. Guarda:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:148: 'end' expected (to close 'function' at line 58) near <eof>
|
|
----
|
|
|
|
La línea 148 es el final del archivo, justo después de su última línea. A la
|
|
línea 148 no le pasa nada.
|
|
Lua leyó hasta el final del todo -- `<eof>` es como llama al final de un
|
|
archivo -- esperando todavía un `end` que nunca llegó, y para entonces se había
|
|
tragado todas las funciones que van después de `spawnBlock` como si fueran
|
|
parte de ella.
|
|
|
|
La mitad útil de ese mensaje es `(to close 'function' at line 58)`, que es
|
|
donde empieza `spawnBlock`. Cuando un mensaje señala el final del archivo,
|
|
créete lo que dice entre paréntesis, no el número de línea. Por esto también
|
|
importa tanto la sangría: un `end` que falta es casi invisible en una lista de
|
|
líneas, y salta a la vista en cuanto todo lo que hay debajo está sangrado un
|
|
paso de más.
|
|
|
|
Dos más que vale la pena reconocer, porque te vas a encontrar los dos esta
|
|
misma semana:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:41: unfinished string near '"GAME OVER)'
|
|
----
|
|
|
|
Unas comillas que abriste y nunca cerraste, que ya viste en la lección uno.
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:135: 'then' expected near '='
|
|
----
|
|
|
|
Ese es `if state = "playing" then`. Un `=` da valor a una variable, dos `==`
|
|
hacen una pregunta, y Lua no te deja dar valor a una dentro de un `if`. Parece
|
|
una queja rara, y siempre significa lo mismo.
|
|
|
|
[#_attempt_to_index_a_nil_value]
|
|
=== Attempt to index a nil value
|
|
|
|
Este es el que más verás, con mucha diferencia.
|
|
|
|
Ve a `spawnBlock` y escribe mal `block` en la primera línea que lo usa, para
|
|
que quede `blocl.x = math.random(0, screenWidth - BLOCK_SIZE)`. Guarda y pulsa
|
|
la barra espaciadora:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:60: attempt to index a nil value (global 'blocl')
|
|
stack traceback:
|
|
dodge/dodge.singe:60: in function 'spawnBlock'
|
|
dodge/dodge.singe:92: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
*Indexar* es lo que hace el punto. `blocl.x` significa «entra en la cosa
|
|
llamada `blocl` y busca la `x` que tiene dentro». Eso solo se puede hacer con
|
|
una tabla.
|
|
|
|
*Nil* es como llama Lua a un nombre que no tiene nada detrás. No lo has visto
|
|
mucho porque has tenido cuidado de darle un valor a todo, pero cualquier nombre
|
|
que no hayas usado nunca ya es `nil`, y escribir mal un nombre se inventa uno
|
|
completamente nuevo.
|
|
|
|
Así que el mensaje entero dice: *has usado un punto en algo que no existe*.
|
|
|
|
La parte entre paréntesis es el regalo. `global 'blocl'` es el nombre exacto de
|
|
la cosa que estaba vacía. Búscalo en tu archivo. Si aparece exactamente una
|
|
vez, es una falta de ortografía, y la has encontrado en diez segundos.
|
|
|
|
La palabra que va antes del nombre te dice dónde lo buscó Lua:
|
|
|
|
* `global` -- un nombre que no tiene `local` delante en ningún sitio.
|
|
* `local` -- un nombre que declaraste con `local` en la función en la que
|
|
estás.
|
|
* `field` -- un nombre detrás de un punto, como la `y` de `block.y`.
|
|
* `upvalue` -- un `local` de más afuera que esta función puede ver, que es lo
|
|
que son todas las variables de `dodge.singe`, ya que se declaran al principio
|
|
del archivo y se usan dentro de funciones.
|
|
|
|
`field` es la otra causa habitual, y no es ninguna falta de ortografía. Si
|
|
escribiste `block.hit.when` y nunca diste valor a `block.hit`, te sale
|
|
`attempt to index a nil value (field 'hit')`. El punto que falla es el segundo.
|
|
Lua te dice cuál nombrándolo.
|
|
|
|
[#_attempt_to_call_a_nil_value]
|
|
=== Attempt to call a nil value
|
|
|
|
Arregla `blocl`. Ahora escribe mal una función del motor: en `drawGame`,
|
|
cambia `overlayPrint` por `overlayPirnt`. Guarda:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:36: attempt to call a nil value (global 'overlayPirnt')
|
|
stack traceback:
|
|
dodge/dodge.singe:36: in function 'drawGame'
|
|
dodge/dodge.singe:140: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
El mismo `nil`, otro delito. *Llamar* es lo que hacen los paréntesis, y eso
|
|
solo se puede hacer con una función. No hay ninguna función llamada
|
|
`overlayPirnt`, así que `overlayPirnt` es `nil`, así que los paréntesis no
|
|
tienen nada a lo que llamar.
|
|
|
|
Esto pasa por cuatro razones, de más a menos frecuente:
|
|
|
|
. Has escrito mal una de tus propias funciones.
|
|
. Has escrito mal una de las de Singe. Nada en el motor comprueba cómo
|
|
escribes los nombres cuando se carga el archivo, así que un nombre del motor
|
|
mal escrito es solo otro nombre que resulta estar vacío, y no te cuesta nada
|
|
hasta que se ejecuta la línea.
|
|
. La función no existe en Singe en absoluto -- era una suposición razonable, y
|
|
las suposiciones razonables fallan más o menos la mitad de las veces. La
|
|
lista de funciones de la referencia es la única forma de estar seguro.
|
|
. La hiciste `local` y la llamaste desde algún sitio por encima de donde se
|
|
declara. Esa es justo la razón de que las funciones de `dodge.singe` sean
|
|
globales.
|
|
|
|
Hay una versión mucho peor de esta equivocación, y te la encontraste en la
|
|
lección uno sin saberlo: escribe mal un *callback* y no te sale ningún mensaje.
|
|
`onOverlayUpdated` no es un error. Es una función perfectamente válida de la
|
|
que Singe no ha oído hablar nunca y a la que nunca llamará, así que la pantalla
|
|
se queda negra y nada se queja. Cuando toda una parte del juego no hace
|
|
absolutamente nada, sospecha del nombre.
|
|
|
|
[#_attempt_to_perform_arithmetic_on_a_nil_value]
|
|
=== Attempt to perform arithmetic on a nil value
|
|
|
|
Vuelve a poner `overlayPrint`. En `updatePlaying`, escribe mal `blockSpeed` en
|
|
la línea de caída, para que quede `block.y = block.y + blockSped`:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped')
|
|
stack traceback:
|
|
dodge/dodge.singe:98: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
La aritmética es `+`, `-`, `*` y `/`. No se puede sumar la nada a un número. De
|
|
nuevo los paréntesis nombran la cosa vacía, y de nuevo la causa más común es
|
|
una errata -- pero este tiene una segunda causa que vale la pena conocer,
|
|
porque es más difícil de ver.
|
|
|
|
Una variable a la que nunca diste un número es `nil`, no cero. Si añades un
|
|
récord al juego y escribes `local hiScore` al principio sin `= 0`, guarda `nil`
|
|
hasta que algo le dé valor, y el primer `hiScore + score` detiene el juego. Lua
|
|
no empieza los números en cero por ti. Los empiezas tú en cero.
|
|
|
|
Dos parientes cercanos, con la misma causa y la misma cura:
|
|
|
|
----
|
|
dodge/dodge.singe:37: attempt to concatenate a nil value (upvalue 'hiScore')
|
|
----
|
|
|
|
`..` uniendo una cadena a algo que no está ahí. Verás este cada vez que
|
|
imprimas una puntuación a la que se te olvidó dar valor -- aquí, `hiScore`
|
|
añadido a la línea de la puntuación en `drawGame`, que es la línea 37 ahora que
|
|
`local hiScore` está encima.
|
|
|
|
----
|
|
dodge/dodge.singe:105: attempt to compare nil with number
|
|
----
|
|
|
|
Ese es `if score > hiScore then`, puesto debajo de `state = "over"` para
|
|
guardar la mejor puntuación cuando acaba una partida, sin nada en `hiScore`.
|
|
Fíjate en que este mensaje no nombra nada entre paréntesis. Cuando Lua no puede decirte qué lado estaba
|
|
vacío, la sección que va después de la siguiente te enseña a averiguarlo.
|
|
|
|
[#_an_argument_of_the_wrong_kind]
|
|
=== Un argumento del tipo equivocado
|
|
|
|
Todos los mensajes hasta ahora venían de Lua. Este viene de Singe, y tiene un
|
|
aspecto completamente distinto, así que vale la pena conocerlo a propósito.
|
|
|
|
Supón que quieres ver qué hay en `blocks`, e intentas imprimirlo. Añade esta
|
|
línea a `drawGame`, debajo de la puntuación:
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(1, 3, blocks)
|
|
----
|
|
|
|
----
|
|
37:overlayPrint: Argument 3 must be a string.
|
|
----
|
|
|
|
Ni nombre de archivo, ni traza, ni ningún `attempt to` nada. Una queja del
|
|
motor empieza con el número de línea, luego el nombre de la función que se ha
|
|
negado y luego lo que quería. Aquí a `overlayPrint` le han dado una tabla donde
|
|
necesita texto, y se ha parado en lugar de dibujar algo sin sentido.
|
|
|
|
El mensaje hermano cuenta en lugar de comprobar:
|
|
|
|
----
|
|
34:overlayBox: Expected 4 argument(s), got 3.
|
|
----
|
|
|
|
Eso es un `overlayBox` al que le falta una esquina. Ese `argument(s)` tan raro
|
|
es del motor, no una errata tuya.
|
|
|
|
Cuando te salga uno de estos, la entrada de esa función en la referencia es la
|
|
respuesta, y te llevará un minuto. Cada entrada enumera los argumentos en
|
|
orden, con lo que tiene que ser cada uno. Singe los comprueba porque la
|
|
alternativa es un juego que funciona durante diez minutos y luego dibuja
|
|
basura.
|
|
|
|
[#_finding_out_what_is_really_in_there]
|
|
=== Averiguar qué hay de verdad ahí dentro
|
|
|
|
La mitad de depurar no consiste en leer errores. Es el caso en el que nada se
|
|
cae y nada funciona: bloques que nunca aparecen, un jugador que no se mueve,
|
|
una puntuación atascada en cero. Para eso necesitas ver el interior del juego
|
|
mientras se ejecuta, y hay dos formas.
|
|
|
|
La primera es `debugPrint`, que escribe una línea en la terminal desde la que
|
|
arrancaste Singe. Coge la línea que no consigues entender y pon una encima:
|
|
|
|
[source,lua]
|
|
----
|
|
debugPrint("blocks " .. #blocks .. ", timer " .. spawnTimer)
|
|
----
|
|
|
|
Ahora estás mirando lo que hay de verdad en lugar de lo que estás seguro de que
|
|
hay, y en ese hueco es donde viven los fallos. Imprime `playerX` del mismo modo
|
|
y también verás la verdad sobre él: se imprime como `160.0`, no `160`, porque dividir con
|
|
`/` en Lua siempre da un número con parte decimal, aunque esa parte sea cero.
|
|
|
|
Hay dos cosas que te van a morder. La primera es que `debugPrint` exige texto o
|
|
un número; dale cualquier otra cosa y detiene el juego igual que hizo
|
|
`overlayPrint`. `debugPrint(blocks)` no te enseñará una lista, y
|
|
`debugPrint("left " .. goingLeft)` al principio de `updatePlaying` tampoco te
|
|
enseñará un `true` ni un `false`:
|
|
|
|
----
|
|
dodge/dodge.singe:78: attempt to concatenate a boolean value (upvalue 'goingLeft')
|
|
----
|
|
|
|
`tostring` es el arreglo. Acepta cualquier cosa y te da texto para ella, así
|
|
que `tostring(goingLeft)` es `"true"` o `"false"` y la línea funciona.
|
|
|
|
La segunda es que `onOverlayUpdate` se ejecuta sesenta veces por segundo, así
|
|
que un `debugPrint` dentro de él imprime sesenta líneas por segundo y no puedes
|
|
leer ninguna. La referencia lo dice en su entrada: la llamada es barata, pero nada
|
|
la frena.
|
|
|
|
Antes que nada, pon un interruptor delante de cada línea de depuración que
|
|
añadas, para poder quitar el ruido sin tener que volver a buscarlas todas:
|
|
|
|
[source,lua]
|
|
----
|
|
local DEBUG = true
|
|
|
|
|
|
function debugLog(text)
|
|
if DEBUG then
|
|
debugPrint(text)
|
|
end
|
|
end
|
|
----
|
|
|
|
Cada `debugLog` del juego obedece a ese único `true`. Cámbialo a `false` y el
|
|
juego se queda callado; vuelve a cambiarlo y todo regresa.
|
|
|
|
Luego imprime cuando algo *pasa*, en lugar de en cada fotograma:
|
|
|
|
[source,lua]
|
|
----
|
|
debugLog("hit at y " .. math.floor(block.y) .. ", lives now " .. lives)
|
|
----
|
|
|
|
O imprime con un temporizador, que cuenta hacia atrás exactamente igual que
|
|
cuando nacen los bloques:
|
|
|
|
[source,lua]
|
|
----
|
|
reportTimer = reportTimer - 1
|
|
if reportTimer <= 0 then
|
|
debugReport()
|
|
reportTimer = REPORT_FRAMES
|
|
end
|
|
----
|
|
|
|
La carpeta `learn` tiene todo esto como `08-when-it-goes-wrong.singe`: es el
|
|
juego de la lección siete con un informe cada dos segundos más o menos, una
|
|
línea cuando te dan y una línea cuando termina la partida.
|
|
|
|
La segunda forma de ver el interior es ponerlo en pantalla, que es mejor para
|
|
cualquier cosa que cambie en cada fotograma, porque sesenta líneas por segundo
|
|
en una terminal no sirven de nada, pero un número que va cambiando en una
|
|
esquina es fácil de vigilar:
|
|
|
|
[source,lua]
|
|
----
|
|
if DEBUG then
|
|
overlayPrint(1, 3, "BLOCKS " .. #blocks .. " PLAYER " .. playerX)
|
|
end
|
|
----
|
|
|
|
[#_cutting_the_problem_in_half]
|
|
=== Partir el problema por la mitad
|
|
|
|
Cuando no ves cuál de veinte líneas está mal, deja de mirar y empieza a quitar.
|
|
|
|
Pon dos guiones al principio de una línea y Lua ignora el resto. Eso es un
|
|
*comentario*, y lo normal es usarlo para dejarte notas, pero el mejor uso es
|
|
este:
|
|
|
|
[source,lua]
|
|
----
|
|
-- overlayPrint(1, 1, "SCORE " .. score .. " LIVES " .. lives)
|
|
----
|
|
|
|
Comenta la mitad de `drawGame` y ejecútalo. Si el problema sigue ahí, está en
|
|
la mitad que has dejado. Si ha desaparecido, está en la mitad que has quitado.
|
|
Vuelve a poner esa mitad y pártela *a ella* por la mitad. Veinte líneas son
|
|
cinco rondas de esto, y cada ronda es guardar una vez y mirar una vez.
|
|
|
|
Funciona con algo más que líneas. Comenta el cuerpo de `updatePlaying` y los
|
|
bloques dejan de moverse, lo que te dice si lo que persigues está en el
|
|
movimiento o en el dibujo. Comenta la rama de `collideRects` y mira si el juego
|
|
deja de caerse. Mientras haces esto no intentas arreglar nada. Intentas
|
|
averiguar dónde no está.
|
|
|
|
[#_going_round_faster]
|
|
=== Dar vueltas más deprisa
|
|
|
|
Todo esto depende de poder probar algo en un par de segundos, y para eso está
|
|
`-R`. Lo usas desde la lección uno; esto es lo que hace de verdad, y vale la
|
|
pena saberlo ahora que vas a apoyarte en él.
|
|
|
|
Singe vigila cada archivo de script que ha cargado tu juego. Cuando guardas
|
|
uno, el juego vuelve a empezar desde el principio: todo lo que había creado el
|
|
script se tira y tu archivo se ejecuta de nuevo, en lo que se tarda en leer el
|
|
archivo y no en lo que se tarda en arrancar el motor. `F5` hace lo mismo cuando
|
|
quieras, sin guardar nada.
|
|
|
|
Una tecla que mantienes pulsada durante una recarga se ignora hasta que la
|
|
sueltas, así que el jugador no se irá corriendo él solo después de una recarga.
|
|
|
|
Los dos tipos de parada se comportan de forma distinta aquí, y es útil saber
|
|
cuál estás viendo sin leer el mensaje. Un script que no compila *se imprime y
|
|
Singe sigue vivo*: la ventana sigue abierta y vacía, el archivo sigue vigilado,
|
|
y guardar el arreglo devuelve el juego. Un fallo dentro de un callback -- todos
|
|
los mensajes `attempt to` y las quejas del motor sobre los argumentos -- tumba
|
|
a Singe, y lo vuelves a arrancar a mano.
|
|
|
|
[#_reading_a_traceback]
|
|
=== Leer una traza
|
|
|
|
Las líneas que hay debajo de `stack traceback:` son la lista de funciones que
|
|
estaban en marcha cuando todo se paró, empezando por la más interna. Aquí está
|
|
otra vez la de `blocl`:
|
|
|
|
----
|
|
dodge/dodge.singe:60: in function 'spawnBlock'
|
|
dodge/dodge.singe:92: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
Léela de abajo arriba y es una frase. Singe llamó a `onOverlayUpdate`. En la
|
|
línea 136, `onOverlayUpdate` llamó a `updatePlaying`. En la línea 92,
|
|
`updatePlaying` llamó a `spawnBlock`. En la línea 60, `spawnBlock` se rindió.
|
|
|
|
La línea de arriba es donde se rompió. Las líneas de debajo son cómo llegó ahí,
|
|
y son lo que necesitas cuando la línea de arriba parece inocente. Una función
|
|
que suma un número a `block.y` no está mal; una función que metió un bloque en
|
|
la lista sin una `y` sí lo está. Solo el rastro une las dos, y el rastro se te
|
|
imprime todas y cada una de las veces.
|
|
|
|
[#_the_habit]
|
|
=== La costumbre
|
|
|
|
Todo lo de arriba es técnica. Esto es la costumbre, y importa más:
|
|
|
|
*Cambia una cosa. Ejecútalo. Mira lo que ha pasado.*
|
|
|
|
Cuando algo no funciona, la tentación es cambiar cuatro cosas a la vez, porque
|
|
seguro que una de ellas es la buena. Hazlo y dejará de funcionar de una forma
|
|
nueva, y ahora no sabrás cuál de las cuatro ha sido, ni si dos de ellas se
|
|
están anulando entre sí. Has hecho el problema más grande y lo has escondido
|
|
mejor.
|
|
|
|
Un cambio. Ejecutar. Mirar. Parece más lento. Es lo más rápido que hay, y
|
|
todos los programadores con experiencia que conozcas lo hacen, exactamente por
|
|
la razón que estás a punto de descubrir.
|
|
|
|
[#_try_it_8]
|
|
=== Pruébalo
|
|
|
|
. *Conócelos a todos.* Comete a propósito en `dodge.singe` cada una de las
|
|
cinco equivocaciones de esta lección, de una en una, y lee el mensaje antes
|
|
de arreglarla. Nunca más te darán miedo.
|
|
. *Vigila dónde nacen.* Pon dentro de `spawnBlock` un `debugPrint` que imprima
|
|
la `x` del bloque nuevo. Juega treinta segundos y luego mira los números.
|
|
¿Están repartidos por toda la pantalla, o prefieren un extremo?
|
|
. *Apágalo.* Cambia `DEBUG` a `false` en el script de la lección, y comprueba
|
|
por ti mismo que no se imprime nada y que el juego no ha cambiado.
|
|
. *Un fallo sin error.* En `updatePlaying`, cambia `if spawnTimer <= 0 then`
|
|
por `if spawnTimer == 0 then`, que parece querer decir lo mismo. Ejecútalo y
|
|
empieza una partida. No hay ningún error, nada se cae y no cae ni un solo
|
|
bloque, así que ni siquiera puedes perder. Pon un `debugLog` dentro de
|
|
`spawnBlock`, añade `spawnTimer` a la primera línea que imprime
|
|
`debugReport` y vuelve a jugar. Averigua por lo que dicen, y por lo que uno
|
|
de ellos no dice nunca, por qué `==` era la pregunta equivocada.
|
|
. *Rompe el nombre de un callback.* Cambia el nombre de `onInputPressed` por
|
|
`onInputPress` y ejecútalo. No se imprime ningún error, nada se cae, el jugador no se
|
|
mueve y la barra espaciadora no empieza una partida. Luego explícate a ti
|
|
mismo por qué esa es la equivocación más peligrosa de esta lección.
|
|
|
|
[#_what_you_learned_8]
|
|
=== Lo que has aprendido
|
|
|
|
* Los errores tienen un archivo, una línea y una queja. Léelos en ese orden.
|
|
* `Error running script` significa que no se ha ejecutado nada; la forma con
|
|
traza significa que estaba funcionando y se paró.
|
|
* `nil` es un nombre sin nada detrás, y está detrás de la mayoría de los
|
|
mensajes de error que verás en tu vida.
|
|
* La palabra entre paréntesis nombra la cosa vacía, y dice si era un global, un
|
|
local, un campo o un upvalue.
|
|
* Un `end` que falta se señala al final del archivo, y los paréntesis te dicen
|
|
dónde está de verdad la equivocación.
|
|
* Las quejas del motor tienen otro aspecto: un número de línea, un nombre de
|
|
función y lo que quería en su lugar.
|
|
* `debugPrint` te enseña lo que guarda de verdad una variable. `tostring` hace
|
|
que cualquier cosa se pueda imprimir, y un interruptor como `DEBUG` quita el
|
|
ruido.
|
|
* Comenta una mitad con `--` para averiguar en qué mitad está el problema.
|
|
* Un callback mal escrito no produce ningún error, y eso lo hace peor que uno
|
|
que sí lo produce.
|
|
* Cambia una cosa, ejecútalo, mira.
|
|
|
|
[#_next_time_8]
|
|
=== La próxima vez
|
|
|
|
Ese es el final de la primera parte. Sabes escribir un juego, y ahora sabes
|
|
arreglarlo, lo que significa que todo lo que viene a partir de aquí es añadir y
|
|
no poner cimientos. La segunda parte empieza cambiando esas cajas de colores
|
|
por imágenes de verdad, y a partir de ahí el juego que has estado construyendo
|
|
empieza a parecer un juego al que otra persona querría jugar.
|