548 lines
21 KiB
Text
548 lines
21 KiB
Text
[#_lesson_12_score_lives_and_game_over]
|
|
== Lección 12: Puntuación, vidas y fin de partida
|
|
|
|
image::learn/12-score.png[La lección terminada, 480]
|
|
|
|
Tu juego lleva la puntuación, quita vidas y dice GAME OVER. Es casi un juego.
|
|
Todavía le faltan tres cosas. No tiene puerta de entrada: está en plena acción
|
|
en el instante en que se carga. Se olvida de todo: sal, y la mejor puntuación
|
|
que nadie haya conseguido se va con él. Y lleva la cuenta de lo que está
|
|
haciendo con dos variables de verdadero o falso, `over` y `dying`, que es la
|
|
trampa de la que te avisó la lección siete.
|
|
|
|
Esta lección arregla las tres. Una variable para lo que está haciendo el juego,
|
|
una pantalla de título desde la que empezar, un récord que mañana seguirá ahí y
|
|
una segunda partida que empieza exactamente igual que la primera.
|
|
|
|
[#_a_game_is_always_in_exactly_one_state]
|
|
=== Un juego siempre está exactamente en un estado
|
|
|
|
Observa cualquier máquina recreativa durante dos minutos. Muestra un título y
|
|
espera a alguien. Alguien pulsa start y se pone a jugar. El jugador se queda
|
|
sin vidas y la máquina lo dice, y luego vuelve a esperar. La máquina siempre
|
|
está haciendo exactamente una de esas cosas.
|
|
|
|
Dodge, en la lección siete, guardaba eso en una variable que contenía una
|
|
palabra: `"waiting"`, `"playing"` o `"over"`. El juego de la lección nueve
|
|
volvió a un `over` de verdadero o falso, y la lección diez le añadió `dying` al
|
|
lado. Cuenta lo que pueden decir dos variables así. Cada una es `true` o
|
|
`false`, así que juntas dan cuatro combinaciones. Tres significan algo:
|
|
jugando, muriendo y fin de partida. La cuarta, las dos a la vez, es un
|
|
disparate, y tu código tiene que tener cuidado de no producirla nunca. Lo
|
|
tiene: mira `endGame`, que vuelve a poner `dying` a `false` antes de poner
|
|
`over` a `true`. Ahora añade una pantalla de título. Eso es un tercer
|
|
indicador, ocho combinaciones, y cuatro de ellas disparates. Esa es
|
|
exactamente la trampa de la lección siete, y es donde los juegos pequeños van a
|
|
morir.
|
|
|
|
Así que haz lo que hizo Dodge. Cuatro estados, una variable. Pon los nombres de
|
|
los estados debajo de tus otras constantes:
|
|
|
|
[source,lua]
|
|
----
|
|
local TITLE = "title"
|
|
local PLAYING = "playing"
|
|
local DYING = "dying"
|
|
local OVER = "over"
|
|
----
|
|
|
|
Luego quita `over` y `dying` de tus variables, y pon una sola línea en su
|
|
lugar:
|
|
|
|
[source,lua]
|
|
----
|
|
local state = TITLE
|
|
----
|
|
|
|
Dodge usaba las propias palabras. Los nombres en mayúsculas no son más listos,
|
|
y no atrapan una falta de ortografía: Lua trata un nombre mal escrito como
|
|
`nil` y una palabra mal escrita como una palabra que no coincide con nada, y
|
|
cualquiera de las dos te da un juego que deja de responder sin decir nada. Lo
|
|
que te compran los nombres es una lista. Cada estado en el que puede estar el
|
|
juego está escrito una vez, arriba del todo del archivo, donde puedes ver los
|
|
cuatro de un vistazo.
|
|
|
|
Allí donde el juego ponía un indicador, ahora pone el estado. En
|
|
`updateRocks`, donde se va la última vida:
|
|
|
|
[source,lua]
|
|
----
|
|
if lives <= 0 then
|
|
state = DYING
|
|
deathChannel = channel
|
|
----
|
|
|
|
En `endGame`, dos líneas se convierten en una, y la combinación disparatada
|
|
desaparece porque ya no queda nada que combinar:
|
|
|
|
[source,lua]
|
|
----
|
|
function endGame()
|
|
deathChannel = -1
|
|
state = OVER
|
|
musicStop(theme, 1500)
|
|
end
|
|
----
|
|
|
|
Y en `startGame`, las dos líneas que borraban los dos indicadores se convierten
|
|
en `state = PLAYING`.
|
|
|
|
[#_one_place_that_decides]
|
|
=== Un solo sitio que decide
|
|
|
|
En cada fotograma, `onOverlayUpdate` toma las dos decisiones que importan: qué
|
|
se mueve y qué palabras van encima. Qué se mueve es una sola comprobación:
|
|
|
|
[source,lua]
|
|
----
|
|
if state == PLAYING then
|
|
updateShip()
|
|
updateShots()
|
|
updateRocks()
|
|
end
|
|
----
|
|
|
|
Eso sustituye a `if not over and not dying then`, y dice lo que quiere decir.
|
|
En el título, mientras suena la última explosión y cuando la partida ya ha
|
|
terminado, nada se mueve. Lo consigues no llamando a las funciones que mueven
|
|
cosas. Parar un juego no es una característica especial; es dejar cosas fuera.
|
|
|
|
El mundo se sigue dibujando en cada fotograma, sea cual sea el estado,
|
|
exactamente como lo dibujaba Dodge. Solo cambian las palabras. Sustituye la
|
|
línea `drawHud()` por esto:
|
|
|
|
[source,lua]
|
|
----
|
|
if state == TITLE then
|
|
drawTitle()
|
|
else
|
|
drawHud()
|
|
end
|
|
----
|
|
|
|
`drawTitle` es nueva, y va después de `drawStars`:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawTitle()
|
|
overlayPrint(25, 6, "R O C K S")
|
|
overlayPrint(25, 9, "BEST " .. best)
|
|
overlayPrint(22, 12, "PRESS 1 TO PLAY")
|
|
end
|
|
----
|
|
|
|
`best` es la mejor puntuación hasta ahora, y llega en la sección siguiente; el
|
|
juego no funcionará hasta que llegue. Antes de la primera partida la lista de
|
|
rocas está vacía, así que lo que muestra el título son las estrellas pasando y
|
|
la nave esperando abajo, que es todo lo que necesita una pantalla de título.
|
|
|
|
Hay una línea que tiene que irse, o no verás nunca el título. Borra el
|
|
`startGame()` del final del todo del archivo. Ahora el juego empieza en su
|
|
título, y *Volver a empezar* explica por qué importa esa línea.
|
|
|
|
`drawHud` hace una pregunta propia. Cambia su `if over then` por:
|
|
|
|
[source,lua]
|
|
----
|
|
if state == OVER then
|
|
----
|
|
|
|
El callback de entrada necesita el mismo tipo de razonamiento. Aquí lo tienes
|
|
entero:
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_LEFT then
|
|
goLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
goRight = true
|
|
elseif what == SWITCH_BUTTON1 and state == PLAYING then
|
|
shots[#shots + 1] = { x = shipX + shipWidth / 2 - shotWidth / 2, y = shipY }
|
|
soundPlay(shootClip, 0, 35)
|
|
elseif what == SWITCH_START1 and (state == TITLE or state == OVER) then
|
|
soundPlay(clickClip)
|
|
startGame()
|
|
end
|
|
end
|
|
----
|
|
|
|
Léelo desde arriba. Las flechas se apuntan sea cual sea el estado, porque solo
|
|
dicen qué teclas están pulsadas, y nada mueve la nave a menos que el estado sea
|
|
`PLAYING`. Disparar solo significa algo mientras se juega. Y `1` empieza una
|
|
partida desde el título o desde la pantalla de fin de partida, y no mientras la
|
|
última explosión sigue sonando: la partida no ha terminado hasta que lo dice.
|
|
Los paréntesis funcionan igual que en aritmética. El `or` de dentro se resuelve
|
|
primero, y luego el `and` lo une a la tecla.
|
|
|
|
[#_score_and_lives]
|
|
=== Puntuación y vidas
|
|
|
|
Tienes las dos desde la lección nueve, y apenas cambian. Lo nuevo es la mejor
|
|
puntuación, que empieza su vida como una variable normal. Ponla con las demás,
|
|
debajo de `lives`:
|
|
|
|
[source,lua]
|
|
----
|
|
local best = 0
|
|
----
|
|
|
|
Muéstrala en la línea de arriba, entre las otras dos. En `drawHud`:
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(1, 1, "SCORE " .. score)
|
|
overlayPrint(25, 1, "BEST " .. best)
|
|
overlayPrint(50, 1, "LIVES " .. lives)
|
|
----
|
|
|
|
Y dale a `endGame` el trabajo de darse cuenta de un récord:
|
|
|
|
[source,lua]
|
|
----
|
|
if score > best then
|
|
best = score
|
|
end
|
|
----
|
|
|
|
Ejecútalo. El título dice `BEST 0`. Pulsa `1`, dispara a algo, pierde, y la
|
|
pantalla de fin de partida muestra tu puntuación como la mejor. Vuelve a pulsar
|
|
`1` y la mejor se queda mientras la puntuación vuelve a empezar. Ahora sal,
|
|
vuelve a ejecutarlo y mira el título: `BEST 0`. El récord ha vivido exactamente
|
|
lo mismo que el programa. Esa es la segunda mitad de esta lección.
|
|
|
|
Ya que estás mirando las vidas, fíjate en que la lección nueve escribió la
|
|
comprobación como `lives <= 0`, no `lives == 0`, y déjala así. Hoy nada puede
|
|
quitar dos vidas a la vez, así que las dos solo pueden coincidir. El día que
|
|
añadas una roca que cueste dos, `== 0` se salta el cero y el jugador sigue
|
|
jugando con menos una vida, mientras que `<= 0` sigue funcionando. Pide lo que
|
|
quieres decir.
|
|
|
|
[#_starting_again]
|
|
=== Volver a empezar
|
|
|
|
Ya has borrado el `startGame()` del final del archivo. En la lección diez la
|
|
primera partida la empezaba esa línea y todas las siguientes se empezaban
|
|
pulsando `1`, que son dos formas de entrar. Ahora hay una. Cada partida, la
|
|
primera incluida, empieza cuando alguien pulsa `1` y se ejecuta `startGame`,
|
|
así que la primera partida y la quinta empiezan exactamente de la misma forma.
|
|
|
|
`startGame` tiene que volver a poner en su sitio todo lo que cambia una
|
|
partida:
|
|
|
|
[source,lua]
|
|
----
|
|
function startGame()
|
|
shipX = (screenWidth - shipWidth) / 2
|
|
score = 0
|
|
lives = START_LIVES
|
|
state = PLAYING
|
|
deathChannel = -1
|
|
shots = {}
|
|
for i = 1, ROCK_COUNT do
|
|
rocks[i] = {}
|
|
newRock(rocks[i])
|
|
end
|
|
musicSetVolume(50)
|
|
musicPlay(theme, -1)
|
|
end
|
|
----
|
|
|
|
Igual de importante es saber qué deja en paz, y por qué. `best` sobrevive a
|
|
todas las partidas; ese es todo su trabajo. Las estrellas son decorado. Y
|
|
`goLeft` y `goRight` no son del juego en absoluto. Son del teclado, y solo el
|
|
teclado debería cambiarlas.
|
|
|
|
Esa última es un fallo que acabas de arreglar sin darte cuenta. El callback de
|
|
entrada de la lección diez empezaba con `if over then`, y mientras la partida
|
|
estaba terminada tiraba todas las teclas salvo `1`. Pulsa la flecha derecha
|
|
mientras se ve GAME OVER, mantenla pulsada y pulsa `1`. La nave nueva se queda
|
|
quieta: la pulsación de la flecha derecha se tiró, todavía no la has soltado, y
|
|
por eso `goRight` es `false` mientras la tecla está claramente pulsada. La nave
|
|
no se mueve hasta que sueltas y vuelves a pulsar. El callback nuevo apunta las
|
|
flechas en todos los estados, así que la tecla mantenida sigue mantenida cuando
|
|
empieza la partida.
|
|
|
|
Los fallos al volver a empezar son los más comunes de cualquier juego, y al
|
|
jugar todos se notan igual: la segunda partida está sutilmente mal y la primera
|
|
estaba bien. Cuando pase eso, lee `startGame` y hazte dos preguntas. ¿Qué
|
|
cambió la partida anterior que esta no vuelve a poner en su sitio? ¿Y qué está
|
|
poniendo esta en su sitio que nunca le correspondió tocar al juego?
|
|
|
|
[#_a_number_that_outlives_the_program]
|
|
=== Un número que sobrevive al programa
|
|
|
|
Todo lo que ha guardado tu juego hasta ahora -- `score`, `lives`, la lista de
|
|
rocas -- vive en memoria, y la memoria desaparece en el instante en que el
|
|
programa se para.
|
|
|
|
Singe le da a cada juego una partida guardada propia: un conjunto de nombres
|
|
con valores debajo, que se escribe en el disco por ti. Es otra parte del kit de
|
|
juego de la lección once, así que es el mismo tipo de cosa que `collideRects`:
|
|
llamadas sencillas, nada que cargar ni liberar.
|
|
|
|
[source,lua]
|
|
----
|
|
saveSet("highScore", 4200)
|
|
----
|
|
|
|
`saveSet` recibe un nombre -- la *clave* -- y un valor. La clave la eliges tú y
|
|
no cambia nunca; el valor es lo que quieras recordar. Caben números, cadenas,
|
|
`true` y `false`, y tablas enteras de esas cosas. Lo que solo tiene sentido
|
|
mientras el programa se ejecuta, no. Un handle de sprite no es más que un
|
|
número, así que se guarda, y mañana es un número que no significa nada. Una
|
|
función no se puede escribir en ningún sitio, y si intentas guardar una el
|
|
juego se para con un error en lugar de dejar que lo descubras la semana que
|
|
viene.
|
|
|
|
Leerlo de vuelta es la otra mitad. Cambia la línea que añadiste para `best`:
|
|
|
|
[source,lua]
|
|
----
|
|
local best = saveGet("highScore", 0)
|
|
----
|
|
|
|
Se ejecuta una vez, cuando arranca el juego. El segundo argumento es el *valor
|
|
por defecto*: qué responder cuando nunca se ha guardado nada bajo esa clave.
|
|
Enseguida hablamos más de eso, porque es todo el error de esta lección.
|
|
|
|
Y haz que `endGame` apunte el récord cuando se bata:
|
|
|
|
[source,lua]
|
|
----
|
|
function endGame()
|
|
deathChannel = -1
|
|
state = OVER
|
|
musicStop(theme, 1500)
|
|
if score > best then
|
|
best = score
|
|
saveSet("highScore", best)
|
|
saveFlush()
|
|
end
|
|
end
|
|
----
|
|
|
|
No tienes que pedirle a Singe que escriba el archivo. Lo escribe una vez al
|
|
final de cualquier fotograma en el que haya cambiado algo, da igual cuántas
|
|
claves hayas fijado, y otra vez cuando el juego se cierra. `saveFlush`, en
|
|
cambio, lo escribe en el acto: un récord nuevo merece una escritura del archivo
|
|
para él solo, porque a una recreativa la apagan desde el enchufe y un fotograma
|
|
es mucho tiempo.
|
|
|
|
Ejecútalo, marca una puntuación, sal y vuelve a ejecutarlo. El título se
|
|
acuerda.
|
|
|
|
La sección Partida guardada de la referencia enumera el resto de la familia.
|
|
`saveDelete` olvida una clave, `saveClear` lo olvida todo -- eso es el «borrar
|
|
récords» de un menú de servicio -- y `saveGetAll` te da una copia de todo, que
|
|
es útil cuando quieres imprimir la partida guardada entera mientras persigues
|
|
algo.
|
|
|
|
[#_where_the_file_actually_goes]
|
|
=== Adónde va de verdad el archivo
|
|
|
|
No va junto a tu script, y no es casualidad.
|
|
|
|
Un juego terminado puede vivir en un disco, en una carpeta compartida de la red
|
|
o dentro de un único archivo empaquetado (lección treinta), y en ninguno de
|
|
esos sitios se puede escribir. Un juego que guarda junto a su propio script
|
|
funciona perfectamente en el ordenador en el que lo escribiste y falla en la
|
|
mitad de los ordenadores en los que se instala. Singe 2 lo hacía así, y estaba
|
|
mal.
|
|
|
|
Así que todo lo que Singe escribe para un juego -- la partida guardada, las
|
|
capturas de pantalla, su registro -- va a un solo sitio, y `singeGetDataPath`
|
|
te dice dónde:
|
|
|
|
[source,lua]
|
|
----
|
|
debugPrint(singeGetDataPath())
|
|
----
|
|
|
|
Ponlo un momento justo debajo de la línea `dofile` y ejecútalo. Dice
|
|
`data/rocks/`: la carpeta `data` que apareció en tu carpeta de trabajo la
|
|
primera vez que ejecutaste algo, en la lección uno, y dentro una carpeta que se
|
|
llama como la carpeta de tu juego. Ahí dentro encontrarás `save.json`. Ábrelo en tu editor de
|
|
texto: es texto plano, y tu récord está ahí bajo el nombre que le diste.
|
|
|
|
Esa es también la respuesta siempre que necesites un archivo propio. Construye
|
|
el nombre a partir de `singeGetDataPath()` y acabará en un sitio donde se puede
|
|
escribir; escribe en un nombre inventado por ti y tarde o temprano no será así.
|
|
|
|
[#_the_first_time_there_is_nothing]
|
|
=== La primera vez no hay nada
|
|
|
|
La primerísima vez que alguien ejecuta tu juego, nunca se ha guardado ningún
|
|
récord. `saveGet` no tiene nada que devolver, así que devuelve el valor por
|
|
defecto que le diste:
|
|
|
|
[source,lua]
|
|
----
|
|
local best = saveGet("highScore", 0)
|
|
----
|
|
|
|
No hay nada guardado, así que `best` vale `0`, la pantalla de título dice
|
|
`BEST 0`, y la primera partida que juegue alguien marca un récord. Eso es
|
|
exactamente lo que quieres, y cuesta un argumento más.
|
|
|
|
Quita el `0` y `saveGet` no tiene ningún valor por defecto al que recurrir, así
|
|
que responde `nil` -- el valor que usa Lua para «aquí no hay nada», que
|
|
conociste en la lección tres. `nil` no es cero. No puedes sumarle nada, no
|
|
puedes compararlo con un número y no puedes pegarlo a una cadena. Falla en el
|
|
primer fotograma, siempre, en cada ordenador donde no se haya jugado al juego
|
|
-- lo que incluye todos los ordenadores en los que tus jugadores están a punto
|
|
de instalarlo, y no el que usas para probar.
|
|
|
|
Dale a cada `saveGet` un valor por defecto. Es la costumbre más barata de este
|
|
libro.
|
|
|
|
[#_what_just_happened_8]
|
|
=== Qué acaba de pasar
|
|
|
|
[source,lua]
|
|
----
|
|
local TITLE = "title"
|
|
----
|
|
|
|
Una constante: un nombre al que das valor una vez y no cambias nunca, en
|
|
mayúsculas para que veas de un vistazo que lo es, como en la lección siete. Lua
|
|
no lo impone -- nada te impide asignar algo a `TITLE` -- así que las mayúsculas
|
|
son un mensaje para la siguiente persona que lo lea, que eres tú dentro de dos
|
|
semanas.
|
|
|
|
[source,lua]
|
|
----
|
|
if state == PLAYING then
|
|
----
|
|
|
|
La pregunta que decide si el mundo se mueve. Cuando más adelante añadas un
|
|
quinto estado -- una pausa, o una ronda de bonificación -- lo primero que hay
|
|
que preguntarse es si el mundo se mueve en él, y aquí es donde va la respuesta.
|
|
|
|
[source,lua]
|
|
----
|
|
elseif what == SWITCH_START1 and (state == TITLE or state == OVER) then
|
|
----
|
|
|
|
Sin los paréntesis, Lua uniría primero el `and` y lo leería como «la tecla es
|
|
`1` y el estado es el título, o el estado es el de fin de partida». Entonces
|
|
cualquier tecla que no hubieran atrapado las ramas de arriba empezaría una
|
|
partida desde la pantalla de fin de partida, disparo incluido, y un jugador que
|
|
siguiera aporreando el botón de disparo cuando le da la última roca se vería
|
|
metido en una partida nueva en el momento en que apareciera GAME OVER. Los
|
|
paréntesis hacen que el `or` vaya primero. Cuando mezcles `and` y `or`, pon los
|
|
paréntesis aunque creas que sabes el orden. Así la siguiente persona que lo lea
|
|
no tendrá que saberlo.
|
|
|
|
[source,lua]
|
|
----
|
|
saveSet("highScore", best)
|
|
saveFlush()
|
|
----
|
|
|
|
`saveSet` mete el número en la partida guardada; `saveFlush` pone la partida
|
|
guardada en el disco ahora en lugar de al final del fotograma. Los dos están
|
|
dentro del `if`, así que el archivo solo se escribe cuando de verdad se bate el
|
|
récord, que es unas pocas veces por sesión y no sesenta veces por segundo.
|
|
|
|
[source,lua]
|
|
----
|
|
local best = saveGet("highScore", 0)
|
|
----
|
|
|
|
Se lee una vez, al arrancar, en una variable normal. El resto del juego lee
|
|
`best` en lugar de volver a llamar a `saveGet`, porque la partida guardada es
|
|
un archivo y una variable es una variable. Escribe en las dos cuando se bata el
|
|
récord.
|
|
|
|
[#_try_it_12]
|
|
=== Pruébalo
|
|
|
|
. *Vuelve al título.* Una máquina recreativa no se queda en GAME OVER para
|
|
siempre. Busca `timerAfter` en la sección Temporizador de la referencia, otra
|
|
parte del kit de juego, y úsalo en `endGame` para devolver el estado a
|
|
`TITLE` cinco segundos después de que termine la partida. Luego pulsa `1`
|
|
enseguida al terminar una partida y asegúrate de que el temporizador no
|
|
devuelve tu partida nueva al título a los tres segundos.
|
|
. *Muestra las vidas como naves.* Dibuja `shipSprite` una vez por vida en la
|
|
esquina superior derecha con `spriteDraw` en lugar de imprimir el número. Un
|
|
bucle `for` de la lección cuatro, y `shipWidth` te dice a qué distancia
|
|
ponerlas.
|
|
. *Guarda más que un número.* Añade `saveSet("gamesPlayed", saveGet("gamesPlayed",
|
|
0) + 1)` a `startGame`, e imprímelo en la pantalla de título. Sal, vuelve a
|
|
ejecutarlo y mira cómo lo recuerda.
|
|
. *Encuentra el archivo y rómpelo.* Abre `data/rocks/save.json` en tu editor,
|
|
cambia el récord por algo absurdo y ejecuta el juego. Luego borra el archivo
|
|
entero y vuelve a ejecutarlo. Las dos cosas les van a pasar a tus jugadores.
|
|
. *Bórralo desde dentro del juego.* Haz que una tecla que nunca pulsarías sin
|
|
querer llame a `saveClear()`, y mira después la pantalla de título. Cuidado:
|
|
`best` sigue en memoria, y `saveClear` no lo toca. Averigua qué más tiene que
|
|
pasar.
|
|
|
|
[#_break_it_on_purpose_11]
|
|
=== Rómpelo a propósito
|
|
|
|
Quita el valor por defecto, para que la línea quede así:
|
|
|
|
[source,lua]
|
|
----
|
|
local best = saveGet("highScore")
|
|
----
|
|
|
|
Si ya has marcado un récord, no pasa nada en absoluto, y esa es la trampa: la
|
|
partida guardada tiene la clave, así que `saveGet` la devuelve. Borra primero
|
|
`data/rocks/save.json` y luego ejecútalo. La pantalla de título no aparece
|
|
nunca, y te sale esto:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': rocks/rocks.singe:101: attempt to concatenate a nil value (upvalue 'best')
|
|
stack traceback:
|
|
rocks/rocks.singe:101: in function 'drawTitle'
|
|
rocks/rocks.singe:165: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
Léelo como te enseñó la lección ocho. El archivo y la línea te llevan a
|
|
`overlayPrint(25, 9, "BEST " .. best)` en `drawTitle`, y la queja dice que algo
|
|
que era `nil` se ha pegado a una cadena. `..` une dos cadenas, y `nil` no es
|
|
una. Si tus líneas están colocadas de otra forma, tus números también serán
|
|
otros.
|
|
|
|
La línea que nombra no es donde está el error. La línea 101 es donde el juego
|
|
por fin ha tropezado con él; el error se cometió cerca del principio del
|
|
archivo, donde se pidió `best` y no había nada. Esa distancia entre dónde se
|
|
crea un valor malo y dónde se nota es lo más difícil de `nil`, y la razón de
|
|
que un valor por defecto cueste un argumento y te ahorre una tarde.
|
|
|
|
Vuelve a poner el `0`.
|
|
|
|
[#_what_you_learned_12]
|
|
=== Lo que has aprendido
|
|
|
|
* Un juego está exactamente en un estado cada vez, y los indicadores de
|
|
verdadero o falso que intentan decir cuál crían combinaciones que no
|
|
significan nada.
|
|
* Guarda el estado en una variable, con sus valores posibles nombrados una vez
|
|
arriba del todo del archivo.
|
|
* `onOverlayUpdate` decide qué se mueve y qué se dice, preguntando al estado.
|
|
* Parar un juego es cuestión de no llamar a lo que lo mueve.
|
|
* Empieza cada partida, la primera incluida, a través de una sola función, y
|
|
haz que esa función vuelva a poner en su sitio todo lo que cambia una
|
|
partida -- y nada más.
|
|
* Las teclas son del teclado. Apúntalas en todos los estados.
|
|
* `saveSet` guarda un valor bajo una clave, y `saveGet` lo vuelve a leer en una
|
|
ejecución posterior.
|
|
* `saveFlush` escribe el archivo ya, para las cosas que te daría rabia perder.
|
|
* La partida guardada va en la carpeta de datos del propio juego, que nombra
|
|
`singeGetDataPath`. Nunca escribas junto a tu script.
|
|
* `saveGet` sin valor por defecto responde `nil` cuando no se ha guardado nada,
|
|
y `nil` rompe el primer fotograma en un ordenador que nunca ha ejecutado tu
|
|
juego.
|
|
* Cuando mezcles `and` y `or`, usa paréntesis.
|
|
|
|
[#_next_time_12]
|
|
=== La próxima vez
|
|
|
|
Tu juego ya es un juego, y es una carpeta que solo tú sabes arrancar. La
|
|
lección trece le da un `games.dat`, para que el propio menú del motor lo ponga
|
|
en su lista con un título y una imagen, y un jugador que nunca ha oído hablar
|
|
de ti pueda arrancarlo sin escribir nada.
|