![]() | ![]() | ![]() | Conceptos de programación orientada a objetos |
Este capítulo es un poco "avanzado". Si no lo entiendes bien puedes pasar directamente al siguiente y ya lo volverás a leer cuando tengas más experiencia con Inform.
En realidad los mensajes ya han aparecido en la sección anterior, pero no te lo habíamos dicho. El programador puede enviar a cualquier objeto del juego lo que se llama un "mensaje", o más bien podríamos llamarlo un "aviso". Por ejemplo, a un objeto llamado cuervo, podríamos mandarle un mensaje llamado desaparecer. Junto con el mensaje puede ir información adicional (llamemosla por ejemplo info1 e info2. La sintaxis de Inform para enviar este mensaje sería:
cuervo.desaparecer(info1, info2); |
En realidad se trata de "activar" la rutina llamada desaparecer, que forma parte del cuervo, pasándole como parámetros las variables info1 e info2, pero en terminología de programación orientada a objetos se dice que el objeto "cuervo" recibe el mensaje "desaparecer".
En respuesta a este mensaje, el objeto realizará las acciones que considere oportunas (estas acciones estarán escritas en la rutina desaparecer que forma parte del cuervo) y finalmente devolverá al programador una respuesta al mensaje. Normalmente la respuesta será un número o bien el valor true o false. ¿Te suena?
En efecto, las propiedades antes y despues que hemos visto en el capítulo anterior son mensajes que la librería envía a los objetos. Esto es lo que realmente ocurre cuando el jugador intenta comer la seta: primero la librería envía al objeto seta el mensaje antes, para avisarle de que el jugador intenta algo sobre él. El objeto seta puede contestar true, y entonces la librería ya no hace nada más con él, o bien puede responder false, en cuyo caso la librería lleva a cabo su acción Comer (esta acción, por defecto, comprobará si el objeto tiene el atributo comestible y actuará en consecuencia. Si lo tiene, de hecho, el objeto es comido por el jugador y desaparece del juego). Tras haber llevado a cabo la acción, la librería envía al objeto seta el mensaje despues, para informar a la seta de su desaparición (el objeto aquí puede aprovechar para imprimir algún texto adicional o cambiar el estado del juego). Si, en respuesta al mensaje despues el objeto responde true, la librería no hará nada más. Si en cambio responde false, imprimirá su mensaje estándar para esa acción (que sería "Te comes la seta moteada, no está mal").
Así que las propiedades como antes son en realidad reglas para manejar los mensajes que llegan a los objetos. En realidad esto es cierto para casi todas las propiedades de los objetos. Por ejemplo:
seta.descripcion(); |
es un mensaje enviado por la librería a la seta cuando el jugador la examina. Si la respuesta fuera false, la librería mostraría el mensaje "No observas nada especial en la seta moteada." Sin embargo el código de nuestra seta era de esta otra forma:
descripcion "La seta está totalmente cubierta de manchas y no
estás muy seguro de que no sea venenosa.",
|
Esto no parece una regla para manejar un mensaje, pero de hecho lo es. Significa "Imprime el texto que hay entre comillas, añade un retorno de carro y responde true". Podría haberse dado una regla más complicada, un mini-programa que muestre diferentes descripciones según el estado de otros atributos, por ejemplo:
descripcion [;
if (seta has general) "La seta está muy estropeada y despide un
terrible olor. Ahora estás seguro de que es venenosa.";
"La seta está totalmente cubierta de manchas y no estás muy
seguro de que no sea venenosa.";
],
|
Observa que en este caso descripcion ya es una rutina "normal", en el sentido de que va delimitada por corchetes como cualquier otra (como antes y despues).
Otro ejemplo, hagamos variable la descripción de los escalones, según el jugador los haya pisado o no:
descripcion [;
print "Los agrietados y gastados escalones descienden a una cámara
oscura. Tus pies ";
if (Camara has visitado) print "serán ";
else print "han sido ";
"los primeros en pisarlos en los últimos quinientos años.",
],
|
(En el ejemplo anterior usamos un nuevo atributo, llamado 'visitado'. Este es un atributo que pueden tener las habitaciones, sólo si el jugador ya ha estado en ellas. Es la librería la que lo pone automáticamente. Así podemos saber, si el jugador ha estado en la Camara, que ha tenido que pisar los escalones).
Además de los mensajes 'antes' y 'descripcion', la librería puede generar cerca de 40 mensajes diferentes. Cuanto más interesante sea un objeto, más ingeniosamente responderá a estos mensajes; un objeto que ignore los mensajes que le llegan será una cosa inerte en el juego, como una piedra.
![]() ![]() | En realidad la librería no siempre usa las propiedades mediante el envío de un mensaje. Por ejemplo, la propiedad nombre no es realmente un receptor de mensajes, sino una simple lista de datos. Por otro lado, la librería se toma sus precauciones para no enviar un mensaje descripcion (o cualquier otro) a un objeto que no proporcione la correspondiente propiedad. Pero la idea a grandes rasgos es correcta. |
De modo que la librería está constantemente enviando mensajes a tus objetos. Tus objetos también pueden enviarse mensajes unos a otros. Incluso puedes crear nuevos tipos de mensaje para que tus objetos se envíen entre sí (y que la librería no enviará nunca, ya que los has inventado tú). Esto es una forma habitual de hacer que ocurran cosas en el juego. Por ejemplo, supón que en "Ruinas" hay un loro que de vez en cuando grita lo que hace el jugador. Este loro podría estar programado para responder al mensaje "grita", que le sería enviado desde otros objetos. Así por ejemplo:
Object loro "loro de cola roja" Bosque
with nombre 'loro' 'lorito' 'pajaro',
adjetivos 'cola' 'roja',
descripcion "Bonito plumaje.",
gritar [ frase;
if (loro in localizacion)
print "El loro grita, ~¡", (string) frase, "! ¡",
(string) frase, "!~^";
],
has animado;
|
Observa cómo hemos añadido una nueva propiedad al loro, llamada gritar. Esta propiedad no es conocida por la librería y por tanto nunca la usará, en cambio otros objetos sí pueden enviarle al loro el mensaje 'gritar', indicándole como parámetro qué tiene que gritar el loro. La regla que seguirá entonces el loro será, en primer lugar comprobar que se halla en la misma localidad que el jugador (la variable localizacion siempre contiene la habitación en la que se halla el jugador). Si ambos están en la misma localización, entonces se imprime el texto, mediante el cual el loro grita (por dos veces) la frase dada. Si el loro no estuviera en el mismo lugar que el jugador, ese texto no sería impreso.
Podemos hacer que la seta cause el grito del loro, por ejemplo modificando una línea en la rutina antes de la seta:
Dejar: loro.gritar("Dejar la seta");
"La seta cae al suelo, ligeramente machacada.";
|
Así que cada vez que el jugador deje la seta, el loro gritará "¡Dejar la seta! ¡Dejar la seta!". De la misma forma otros muchos objetos pueden enviar al loro el mensaje gritar, con un texto diferente en cada ocasión. En cambio, sería un error enviar el mensaje gritar a otro objeto que no sea el loro porque sólo el loro tiene una regla programada que le dice qué hacer ante este mensaje.
EJERCICIO 2 |
Imagina que tienes una botella de medicamentos, que puede ser abierta
de varias formas en el juego. ¿Cómo harías para que el texto que dice
"Por fin la botella se abre con un crujido", aparezca una sola vez
en tu programa, como parte de la definición de la botella? (Solución) |
En la mayoría de los juegos habrá conjuntos de objetos con algunas reglas en común, y sería pesado tener que escribirlas varias veces. Para este tipo de conjuntos es una buena técnica definir una clase. Las definiciones de clases se parecen mucho a las definiciones de objetos, pero ya que sólo describen "prototipos" en vez de objetos reales, no tienen un lugar inicial. (Un árbol concreto estará en un lugar concreto, pero el concepto de árbol no está en ningún sitio en particular). Por esto la cabecera de la definición de una clase es más simple.
Por ejemplo, volvamos a nuestro juego "Ruinas". Le daremos un sistema de puntuación así: el protagonista es un arqueólogo de la vieja escuela, y obtiene puntos cada vez que encuentra un "tesoro" (es decir, un objeto de valor cultural) y lo deposita sano y salvo en su cajón de embalaje. Todos estos "tesoros" por tanto tienen una regla en común en lo que respecta a ser depositados en el embalaje y por tanto podremos programar la siguiente clase para describirlos:
Class Tesoro
with valor_cultural 10,
despues [;
Meter:
if (otro==embalaje)
puntuacion=puntuacion+self.valor_cultural;
"Empaquetado y a salvo.";
],
antes [;
Coger, Sacar:
if (self in embalaje)
"¿Desempaquetar un objeto tan valioso? Mejor esperamos
a que lo haga el Museo Metropolitano.";
];
|
Observar la palabra self. Se refiere al objeto concreto que esté ejecutando esa regla. En el código de la seta podíamos haber usado también la palabra self en vez de seta, para referirnos al objeto seta. Pero aquí la cosa es más interesante aún, ya que se refiere al tesoro concreto sobre el que se esté intentando actuar. Las explicaciones sobre Meter y Sacar vienen más tarde, pero espero que la idea quede clara. Ahora podemos crear objetos de la clase "Tesoro" y "heredarán" automáticamente todas las reglas y atributos que se hayan definido en la clase. Así por ejemplo
Tesoro estatuilla "estatuilla de un pigmeo"
with descripcion "Una estatuilla de un pigmeo, amenazadora, casi
como de cómic, con una serpiente alrededor del cuello.",
inicial "¡Aquí hay una preciosa estatuilla Maya!",
nombre 'estatuilla' 'maya' 'pigmeo' 'estatua' 'serpiente',
has femenino;
|
hereda un valor_cultural de 10, y las reglas para coger y
dejarla. Si hubiéramos dado a la estatuilla una propiedad
valor_cultural con el valor 15, entonces éste sería su valor ya
que la definición del objeto real siempre tiene prioridad sobre la que
se hubiera especificado en la clase.
Otro tesoro menos corriente:
Tesoro panal "panal antiguo"
with nombre 'miel' 'panal',
adjetivos 'antiguo',
descripcion "Quizás fuera algún tipo de ofrenda funeraria.",
inicial "¡Aquí hay una panal antiguo, lleno de miel
exquisitamente conservada!",
despues [;
Comer:
"Probablemente la comida más cara que hayas hecho en tu
vida. La miel sabe rara, quizás porque se usó para
conservar dentro las entrañas del rey, pero a miel de
todas formas.";
],
has comestible;
|
Este panal tiene ahora dos reglas 'despues'. Una, la suya propia para la acción Comer, otra la que heredó de la clase Tesoro para la acción Meter. Ambas serán usadas, pero la nueva se comprobará en primer lugar.
![]() |
Si comparamos las propiedades valor_cultural y
despues parece haber una inconsistencia. En la primera el
valor suministrado en el objeto real sustituía al valor que heredó
de la clase. En la segunda en cambio los dos valores se juntan en
una lista. ¿Por qué? La respuesta es que algunas de las propiedades
de la librería son especiales por ser "aditivas"
(additive), de modo que sus valores se van añadiendo en una
lista a través de las herencias. Los ejemplos más utilizados de este
tipo de reglas son antes, despues y
nombre.
|
![]() ![]() |
Las propiedades que tú mismo inventes (como aquella gritar
que tenia el loro) nunca serán aditivas, a menos que tú mismo lo
indiques, para lo cual deberás poner una línea así:
antes de que la propiedad gritar haya sido mencionada de otra forma. |
Finalmente, señalar que un objeto puede heredar de varias clases a la
vez (ver la sección sobre definición de objetos para una
explicación detallada de esto). Es más, una clase puede heredar de
otra clase, lo que hace muy fácil programar una clase "como la clase
Tesoro, pero con valor_cultural por defecto 8 en vez
de 10.
![]() | ![]() | ![]() | Conceptos de programación orientada a objetos |