 |  |  | El paso del tiempo: daemons, relojes, turnos |
El paso del tiempo: daemons, relojes, turnos
En la filosofía medieval neo-platónica, los demonios eran
intermediarios de Dios, planeando invisibles sobre el mundo e
interfiriendo con él. Podían ser espíritus guardianes de lugares o
personas. En Inform, un daemon es un espíritu intermediario,
asociado con un objeto del juego, que tiene la oportunidad de
interferir en cada turno (si está "activo"). El ejemplo clásico es
el del enano de Aventura, que aparece en la cueva de vez en cuando:
una rutina daemon asociada al enano es la encargada de hacerle
aparecer, lanzar cuchillos, etc. Cada objeto del juego puede tener su
propia rutina daemon que puede ser activada y desactivada
llamando a las rutinas de la librería:
ArrancarDaemon(objeto);
PararDaemon(objeto);
|
Una vez activada, la rutina daemon del objeto es llamada
(ejecutada) cada turno. A menudo las rutinas daemon son
activadas por Inicializar al principio del juego y permanecen
activas durante todo el juego. Algunos de estos daemons hacen
algo en cada turno (como por ejemplo una linterna a pilas que se va
quedando poco a poco sin carga), otros cuentan turnos o comprueban si
se cumple una condición y empiezan a hacer algo cuando se ha alcanzado
un cierto número de turnos o si la condición es cierta.
 |
De hecho un daemon no deja de funcionar aunque el jugador
se haya movido a otra parte desde donde no ve ya al objeto. Un
daemon no se detiene nunca a menos que lo paremos con la
función PararDaemon. Esto es muy útil ya que los daemons
pueden usarse para activar consecuencias de las acciones del jugador.
|
EJERCICIO 34 |
Algunos juegos tienen "monstruos" que caminan al azar de un lugar
a otro del juego. Utiliza un daemon para programar un
"ladrón" que camine libremente por las habitaciones del juego. (Solución) La solución es larga porque hay que comprobar qué
salidas puede tomar y elegir una al azar. No es evidente cómo hacer
esto para el principiante.
|
 |
EJERCICIO 35 |
A veces el daemon no tiene por qué estar asociado a ningún objeto
"físico" de los que componen el juego. Puedes inventar un objeto
"imaginario" que tenga asociado un daemon que se ocupe de
comprobar si ocurren ciertas cosas en el juego. Por ejemplo,
puedes hacer un objeto "ComprobadorDePeso" que tenga un daemon
asociado para que, tras cada turno, se compruebe cuánto peso está
llevando el jugador.
Escribe un daemon así de forma que si el jugador lleva demasiado
peso, empiece a debilitarse, hasta el punto de que, si no deja
algo, se le empezarán a caer las cosas. Además podemos asignar a
cada objeto del juego un peso diferente, de forma que el daemon
haga caerse el más pesado de los que el jugador lleva.
La solución es muy compleja, y seguramente no se te ocurrirá a la
primera, pero es muy interesante para ver lo flexible que es la
librería. No la pases por alto. (Solución)
|
|
A un objeto también se le puede asociar un "reloj", aunque casi
sería más correcto llamarlos "alarmas" (tradicionalmente se llamaban
"fusibles"). Si un objeto tiene un reloj asociado, podremos
arrancarlo mediante la función:
ArrancarReloj(objeto, tiempo);
|
Es como programar una alarma, que se activará cuando hayan
transcurrido el número de turnos especificados en
"tiempo". Cuando esto haya ocurrido, se llamará a la
rutina tiempo_agotado de ese objeto (sólo se llamará
entonces). Podemos desactivar el reloj, de forma que
tiempo_agotado no será llamada nunca, mediante la función:
Un objeto que tenga un reloj, debe proporcionar una propiedad llamada
tiempo_restante, que la librería usará para almacenar cuánto
tiempo le falta a la alarma para dispararse. Si el objeto no
proporciona esta propiedad, se producirá un error en el momento de
ejecutar el juego. El valor de esta propiedad puede ser cambiado desde
cualquier rutina de cualquier otro objeto. Si le damos un valor de 0,
esto indica la inmediata activación de la alarma (siempre que su
"reloj" hubiera sido activado con ArrancarReloj).
 |
