Acciones y reaccionesFundamentos¡Empecemos!Conceptos de programación orientada a objetos

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.

Mensajes

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)

Clases

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í:

    Property additive gritar;

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.


Zak McKraken - spinf@geocities.com

Acciones y reaccionesFundamentos¡Empecemos!Conceptos de programación orientada a objetos