La librería pone un límite al número de relojes y
daemons que pueden estar activados a la vez. El límite es
de 32 (no hay límite al número de ellos inactivos). Este límite, no
obstante, puede cambiarse fácilmente si tu juego así lo
necesita. Basta para ello definir la constante MAX_RELOJES
y darle un valor mayor, poniendo esta definición antes de incluir el
fichero "EParser".
|
Y hay una tercera forma de eventos temporizados. Si una habitación
proporciona la rutina cada_turno, entonces esta rutina será
llamada al final de cada turno mientras el jugador esté en esa
habitación; si un objeto proporciona esta rutina, será llamada
mientras el objeto esté cerca del jugador. Por ejemplo, una radio
puede emitir música mientras el jugador está cerca. Una espada puede
resplandecer cuando hay enemigos cerca, etc.
cada_turno es especialmente útil para crear criaturas que
permanecen fijas en una habitación y que sólo se activan cuando el
jugador está cerca. Un ogro con paciencia limitada puede tener una
rutina cada_turno que avise al jugador ("El ogro patea el
suelo enfurecido") y a la vez tener un reloj que dispare una
alarma cuando su paciencia se acabe.
 |
"Cerca" significa realmente "al alcance", una idea que será
explicada con detalle más adelante, pero que de momento puedes tomar
como "a la vista".
|
  |
Pero eso significa que si una radio está metida en un recipiente
opaco, dejaría de ejecutarse su rutina cada_turno (dejaría
de oírse, si el sonido se creaba en esta rutina). La única forma de
evitar este comportamiento es cambiar las reglas que definen qué
está y qué no está "al alcance". Para ello deberías programar una
rutina llamada AlAlcance. Si esta rutina existe, será
llamada por el parser en varias ocasiones para saber si un objeto
está o no "al alcance". En esta rutina deberás consultar la
variable razon_alcance para saber si el parser se halla en
la fase de parsing o en la fase cada_turno (en este caso
el valor de razon_alcance sería
RAZON_CADA_TURNO. Modificar el alcance puede dar lugar a
efectos muy interesantes, por ejemplo puedes poner la radio al
alcance en las habitaciones vecinas, para que siga oyéndose desde
ellas, o puedes hacer que el ladrón que camina aleatoriamente por
el laberinto pueda ser oído por el jugador cuando está cerca (como
en Zork).
|
EJERCICIO 36 |
Hagamos que en el juego Ruinas la oscuridad sea mucho más
amenazante. Mientras el jugador esté a oscuras debe escuchar
"pisadas sigilosas" y tras cuatro turnos consecutivos a oscuras,
finalmente será asesinado por los seres de la oscuridad. (Solución)
|
 |
EJERCICIO 37 |
Más difícil: programa las pisadas sigilosas como si fueran un
objeto, y dentro de él todo el código necesario para que ocurra lo
que dice el ejercicio anterior. No debe haber código relacionado
con estas pisadas sigilosas en ninguna otra parte del programa,
ni siquiera en la rutina Inicializar, sólo en el objeto
"pisadas" (por tanto no vale usar un daemon ya que éste
habría que activarlo en Inicializar). Solución
|
|
La librería tiene unas variables para almacenar la hora que es en el
juego (no tiene nada qué ver con la hora que es en la vida real, de
hecho Inform no tiene posibilidad de acceder al reloj del ordenador
para saber la hora real). La hora del juego se almacena en la variable
llamada la_hora, que contiene el número de minutos
transcurridos desde medianoche (los segundos no se cuentan). Por tanto
puede tomar valores entre 0 (es medianoche) y 1439 (falta un minuto
para medianoche). Podemos poner una hora concreta usando:
PonerLaHora(60*horas+minutos, velocidad);
|
El parámetro velocidad controla si la librería debe cambiar
esta hora cada turno de juego. Si velocidad es 0, la librería
no cambiará esta hora nunca (a menos que el juego llame de nuevo a la
función PonerLaHora). Si velocidad es positivo (por
ejemplo +3) significa que en cada turno de juego transcurren
velocidad minutos (3 minutos en cada turno, en el
ejemplo). Si es negativo (por ejemplo -4) significa que transcurre 1
minuto cada velocidad turnos (1 minuto cada 4 turnos, en el
ejemplo).
Lo habitual es llamar a PonerLaHora una sola vez en la
rutina Inicializar. Puedes hacer que la hora sea visible en
la barra de título (barra de estado) del juego si pones en algún lugar
del programa la directiva:
EJERCICIO 38 |
¿Cómo harías para que el juego detectase el paso por medianoche, y
así poder llevar la cuenta también del día en que estás? (Solución)
|
 |
EJERCICIO 39 |
Haz que la luz aparezca y desaparezca durante el juego, en función
de la hora (a la puesta del sol, la luz desaparece, al amanecer
aparece de nuevo). (Solución)
|
|
 |
Para el que necesite conocer los detalles, lo que ocurre realmente
tras cada turno es lo siguiente, y en este orden:
- El contador de turnos se incrementa (variable turnos)
- La variable la_hora es actualizada (si es necesario,
según la velocidad del reloj que haya elegido el programador).
- Se ejecutan todos los daemons y todos los
relojes que estén activados en todos los objetos del
juego (pero no se garantiza ningún orden concreto de activación
entre ellos).
- Se ejecuta la rutina cada_turno de la
localizacion donde se halla el jugador.
- Se ejecuta la rutina cada_turno de todos los objetos
al alcance del jugador (sin garantizar ningún orden concreto).
- Se llama a la rutina global PasaElTiempo (si es que
existe, lo normal es que no exista, pero el programador puede
poner una).
- Se comprueba de nuevo si hay luz para el jugador (puesto que
pueden haber cambiado muchas cosas desde el turno anterior).
La secuencia puede ser interrumpida si el jugador gana o muere como
consecuencia de la ejecución de uno cualquiera de estos puntos.
|
 |
EJERCICIO 40 |
Imagina que el jugador está flotando mágicamente en el aire.
Cualquier cosa que suelte mientras está así, debe desaparecer (se
supone que cae al lejano suelo). La forma más normal de programar
esto sería usar un daemon que tras cada turno mire lo que
hay en el suelo y lo haga desaparecer (es mejor hacerlo así en vez
de capturar la acción Dejar, porque podría haber varias
formas diferentes de hacer que un objeto termine en el suelo). De
todas formas ¿por qué sería mejor aún usar cada_turno en
vez de un daemon?
(Pista: tiene que ver con el orden en que se ejecutan las cosas) (Solución)
|
|
EJERCICIO 41 |
¿Cómo harías un juego en el que, entre turno y turno, pueden
transcurrir desde minutos hasta días? (Solución)
|
Zak McKraken - spinf@geocities.com
 |  |  | El paso del tiempo: daemons, relojes, turnos |