1. Aetheria Game Engine
Documentación de AGE
Cómo crear aventuras con Aetheria
Game Engine
Editor: Coordinador:
Notxor Al-Khwarizmi
Varios Autores
versión de 15 de octubre de 2012
2. Presentación de la
documentación
Estimado lector:
Esta documentación pretende servir de guía y referencia para crear aventuras
y otros juegos basados en texto para Aetheria Game Engine.
Los juegos para AGE se crean mediante su herramienta de desarrollo, lla-
mada PUCK (Playable Universe Construction Kit). Esta herramienta permite
crear los elementos del mundo (habitaciones, cosas, criaturas...) y sus interac-
ciones básicas mediante un entorno gráfico, sin necesidad de programar. Para
conseguir comportamientos más complejos, dinámicos y personalizados, este en-
torno gráfico se complementa con un lenguaje de programación (similar a Java)
llamado BeanShell.
Pero no te asustes: para aprender a crear juegos para AGE con este docu-
mento no es necesario tener ningún conocimiento previo de programación. Todos
los conceptos necesarios se explican desde cero, y crear juegos basados en texto
con PUCK es más sencillo que crear otro tipo de programas. Por lo tanto, aun-
que no sepas nada de programación, con esta guía estarás rápidamente creando
tus propios mundos interactivos; y si ya sabes algo, podrás saltarte varias de las
secciones introductorias.
Esta versión en PDF de la documentación se ha generado a partir de la wiki
(que está en http://www.caad.es/aetheria/doc/doku.php), gracias al traba-
jo de Notxor, que ha maquetado el PDF. La documentación está en constante
proceso de ampliación y mejora, y las actualizaciones irán apareciendo primero
en la wiki para pasar después a sucesivas versiones de este fichero.
No dudes dirigirme cualquier sugerencia o comentario, tanto como para me-
jorar el contenido de la documentación como para mejorar el propio sistema, al
email solrac888@yahoo.com.
Atentamente,
Al-Khwarizmi
Creador de AGE
7. Índice de figuras
1.1. Ventana de trabajo de puck. . . . . . . . . . . . . . . . . . . . . . 9
1.2. Iconos de puck. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.1. Relación tiempo ataque y Habilidad . . . . . . . . . . . . . . . . 110
3.2. Relación tiempo ataque y Habilidad (real) . . . . . . . . . . . . . 111
3.3. Probabilidad de éxito en el combate . . . . . . . . . . . . . . . . 112
3.4. Diagrama del proceso de funcionamiento de un hechizo. . . . . . 121
8. Capítulo 1
Uso básico de puck
1.1. Crear mundos con PUCK
El PUCK (Playable Universe Construction Kit) es una herramienta gráfica
de desarrollo que permite la creación de mundos interactivos para ser jugados
con el Aetheria Game Engine. Estos mundos pueden utilizarse para jugar
aventuras de texto, MUDs o juegos de rol mono y multijugador.
En las siguientes secciones explicaremos detalladamente cómo se puede uti-
lizar PUCK para crear un juego completo. Empezaremos con un resumen que
nos dará una idea general de lo que tendremos que hacer, para luego entrar más
específicamente en cada una de las etapas de creación de un mundo.
En la figura 1.1 puedes ver un ejemplo de aventura en PUCK.
1.2. Creación de Mundos para AGE con PUCK:
Resumen
Un juego en Aetheria Game Engine viene dado por un mundo, y un mundo
es una colección de diferentes objetos, que pueden ser de diversos tipos:
Habitaciones (también llamadas localidades), que son lugares del mundo
conectados entre sí por caminos. Una habitación puede representar literal-
mente una dependencia de una casa; pero también podría ser, dependiendo
de la ambientación, un cruce de caminos, una calle, etc.
Personajes, que son seres vivos que pueblan el mundo, incluyendo al
jugador, y que se pueden mover entre habitaciones a través de los caminos.
Por ejemplo, tenderos, camareros, enemigos, animales, el propio jugador,
etc.
Cosas, que son seres inertes que se encuentran en las habitaciones o que
llevan los personajes. Por ejemplo, puertas, llaves, cofres, sillas, mesas,
espadas, dinero, etc.
Para crear un juego completo, necesitaremos hacer las siguientes cosas (no
necesariamente en orden):
9. Creación de Mundos para AGE con PUCK: Resumen 9
Figura 1.1: Ventana de trabajo de puck.
Crear los objetos del juego,
Crear las relaciones entre ellos (por ejemplo, un camino es una relación
entre dos habitaciones. O, si un personaje lleva un objeto, entonces el
personaje y el objeto están relacionados mediante una relación «leva»),
Dar a los objetos nombres y descripciones para que se muestren en el
juego,
Dotar a los objetos de comportamiento, es decir, que puedan reaccionar
ante lo que hace el jugador y los demás objetos.
El PUCK (Playable Universe Construction Kit) da soporte a todas estas
actividades de la siguiente manera:
La creación de objetos se hace en el editor gráfico que ocupa la parte iz-
quierda de la ventana de PUCK. Las herramientas crear habitación, crear
personaje y crear cosa permiten crear los objetos y colocarlos en el mundo:
simplemente haz click sobre la herramienta correspondiente en la barra de
herramientas, y a continuación lleva el objeto creado hasta el lugar del
mundo en el que lo quieres poner. Nota: la representación gráfica del mun-
do es simplemente una herramienta de conveniencia. El lugar del panel
donde coloques un objeto no tendrá ninguna relevancia en el juego, así
que simplemente colócalos como más cómodos te sean para verlos y ma-
nipularlos.
La creación de relaciones entre objetos se hace también en el editor gráfico,
mediante las herramientas crear relación estructural y crear relación au-
xiliar. Para crear una relación, basta con hacer click sobre la herramienta
10. 10 Uso básico de puck
Figura 1.2: Iconos de puck.
correspondiente, y a continuación sobre los dos objetos que se desea rela-
cionar. La representación gráfica de cada relación es una flecha.
La descripción de los objetos se hace en los paneles de objeto que apare-
cen en la parte derecha de la ventana de PUCK. Para acceder al panel de
un objeto dado, basta con hacer click sobre él en la representación gráfi-
ca cuando ninguna herramienta está seleccionada. Se nos mostrará toda
la información de ese objeto (nombres, descripciones, etc.) y podremos
modificarla.
Por último, la descripción del comportamiento se hace también en los
paneles de objeto. Ésta es la parte de la creación de un mundo en la
que entra en juego la programación, para lo cual se utiliza el lenguaje de
scripting BeanShell. El código para cada objeto se modifica en la ficha
«Código y Propiedades» de su panel de objeto correspondiente.
1.3. Primeros Pasos: Creando Habitaciones y Ca-
minos
1.3.1. Habitaciones y Caminos
Veamos ahora cómo podemos usar PUCK para definir un mundo. Lo primero
que haremos será crear un par de habitaciones unidas entre sí mediante un
camino, de manera que el jugador pueda hacer algo como esto:
>mirar
11. Primeros Pasos: Creando Habitaciones y Caminos 11
Te encuentras al este del gran río Pecos. Hay muchos árboles bajos a tu alrededor.
Un precario puente de madera lo atraviesa, hacia el oeste.
>ir hacia el oeste
Atraviesas el puente hacia el oeste cuidadosamente. Las tablas de madera que
lo forman crujen inquietantemente; pero llegas sano y salvo a la otra orilla.
Te encuentras al oeste del gran río Pecos. Hay unos cuantos árboles altos a tu
alrededor. Un tosco puente de madera lo atraviesa, hacia el este.
>ir por el puente
Atraviesas el puente hacia el este con sumo cuidado. Las tablas que lo componen
emiten crujidos inquietantes; pero llegas sin incidencias a la otra orilla.
Te encuentras al este del gran río Pecos. Hay muchos árboles bajos a tu alrede-
dor. Un precario puente de madera lo atraviesa, hacia el oeste.
Para ello, lo primero que debemos hacer es ir al menú «Archivo – Nuevo»,
que nos creará un nuevo mundo en blanco para trabajar.
Al hacer esto veremos a la izquierda el editor gráfico en blanco, que es donde
colocaremos nuestros objetos; y a la derecha el panel de objeto asociado al
mundo. En este panel aparece un formulario con una serie de propiedades del
mundo. Es recomendable cubrir al menos el nombre corto y el nombre largo, que
AGE utilizará para identificar el mundo. El nombre corto debería estar formado
por una única palabra, aconsejablemente de no más de doce caracteres; mientras
que el nombre largo puede ser un título de la longitud que se desee, y es lo que
se mostrará al jugador como nombre de la aventura. El resto de campos (autor,
versión, fecha...) son datos meramente informativos que se mostrarán al jugador
cuando vaya a cargar el juego.
Si queremos dejar estas propiedades para más tarde, o modificarlas en algún
momento, es conveniente saber que podemos volver cuando queramos a este
panel de mundo, simplemente seleccionando una zona vacía del editor gráfico.
Pero pasemos a la acción: vamos a crear las dos habitaciones del ejemplo,
situadas una al oeste de otra y separadas por un camino (puente). Para ello
hacemos lo siguiente:
Hacemos click en icono de la herramienta «añadir habitación» de la barra
de herramientas.
Movemos el cursor por el editor gráfico. Veremos que aparece un cuadrado
(la habitación que estamos creando) que se mueve junto a nuestro cursor.
Hacemos click, y la habitación quedará fija en el editor. A la derecha
podremos ver su panel de objeto.
Hagamos que esta habitación represente la orilla oeste del gran río Pecos.
Para ello, cubrimos los campos del panel de objeto:
• Donde pone «Nombre único», pondremos un nombre corto como por
ejemplo «Oeste Pecos». Este nombre no se mostrará al jugador, se
utiliza para identificar internamente la habitación en el juego y dis-
tinguirla de las demás. Nosotros también lo utilizaremos cuando nos
queramos referir a esta habitación en el futuro.
12. 12 Uso básico de puck
• Donde pone «Descripciones» irán las descripciones de la habitación,
que son lo que se muestra al jugador cuando teclea «mirar» o cuando
entra en la misma. Puede extrañarte que no haya una sola «Des-
cripción», sino un campo para «Descripciones». Esto es porque las
descripciones en AGE no son un texto estático, sino algo que puede
variar según distintas condiciones (por ejemplo, la descripción de una
localidad puede ser distinta según si es de día o de noche). El sistema
que se utiliza para hacer posible esto es que la descripción que se
muestra al jugador esté formada por distintos trozos que se pueden
mostrar o no dependiendo de las condiciones que se den. En este ca-
so, nos conformaremos con una descripción estática, con lo cual sólo
necesitaremos uno de estos trozos, y sin ninguna condición asocia-
da. Para ello, donde pone «Descripción» teclearemos la descripción
completa de la habitación:
Te encuentras al oeste del gran río Pecos. Hay unos cuantos árboles altos
a tu alrededor. Un tosco puente de madera lo atraviesa, hacia el este.
Y pulsamos el botón «Añadir». Vemos que la descripción introduci-
da aparecerá en el cuadro «Descripciones», precedida de la palabra
«Siempre». Esto último quiere decir que esta descripción se mostrará
siempre que un jugador mire la habitación, es decir, no está sujeta a
condición alguna.
Por el momento no necesitamos añadir nada más a esta habitación. Es
bueno saber que, si más tarde queremos modificar sus datos, basta con
hacer click sobre su representación en el editor gráfico (cuando no estemos
usando ninguna herramienta) para poder ver y modificar de nuevo su panel
de objeto.
Del mismo modo que creamos la habitación anterior, creamos una nueva
para representar la orilla este del río. Para ello, tenemos que volver a hacer
click en la herramienta «añadir habitación», ya que cada uso de la herra-
mienta sirve para añadir una sola vez. Colocaremos la nueva habitación a
la derecha de la anterior, dado que se encuentra al este. Si no hemos dejado
espacio suficiente en el editor gráfico para colocarla, podemos utilizar las
herramientas de desplazamiento y zoom:
• Para usar la herramienta de desplazamiento, hacemos click sobre su
icono en la barra de herramientas: A continuación, hace-
mos un click sobre el editor gráfico, y movemos el cursor: los objetos
del editor se desplazarán con él. Con otro click dejamos de usar la
herramienta.
• La herramienta zoom se utiliza del mismo modo. Después de hacer
click en su icono y en el editor gráfico, podemos mover el
ratón hacia arriba para alejar la vista o hacia abajo para acercarla.
13. Primeros Pasos: Creando Habitaciones y Caminos 13
Rellenamos el nombre único y la descripción de la nueva habitación de
la misma manera que lo habíamos hecho con la anterior, reflejando que
estamos al este del Pecos.
Ahora vamos a crear la conexión entre las dos habitaciones. Como po-
demos ver en el texto de lo que queremos conseguir, las dos localidades
deben estar unidas por un puente que cruza de este a oeste. El puente se
debe poder cruzar en las dos direcciones; pero es importante saber que en
AGE los caminos son de una sola dirección. Por lo tanto, para permitir
el tránsito en las dos direcciones tenemos que crear dos caminos: uno de
oeste a este y otro de este a oeste. Nótese que, aunque esto pueda parecer
incómodo para casos simples, hace el sistema más poderoso, porque se
puede dar tratamiento distinto a cada sentido del camino, incluyendo per-
mitir el paso en un solo sentido, tener descripciones distintas para ambos,
hacer que suceda algo al cruzar en un sentido dado, etc.
Para crear los caminos, utilizaremos la herramienta «añadir relación es-
tructural» de la barra de herramientas, que está identificada por este icono:
Las relaciones en AGE son formas de expresar cualquier tipo de relación,
permanente o temporal, entre dos objetos. Existen dos tipos de relaciones:
relaciones estructurales y relaciones auxiliares. Las estructurales se llaman
así porque son las que definen la estructura del mundo: nos dicen dónde
están los objetos. También se caracterizan porque un objeto no tendría
ningún sentido si no estuviese (o al menos pudiese estar) conectado al resto
del mundo mediante relaciones estructurales. Las relaciones auxiliares se
utilizan para definir otros aspectos de las interacciones entre objetos.
Un camino se modela en AGE como una relación estructural entre una
habitación y otra, de ahí que se cree con esta herramienta. Para crear un
camino entre las dos habitaciones, seguimos los siguientes pasos:
• Hacemos click sobre la herramienta «añadir relación estructural»
• Hacemos click sobre la habitación que representa el Oeste del Pecos.
• Hacemos click sobre la otra habitación. Veremos que aparece una
flecha entre las dos habitaciones, que representa el camino que las
une.
Al igual que los objetos, podemos ver que las relaciones también tienen
paneles de objeto. Nada más crear esta relación camino, podremos ver
en la parte derecha de la ventana de PUCK su panel correspondiente.
El panel tiene algunos campos ya cubiertos, como son las habitaciones
origen y destino (que son las que marcamos en el mapa) y la dirección
14. 14 Uso básico de puck
estándar, que es un punto cardinal asociado al camino. Como habíamos
colocado una habitación a la derecha de la otra, el camino aparecerá auto-
máticamente marcado con la dirección estándar «este». Esto implica que
se podrá utilizar con comandos como «ir al este». Si a pesar de haber
colocado las habitaciones de esa manera éste no era el comportamiento
que queríamos, podemos cambiar la dirección estándar (o quitarla para
no tener ninguna) en el panel de objeto correspondiente al camino. Del
mismo modo podríamos cambiar en cualquier momento las habitaciones
origen y destino, podemos ver que para esto se utiliza el «nombre único»
que hace referencia a cada habitación.
Debajo de estos campos podemos ver una lista, inicialmente vacía, de co-
mandos personalizados. En esta lista podemos introducir todas aquellas
palabras que el jugador pueda usar para referirse a este camino además
del punto cardinal dado por la dirección estándar. Como queremos que el
jugador pueda teclear cosas como «ir puente» o «ir por el puente», añadi-
mos «puente»: para ello, introducimos la palabra donde pone «Comando»
y pulsamos el botón «Añadir».
A continuación podemos ver un área para descripciones, que ya debería
resultarnos familiar. Las descripciones para los caminos funcionan exacta-
mente igual que para las habitaciones; pero se muestran cuando el jugador
cruza el camino correspondiente. Así, para obtener el comportamiento que
buscamos para este camino, debemos teclear en el campo «Descripción»:
Atraviesas el puente hacia el este con sumo cuidado. Las tablas que lo com-
ponen emiten crujidos inquietantes; pero llegas sin incidencias a la otra orilla.
Con esto tenemos todo lo que necesitábamos por el momento para este
camino. Cuando volvamos a necesitar modificar este panel de objeto, basta
con que hagamos click sobre la flecha que representa el camino en el mapa
para acceder a él.
Para crear el otro camino, como es imaginable, basta con repetir el mismo
proceso; pero haciendo click primero sobre la habitación del este y luego
sobre la del oeste. Nos aparecerá otra flecha en dirección opuesta, que
no se solapa con la anterior para que podamos hacer click fácilmente en
cualquiera de ellas accediendo a su correspondiente panel de objeto. La
descripción y el comando personalizado se introducen del mismo modo.
1.3.2. Creando un Personaje Jugador
Con esto ya hemos creado un mínimo mundo con el que jugar; pero nos falta
un elemento necesario para que la aventura tenga sentido: un personaje que pue-
da recorrerlo. En AGE, los jugadores están en la piel de personajes que habitan
en el mundo e interactúan con él (aunque también pueden crearse personajes
que no sean jugadores). Por lo tanto, incluso en un juego para un solo jugador
deberemos poner al menos un personaje que represente a ese jugador para que
se pueda jugar. Para ello, utilizaremos la herramienta «añadir personaje» de la
barra de herramientas:
15. Primeros Pasos: Creando Habitaciones y Caminos 15
Y haremos lo siguiente:
Para usar esta herramienta, procedemos de la misma manera que cuando
añadimos habitaciones al mundo: hacemos click sobre su icono en la ba-
rra de herramientas, deslizamos el cursor por el editor gráfico y hacemos
click sobre el punto en que queremos dejar al personaje. Será conveniente
que lo coloquemos cerca de la habitación donde queremos que el jugador
aparezca al principio del juego; pero –ojo, importante– no dentro de ella.
Esto es porque en el editor gráfico el hecho de que algo esté dentro de
una habitación no se representa colocando su icono dentro de la misma;
sino uniendo la habitación a ese algo mediante una relación estructural.
Representar unas cosas dentro de otras podría parecer más natural a sim-
ple vista; pero resultaría engorroso cuando una habitación contuviese gran
número de personajes o cosas.
Así pues, el siguiente paso será crear la relación estructural entre la ha-
bitación (que puede ser la oeste o la este, dependiendo de dónde queréis
que empiece el personaje: lo que creamos con PUCK es siempre el estado
inicial del mundo, que luego podrá cambiar durante el juego cuando los
personajes se muevan y manipulen los objetos) y el personaje. Para ello,
utilizamos la herramienta «crear relación estructural» que ya conocemos:
y hacemos click primero sobre la habitación y luego sobre el
personaje (el orden es importante). Esto nos creará una relación estructu-
ral de tipo «contiene» que significa que la habitación contiene al personaje,
y que, al contrario que la relación «camino» que veíamos antes, no tiene
nada que configurar.
Para que todo funcione, sólo falta una cosa: especificar que el personaje
que hemos creado es un jugador. Para ello, hacemos click sobre él para
obtener su panel de objeto, y marcamos la casilla que dice «jugador».
Esto hace que, cuando alguien arranque la aventura, el AGE lo ponga en
la piel de este personaje (en juegos multijugador las cosas son algo más
complicadas; pero ya las veremos).
Si hurgamos en el panel de objeto asociado al personaje podemos ver
que tiene diversos datos configurables, como por ejemplo nombre único
y descripciones. Estas últimas funcionan del mismo modo que las de las
habitaciones y caminos, y en este caso se mostrarían si el jugador se mirase
a sí mismo. Puedes cubrirlas si quieres; aunque no son necesarias para el
comportamiento que queríamos.
Ahora sí que tenemos un mundo de AGE completo y jugable (aunque, de
momento, no muy emocionante). Para jugarlo, no tenemos más que hacer click
en la herramienta Ejecutar mundo:
16. 16 Uso básico de puck
La herramienta nos recuerda que para probar la aventura tiene que guardar
el fichero (lo hará automáticamente) y nos da a elegir entre dos interfaces de eje-
cución: el SDI (ventana simple) o el MDI (ventana con sub–ventanas). Podemos
escoger cualquiera de los dos; aunque para propósitos de prueba de aventuras
de un solo jugador el SDI suele resultar suficiente y más sencillo. Después, le
damos al botón Aceptar para ejecutar la aventura.
Si lo hemos hecho todo bien, podremos jugar la partida que hemos visto al
principio de la sección.
No se puede decir que haya sido difícil, ¿verdad? Pues ahora vamos a mejo-
rarlo.
1.4. Seres inertes
1.4.1. Descripciones de componentes
En la sección 1.3, hemos visto algunos conceptos básicos de PUCK y hemos
definido un mundo rudimentario con un par de habitaciones y caminos. Por el
momento, lo único que este mundo permite hacer al jugador es desplazarse de
una habitación a otra a través de los caminos; pero la interacción con el mundo
no va mucho más allá. Por ejemplo:
>mirar
Te encuentras al este del gran río Pecos. Hay muchos árboles bajos a tu alrededor.
Un precario puente de madera lo atraviesa, hacia el oeste.
>mirar los árboles
¿Qué pretendes mirar?
>mirar el puente
¿Qué pretendes mirar?
Parece que una primera característica deseable para mejorar la interacción
del jugador con el mundo sería que se pudiesen mirar los objetos que apare-
cen mencionados en las descripciones. Así, nos gustaría que cuando el jugador
pusiese «mirar los árboles», apareciese una descripción de los mismos para dar
ambientación. La forma más sencilla de conseguir esto en AGE mediante las
llamadas descripciones de componentes o descripciones extra. Las descripciones
de componentes pueden añadirse a casi cualquier objeto del mundo (incluyendo
habitaciones, personajes, cosas, etc.) y siguen siempre el mismo sistema, que es
una extensión del que seguían las descripciones convencionales que hemos visto.
Así, si en la sección anterior veíamos que las descripciones convencionales
eran realmente listas de descripciones, cada una de las cuales tenía asociada una
condición (que por el momento habíamos dejado en blanco para que se mostraran
siempre) y un texto; las descripciones extra también serán listas de descripciones;
pero cada una tendrá asociada una serie de nombres, una condición y un texto.
Si añadimos una descripción extra a una localidad y el jugador teclea «mirar
‘algo’» (o alguna frase sinónima) en ella, entonces se mostrará la descripción si
ese ‘algo’ coincide con uno de los nombres y además la condición asociada se
cumple.
17. Seres inertes 17
Vamos a hacer que el jugador pueda mirar los árboles y el puente desde la
localidad oeste. Para ello, hacemos click sobre esta localidad en el editor grá-
fico, y nos aparece su panel de objetos. Debajo de la sección de descripciones
convencionales que ya tenemos cubierta, vemos la sección «Descripciones de
componente». En el campo «Nombres de ref. sep. por comas» tecleamos, sin las
comillas: «árbol,árboles»; y en el campo «Descripción» ponemos lo que quere-
mos que le aparezca al jugador cuando mire los árboles, como puede ser «Son
unos árboles muy verdes y muy bonitos.» Como en ocasiones anteriores, dejamos
el campo «Condición» vacío, ya que esta descripción no dependerá de ninguna
condición externa, y pulsamos el botón «Añadir». En la lista de descripciones
extra veremos algo como «árbol,árboles: Siempre: Son unos árboles muy verdes
y muy bonitos», donde se muestran los nombres y la descripción; y la palabra
«Siempre» indica que la descripción va a mostrarse siempre que el jugador mire,
sin depender de ningún otro factor.
Lo mismo podemos hacer con el puente si añadimos una descripción extra con
nombre «puente» y descripción «Es un precario puente de madera.» o cualquier
texto similar. Si queremos que el puente se vea desde las dos localidades, como
sería lógico, tenemos que poner su descripción extra en ambas.
1.4.2. Cosas
Las descripciones de componentes proporcionan una primera manera de ha-
cer el mundo más interesante y llegar un poco más allá de las habitaciones y
caminos; pero tienen sus limitaciones. Las descripciones de componentes nos
permiten definir elementos del mundo que los jugadores pueden mirar; pero la
posible interacción con ellos se reduce sólo a eso: a mirar.
A la hora de crear un juego decente, esto seguramente no sea suficiente. Nos
interesan objetos físicos con los que los jugadores puedan interactuar de más
maneras: cogerlos, llevarlos en su inventario, dejarlos en alguna otra parte, y
seguramente hacer más cosas dependiendo del tipo de objeto que sea. Este tipo
de objetos físicos son lo que en AGE denominamos «cosas».
Para probar las cosas, vamos a crear una piedra que el jugador pueda al
menos coger, llevar y dejar. En capítulos posteriores del tutorial veremos cómo
hacer cosas más complejas (por ejemplo que puedan abrirse y cerrarse, llevar
otras cosas dentro, etc.); pero por el momento empezaremos por lo básico.
Creación de cosas
Al contrario que las descripciones extra, las cosas son objetos de pleno dere-
cho en AGE, y tienen una representación gráfica en el editor del PUCK. Para
añadir una cosa, utilizamos la herramienta «añadir cosa»:
A la hora de utilizar esta herramienta, procedemos de manera análoga a
cuando habíamos añadido un personaje al mundo: hacemos click sobre el icono
de «añadir cosa», movemos el cursor por el editor gráfico hasta dejarlo en la
ubicación deseada para el objeto (que no debe coincidir con la habitación en la
que queremos ponerlo; aunque sí es conveniente que esté cerca), y hacemos click
18. 18 Uso básico de puck
sobre esa ubicación. Para colocar la cosa en una localidad, igual que habíamos
hecho con el personaje, creamos una relación estructural: hacemos click en la
herramienta «crear relación estructural»
y a continuación en la habitación y en la cosa, por ese orden. Esto creará una
relación «contiene» entre habitación y cosa que indica que la cosa está situada
en dicha habitación.
Características básicas de cosas
Una vez que tenemos nuestra cosa creada y vinculada a una habitación, ha-
cemos click en su icono del editor gráfico para ver su panel de objeto. Los paneles
de objeto de las cosas son algo más complicados que los de las habitaciones; pero
algunos de sus componentes nos resultarán familiares, y otros los aprenderemos
fácilmente.
En primer lugar podemos ver el campo para el nombre único, que funciona
igual que el de las habitaciones, sirviendo para distinguir internamente el objeto.
En este punto es conveniente aclarar que los nombres únicos deben ser realmente
únicos, es decir, no debe haber en un mundo dos objetos con el mismo nombre
único, ni siquiera si son objetos de tipos completamente distintos. Esto quiere
decir que no debes ponerle a una cosa un nombre que hayas utilizado para, por
ejemplo, una habitación. A nuestro objeto podemos llamarle «Piedra».
Bajo el nombre único, debajo de unos campos «Heredar de:» y «Ejemplo
de:» que no veremos por el momento, podemos ver un campo de «Género»,
que nos da a elegir entre masculino y femenino. El género de una cosa es el
género que tiene en castellano el nombre de esa cosa, y se utiliza para construir
los textos del juego. Si pusiésemos que la piedra es «Masculino», podríamos
obtener textos en el juego como «aquí hay un piedra» o «coges el piedra del
suelo», así que es importante cubrir bien este campo si queremos un mundo que
hable decentemente el idioma.
Debajo del género, podemos ver unos campos para el «Peso» y el «Volumen»
de la cosa que, como es imaginable, representan el peso y el volumen que tiene el
objeto en el mundo virtual que estamos creando. Aunque se pueden utilizar para
más cosas, la consecuencia más inmediata de estos valores es que un personaje no
podrá llevar consigo objetos que superen el máximo peso y volumen que pueda
acarrear. Si en tu juego no quieres prestar atención a esos detalles, simplemente
puedes dejar el peso y el volumen de todos los objetos a cero. En este caso
podemos poner, por ejemplo, peso y volumen 5.
Después de los campos correspondientes al peso y el volumen, hay en el
formulario dos opciones llamadas «Contenedor» y «Fijo en el sitio». La opción
«Contenedor» puede marcarse para representar cosas que puedan tener otras
cosas dentro, como una bolsa o un baúl.1 La opción «Fijo en el sitio» denota un
1 El
autor de AGE recomienda a título personal no usar los contenedores en juegos más
de lo estrictamente necesario, pues tienden a complicar innecesariamente la vida al jugador
al tener que preocuparse de sacar y meter objetos de dentro de otros, cosa que puede ser
interesante cuando es parte del argumento de la aventura (encontrar la llave de un antiguo
baúl, etc.) pero no aporta mucho si se usa como elemento de ambientación.
19. Seres inertes 19
objeto que no se puede coger, como una farola o una montaña; todas las cosas
que no tengan esta opción marcada serán por defecto susceptibles de ser cogidas
por los jugadores. En el caso de la piedra, lo lógico será no marcar ninguna de
las dos opciones, ya que una piedra no puede contener otras cosas en su interior,
y queremos que el jugador la pueda coger.
A continuación están las descripciones y las descripciones de componentes,
que se rellenan de la misma manera que en las habitaciones. Las descripcio-
nes en este caso aparecerán cuando un jugador ponga «mirar ‘nuestra cosa’», y
funcionarán tanto si el jugador lleva consigo la cosa como si simplemente está
en la misma localidad. Las descripciones de componentes se pueden usar para
describir partes o características de los objetos: por ejemplo, si nuestro objeto
es una linterna, podemos usar una descripción extra para describir el botón de
encendido de la linterna. Si ponemos como nombre para la descripción de com-
ponente «botón», y nuestra linterna respondía al nombre «linterna», el jugador
podrá ver la descripción extra tecleando «mirar el botón de la linterna». Noso-
tros nos limitaremos a añadir una descripción convencional para la piedra, algo
como: «Es una piedra estándar, de las de toda la vida. Podrías abrir cabezas
con ella.»
Nombres para mostrar y nombres de referencia
Todo lo mencionado está en la ficha «General» del panel de objeto, que
es la que hemos visto hasta ahora en todos los objetos que hemos creado. Sin
embargo, puede que ya te hayas fijado en que algunos de ellos tienen más fichas
aparte de ésta, y es el caso de nuestra recién creada piedra. Ahora vamos a ir
a la segunda ficha, llamada «Nombres». Cubrir esta ficha es esencial para crear
una cosa, porque nos permite definir el nombre o nombres de dicha cosa.
Hasta ahora, en la ficha «General» del panel habíamos visto el «Nombre
único» de la cosa, que nos servía para identificarla. Pero, como ya mencionamos
al hablar de habitaciones, los nombres únicos de los objetos sólo se usan para
que los distinga el creador del juego y el propio juego; sin que se muestren en
ningún caso al jugador. Por lo tanto, tendremos que definir otros nombres (que
pueden coincidir o no con el nombre único) para mostrar al jugador y para que
el jugador pueda interactuar con la piedra.
En la ficha «Nombres» podemos ver espacio para poner cuatro tipos de nom-
bres: «Nombres singulares para mostrar», «Nombres plurales para mostrar»,
«Nombres singulares de referencia» y «Nombres plurales de referencia».
Los dos primeros tipos son los nombres que se mostrarán al jugador, y fun-
cionan exactamente igual que las descripciones. Si queremos que el objeto se
llame siempre de una manera determinada y ya está, basta con que tecleemos
su nombre en el campo «Nombre:» de «Nombres singulares para mostrar» y
pulsemos «Añadir». En nuestro caso, si añadimos de esta manera el nombre
«piedra», esto se traducirá en el juego a textos como «aquí puedes ver una
piedra», «coges la piedra» «dejas la piedra», «llevas una piedra»... Si quisiése-
mos que el nombre de un objeto cambiase según circunstancias del juego (por
ejemplo, la piedra podría convertirse en una «piedra mojada» si le echásemos
agua por encima), tendríamos que añadirle más nombres singulares y asociarles
diferentes condiciones. Nosotros nos conformaremos con un nombre estático, así
que añadimos «piedra» sin especificar condición alguna.
Los «Nombres plurales para mostrar» se utilizan para mostrar al jugador
20. 20 Uso básico de puck
si hay varios objetos iguales en un mismo sitio. Por ejemplo, para que el juego
pudiese construir frases como «llevas dos piedras» o «aquí hay tres piedras»,
tendríamos que añadir «piedras» como nombre plural. Si no va a haber varios
objetos iguales en el juego que se puedan agrupar de esta manera, como es
nuestro caso, podemos dejar los nombres plurales en blanco. Más adelante ve-
remos cómo se pueden crear varios objetos idénticos que se puedan agrupar, y
utilizaremos estos nombres plurales.
Los «Nombres singulares de referencia» son aquellos nombres por los cuales el
jugador se puede referir al objeto. Estos nombres no se muestran en la aventura;
pero serán los que el sistema utilice para saber que el jugador se ha referido en su
orden a un objeto dado. Como al jugador puede ocurrírsele referirse a una cosa
usando una palabra que no sea el nombre que se le muestra en pantalla, suele
ser recomendable incluir sinónimos en los nombres de referencia. Por ejemplo,
en este caso podemos añadir los siguientes nombres de referencia para la piedra:
«piedra», «roca» y «pedrusco». Después de teclear cada nombre en el campo
«Nombre», pulsamos el botón «Añadir» para colocarlos en la lista, o podemos
pulsar «Cambiar» para modificar alguno si nos hemos equivocado. De este modo,
el jugador podrá referirse a la piedra mediante comandos como «coger piedra»,
«dejar roca» o «mirar pedrusco», y todos serán entendidos como referidos a
nuestro objeto piedra.
En los «Nombres plurales de referencia» pondremos los nombres por los
cuales el jugador se puede referir a un conjunto de objetos que incluya éste.
Normalmente, tendremos aquí las versiones de los nombres singulares de refe-
rencia: en este caso «piedras», «rocas» y «pedruscos». De esta forma, si hay
varias piedras (todas con esos nombres de referencia) y el jugador pone «coger
las piedras» o «coger todas las rocas», su orden se ejecutará sobre todas ellas.
Un truco que suele venir bien, y podemos hacer en este caso, es poner como
último nombre plural de referencia «todo». De esta manera, cuando el jugador
ponga «coger todo», su orden actuará sobre todos los objetos que tengan ese
nombre plural de referencia.
Interacciones básicas con las cosas
Una vez rellenados de este modo los nombres para mostrar y de referencia de
nuestra piedra, podemos utilizar la herramienta «Ejecutar mundo» para probar
el objeto que hemos creado. Deberíamos ser capaces de hacer estas cosas:
>mirar
Te encuentras al este del gran río Pecos. Hay muchos árboles bajos a tu alrededor.
Un precario puente de madera lo atraviesa, hacia el oeste.
Aquí hay una piedra.
>mirar los árboles
Son unos árboles muy verdes y muy bonitos.
>mirar la piedra
Es una piedra estándar, de las de toda la vida. Podrías abrir cabezas con ella.
>coger la roca
Coges la piedra.
Es una piedra estándar, de las de toda la vida. Podrías abrir cabezas con ella.
>inventario
Tienes una piedra.
>dejar todo
21. Seres inertes 21
Dejas la piedra.
>inventario
No tienes nada.
>mirar
Te encuentras al este del gran río Pecos. Hay muchos árboles bajos a tu alrededor.
Un precario puente de madera lo atraviesa, hacia el oeste.
Aquí hay una piedra.
Cosas con el mismo nombre de referencia
Como hemos visto, los nombres de referencia nos determinan cómo el jugador
se puede referir a una cosa. Pero a veces podría darse el caso de que varias cosas
compartan un mismo nombre de referencia. Por ejemplo, supongamos que hemos
definido las siguientes dos cosas:
Una piedra blanca, que tiene como nombre para mostrar «piedra blan-
ca», y como nombres de referencia «piedra blanca», «pedrusco blanco»,
«piedra», «pedrusco».
Una piedra negra, que tiene como nombre para mostrar «piedra negra», y
como nombres de referencia «piedra negra», «pedrusco negro», «piedra»,
«pedrusco».
En este ejemplo, hemos puesto «piedra» como nombre de referencia de ambas
piedras para que cuando el jugador encuentre una de ellas pueda simplemente
teclear comandos como «coger piedra» y funcionen, sin especificar el color:
Aquí hay una piedra blanca.
>coger la piedra
Coges la piedra blanca.
Sin embargo, ¿qué pasará si el jugador teclea «coger la piedra» en una habi-
tación donde está tanto la piedra blanca como la negra? ¿Cuál de ellas cogerá?
La respuesta es que los nombres de referencia tienen una prioridad corres-
pondiente a la posición que ocupan en la lista de nombres de referencia de
PUCK (posición más alta quiere decir mayor prioridad). Cuando un jugador
teclea una orden, las palabras que siguen al verbo se comparan con los nombres
de referencia de los objetos que están al alcance del jugador, seleccionándose
aquellos nombres de referencia que aparezcan mencionados en las palabras te-
cleadas. En el caso de que haya un único nombre de referencia seleccionado de
este modo (o varios, pero todos pertenecientes a la misma cosa), la acción se
ejecuta sobre la cosa que tiene ese nombre. En el caso de que se seleccionen
nombres de referencia de diferentes cosas, la acción se ejecuta sobre aquélla a
la que perteneza el nombre de referencia de mayor prioridad (es decir, el que
aparece más arriba en la lista) de entre los seleccionados. En el caso de que haya
un empate a prioridades, se ejecuta sobre un objeto cualquiera.
Por ejemplo, si en el caso de las piedras blanca y negra hemos introducido
los nombres de referencia en el orden especificado arriba, las prioridades para la
piedra blanca serían:
Prioridad 1. piedra blanca
Prioridad 2. pedrusco blanco
22. 22 Uso básico de puck
Prioridad 3. piedra
Prioridad 4. pedrusco
Y para la piedra negra serían:
Prioridad 1. piedra negra
Prioridad 2. pedrusco negro
Prioridad 3. piedra
Prioridad 4. pedrusco
Si el jugador teclea «coger la piedra blanca», en la orden tecleada aparecen
nombres de prioridad 1 y 3 de la piedra blanca (aparece «piedra blanca», de
prioridad 1, pero también aparece «piedra», de prioridad 3, como parte de la
cadena). Sin embargo, de la piedra negra, sólo aparece un nombre de prioridad
3 («piedra»). Como la prioridad 1 le gana a la prioridad 3, el jugador cogerá la
piedra blanca.
Si el jugador teclea «coger el pedrusco negro», en la orden aparece un nombre
de prioridad 4 de la piedra blanca («pedrusco») y nombres de prioridad 2 y 4
de la piedra negra («pedrusco negro» y «pedrusco»). Como la prioridad 2 es
mayor que la prioridad 4, el jugador cogerá la piedra negra.
¿Qué pasa si el jugador teclea simplemente «coger una piedra»? En este caso
para ambos objetos se seleccionaría un nombre de prioridad 3 («piedra»). Como
hay empate a prioridades, el jugador cogería una piedra cualquiera: se supone
que ambas se ajustan por igual a la orden dada por el jugador, así que si sólo ha
especificado «coger una piedra», debería servirle cualquiera de los dos. Si por
algún motivo quisiéramos asegurarnos de que con esta orden el jugador siempre
cogiese una piedra dada (por ejemplo, la blanca); tendríamos que hacer que el
nombre de referencia «piedra» tuviese más prioridad en esa piedra que en la
otra.
En general, para que este sistema de prioridades para los nombres de referen-
cia nos conduzca a una interpretación natural de las órdenes de los jugadores,
lo único que tendremos que hacer es poner los nombres más específicos por en-
cima de los más genéricos (tal y como hemos hecho en el ejemplo de la piedra).
Entrando más en detalle, lo que necesitamos es que si un nombre de referencia
es específico de un objeto, tenga más prioridad que los nombres más genéricos
de otros objetos que puedan entrar en conflicto con ese nombre específico. Por
ejemplo, si el nombre específico «piedra blanca» tuviera prioridad 3 en la piedra
blanca y el nombre genérico «piedra» tuviera prioridad 1 en la piedra negra,
tendríamos problemas, porque al «coger la piedra blanca» ganaría la piedra
negra al tener el nombre genérico «piedra» con más prioridad que el nombre
específico «piedra blanca» de la piedra blanca. Sin embargo, casos como éste
prácticamente nunca se podrán dar si seguimos la norma general de que, dentro
de cada cosa, los nombres específicos aparecen antes que los genéricos. Siguien-
do esta simple regla, normalmente no hará falta acordarse de cómo funciona el
sistema de prioridades salvo para casos avanzados en los que se quiera tener un
control muy detallado de las maneras de referirse a cada objeto.
Nótese que, si en lugar de nombres singulares de referencia hablamos de
nombres plurales de referencia, con estos últimos la acción no se ejecutará sobre
23. Seres inertes 23
un solo objeto, sino con todos los que tengan un nombre plural de referencia
seleccionado. Es decir, si las dos piedras del ejemplo tienen como nombre plural
de referencia la palabra «piedras», entonces la orden «coger las piedras» hará
que el jugador coja ambas, independientemente de las prioridades.
24. Capítulo 2
Uso del lenguaje BeanShell
Los formularios de PUCK, como los vistos en el capítulo anterior, propor-
cionan una manera sencilla de construir un mundo con diferentes localizaciones
y objetos que los jugadores puedan manipular; sin necesidad de escribir código
en ningún lenguaje de programación. Sin embargo, si queremos conseguir mun-
dos complejos donde las entidades exhiban comportamientos dinámicos y donde
podamos definir acciones personalizadas que vayan más allá de los comporta-
mientos por defecto, sí que necesitaremos programar.
Para estas definiciones de comportamientos complejos que requieren progra-
mación, AGE utiliza el lenguaje BeanShell. BeanShell es un lenguaje de scripting
muy parecido a Java. De hecho, la sintaxis de Java se puede utilizar tal cual en
BeanShell; pero éste además permite usar variables con tipado dinámico y tiene
otras características donde «relaja» los requisitos de Java.
Para añadir código BeanShell a un mundo con PUCK, se puede ir a la pesta-
ña «Código y Propiedades» de cualquier entidad (las entidades son los objetos
del mundo representados por iconos que añadimos en el mapa de PUCK, como
habitaciones, cosas y criaturas) o bien del propio mundo, y nos aparecerá un
área de texto (con un botón «Ampliar» que la convierte en una ventana inde-
pendiente) para introducir el código. AGE es un sistema orientado a objetos, de
modo que el código que modifique el comportamiento de una entidad determina-
da deberá introducirse en el campo de código de esa entidad. El campo de código
del mundo permite hacer modificaciones más globales en el comportamiento de
la aventura.
A lo largo de las secciones de este capítulo veremos una introducción a cómo
programar en BeanShell para crear aventuras en AGE. Esta información se
ampliará en subsiguientes capítulos, donde la entremezclaremos con más cosas
que se pueden hacer en los formularios de PUCK; ya que ambas cosas no son
independientes sino que hay aspectos de PUCK que hacen uso o interactúan
con el código BeanShell, haciendo necesario tratarlas de forma entrelazada.
2.1. Primeros pasos con BeanShell
BeanShell es un lenguaje orientado a objetos, basado en Java, que se utiliza
para definir comportamientos avanzados en entidades y mundos de AGE. En
ésta y las siguientes secciones, describiremos cómo se puede usar BeanShell
25. Primeros pasos con BeanShell 25
para este propósito. Esto quiere decir que no veremos exhaustivamente todas
las características de BeanShell, sino que sólo describiremos lo necesario para
utilizarlo en AGE de forma lo más sencilla posible. Los programadores que
quieran un conocimiento más completo y riguroso de BeanShell, incluyendo
todas sus características y no limitado a AGE, pueden consultar su página web
http://www.beanshell.org.
2.1.1. Los formularios de código
En los formularios de «Código y propiedades» de PUCK se puede escribir
código BeanShell. Este código puede estar asociado a una entidad concreta
del mundo (una habitación, cosa, etc.), en el caso de que lo escribamos en el
formulario de una entidad; o al mundo en su conjunto, si lo escribimos en el panel
del mundo. La idea es que el comportamiento de cada entidad se especifique
dentro de esa entidad, de modo que las entidades sean unidades autocontenidas
que se puedan llevar fácilmente de un mundo a otro. Por ejemplo, si definimos
una máquina de coser, querremos que el código que usamos para que cosa esté
definido en la entidad «máquina de coser»: de este modo no sólo queda más claro
dónde buscar el código de cada cosa, sino que además nos podríamos llevar esa
entidad a otra aventura y seguiría cosiendo. El panel de código del mundo, por lo
tanto, se utilizará para comportamientos que no estén asociados a una entidad
particular, sino al juego en general.
2.1.2. Los métodos
El código que escribamos en un formulario siempre tendrá que constar de
uno o más métodos. Un método es una porción de código que recibe unos datos
de entrada y los procesa de una u otra manera, y, para los que vengan de otros
lenguajes, es algo análogo al concepto de función o subrutina.
El código de un método consta de una cabecera, que indica qué datos espera
el método como entrada y cuáles produce como salida, y un cuerpo escrito entre
llaves que contiene las instrucciones ejecutadas por el método. Las cabeceras de
los métodos no hace falta escribirlas, las podemos generar directamente con los
menús del PUCK. Sólo hará falta escribir, pues, el cuerpo de los métodos (parte
delimitada por llaves).
Por ejemplo, en el siguiente método:
v o i d parseCommand ( M o b i l e a C r e a t u r e , S t r i n g v e r b , S t r i n g a r g s )
{
i f ( e q u a l s ( verb , " s a l u d a r " ) )
a C r e a t u r e . w r i t e ( " H o l a . n" ) ;
end ( ) ;
}
La cabecera es
v o i d parseCommand ( M o b i l e a C r e a t u r e , S t r i n g v e r b , S t r i n g a r g s )
donde:
1. Mobile aCreature, String verb, String args es la lista de argumentos o pa-
rámetros de entrada del método. Cada parámetro corresponde a un dato
que el método espera recibir cuando se ejecute. Cuando usemos BeanShell
26. 26 Uso del lenguaje BeanShell
para mundos en AGE, normalmente será el AGE quien invoque la mayoría
de los métodos y nos proporcione los datos de los parámetros.
2. Cada una de las tres partes separadas por comas en la lista de parámetros
(por ejemplo, String verb) es la definición de un parámetro de entrada.
Un método puede tener cualquier cantidad de parámetros de entrada,
incluyendo no tener ninguno (en cuyo caso no habría nada dentro de los
paréntesis). La declaración de un parámetro consta de un tipo de dato
y un identificador o nombre. En el ejemplo, String sería el tipo de dato
(indicando que ese parámetro es una cadena de texto) y verb sería el
nombre. El tipo de dato tiene que ser uno de los que soporta AGE o
Java (hay formas de crearlos nuevos, pero no las usaremos); mientras que
el nombre es totalmente arbitrario mientras dentro del mismo método
nos refiramos al parámetro siempre por el mismo nombre: por ejemplo,
podríamos haberle llamado a ese parámetro verbo en lugar de verb, y todo
funcionaría igual mientras cambiáramos ese nombre también en el cuerpo
del método (if ( equals(verbo,"saludar") )); pero no podríamos hacer un
cambio semejante con el tipo de dato.
3. Algunos de los tipos de datos más usados son:
int: número entero.
boolean: representa algo que puede ser verdadero o falso, tiene dos
valores válidos: true o false.
double: permite representar números con cifras decimales.
char: representa una letra o símbolo.
String: cadena de texto.
World: mundo de AGE.
Entity: cualquier entidad del mundo.
Item: cosa del mundo.
Mobile: criatura del mundo.
Room: habitación del mundo.
4. Al escribir los tipos de datos, es importante respetar las convenciones de
mayúsculas y minúsculas que se ven en la lista (BeanShell es sensible a
mayúsculas y minúsculas). El motivo de que unos tipos de dato se escriban
con minúscula y otros con mayúscula es que los que son con mayúscula
corresponden a objetos (y se llaman clases, es decir, Room es un tipo de
dato que es una clase y la habitación de Pedro sería un objeto de tipo
Room) mientras que los que son con minúscula son tipos de datos que se
llaman básicos y corresponden a valores (como el entero −4, el booleano
false o el double 3,25) y no a objetos. En AGE usaremos muy pocos tipos
básicos (de hecho, sólo los de la lista y uno más que mencionaremos ahora
mismo); sin embargo podremos usar bastantes clases (no sólo las de la
lista), que iremos viendo.
5. parseCommand es el nombre del método, que junto con los tipos de los
parámetros (no sus nombres) es lo que lo identifica y distingue de otros.
En general, el nombre puede ser cualquier palabra que cumpla ciertas
27. Primeros pasos con BeanShell 27
reglas (por ejemplo si tiene sólo letras mayúsculas y minúsculas siempre
servirá).
6. Lo que viene antes del nombre, en este caso void, es el tipo de retorno del
método. Y es que, además de procesar unos parámetros de entrada, un
método puede devolver un resultado como salida. El tipo básico void es
un tipo básico especial que no tiene ningún valor, y se utiliza en el caso
en que un método no devuelve nada.
7. En general, podemos definir métodos con cualquier combinación de nom-
bre, parámetros y tipo de retorno. Pero como normalmente vamos a querer
que AGE ejecute nuestros métodos, para que así queden integrados en el
conjunto de la aventura, necesitaremos ponerles unos nombres (y tipos de
parámetros y de retorno, aunque no necesariamente nombres de paráme-
tros, que como dijimos son arbitrarios) determinados que son los que el
AGE espera encontrar. Sin embargo, no es necesario saber de memoria los
nombres y parámetros de los métodos que invoca AGE, ya que el PUCK
nos generará automáticamente una plantilla de los mismos desde los me-
nús contextuales de los formularios de código. Por ejemplo, si en el PUCK
abrimos el área en que se introduce el código del mundo y vamos a su
menú contextual con el botón derecho, seleccionando la opción «Insertar
código – Método de análisis de la entrada (estándar)» se nos generará au-
tomáticamente una plantilla que contendrá la cabecera del método y unos
comentarios sobre para qué sirve el método y la función de cada pará-
metro. Eso sí, el cuerpo del método que aparece automáticamente estará
vacío y por lo tanto no hará nada, tendremos que rellenarlo nosotros para
que haga algo.
8. Si generamos esta plantilla, o cualquier otra con el PUCK, veremos tex-
to que viene después de dobles barras. El texto que viene en una línea
después de una doble barra es un comentario de código y no se ejecuta,
es simplemente para que nosotros escribamos explicaciones de qué hace
ese código o cosas que queramos recordar. Lo mismo sucede con el texto
comprendido entre /* y */, que puede ocupar una o varias líneas. Los
comentarios de código pueden ir en cualquier parte del mismo: sea en la
cabecera de un método, en su cuerpo, o incluso fuera de cualquier método,
ya que AGE los ignora por completo y no los ejecuta.
// e s t o e s un c o m e n t a r i o
/∗ y e s t o
t a m b i é n ∗/
El cuerpo del método del ejemplo anterior es el código entre llaves:
{
i f ( e q u a l s ( verb , " s a l u d a r " ) )
a C r e a t u r e . w r i t e ( " H o l a . n" ) ;
end ( ) ;
}
Y lo que hace es que, si la cadena (verbo) que nos han pasado como segundo
parámetro corresponde a la palabra «saludar» (sin comillas), entonces escribi-
mos una línea que dice «Hola.» en la salida asociada a la criatura que viene
dada como primer parámetro.
28. 28 Uso del lenguaje BeanShell
2.1.3. Variables y entrada/salida sencilla
Ahora que ya sabemos que tenemos que definir métodos y que estructura
básica tienen, y tenemos una idea de cómo obtener sus cabeceras con el PUCK,
vamos a ver qué tipo de cosas podemos poner en los cuerpos de los métodos para
ejecutar código útil. Para ello, por el momento siempre partiremos del método
void parseCommand(Mobile aCreature, String verb, String args) cuya cabecera se
genera automáticamente en la opción «Insertar código – Método de análisis de
la entrada (estándar)» del menú contextual del campo de código de mundo.
Ese método lo invoca AGE cuando un jugador introduce una entrada, y los
parámetros que recibe son, por orden: el objeto que representa al jugador que
ha escrito esa entrada (de momento el único jugador, ya que nos centraremos
en aventuras para un solo jugador), el verbo que ha puesto (o primera palabra
de la cadena que ha tecleado en la entrada), y el resto de la cadena de entrada.
Por ejemplo, si el jugador teclea «comer el plátano de Canarias», entonces el
parámetro aCreature corresponderá a ese jugador, el parámetro verb a la cadena
«comer», y args a la cadena «el plátano de Canarias».
Mostrándole texto al jugador
Una de las primeras cosas que podemos hacer es probar a mostrar un texto
al jugador cada vez que se ejecute el método (es decir, en el caso de este método
particular, cada vez que escribe algo). Esto podríamos hacerlo así:
v o i d parseCommand ( M o b i l e a C r e a t u r e , S t r i n g v e r b , S t r i n g a r g s )
{
a C r e a t u r e . w r i t e ( " H o l a . n" ) ;
}
Si ponemos este código en el mundo, cada vez que el jugador escriba algo,
AGE le dirá «Hola». La sintaxis aCreature.write("Hola.n") significa que
queremos al método write del objeto aCreature, y pasarle como parámetro la
cadena "Hola.n". Para ello, tiene que haber definido en la clase a la que
pertenece aCreature (o sea, la clase Mobile) un método llamado write que coja
una cadena como único parámetro, y efectivamente, este método existe, y lo que
hace es escribir algo por la ventana o consola de esa criatura. El punto y coma
sirve para terminar la instrucción. Se pueden ejecutar varias instrucciones en
secuencia, una después de otra, escribiéndolas una después de otra. No se debe
olvidar poner un punto y coma después de cada una:
v o i d parseCommand ( M o b i l e a C r e a t u r e , S t r i n g v e r b , S t r i n g a r g s )
{
a C r e a t u r e . w r i t e ( " E s t a l í n e a s e e s c r i b e p r i m e r o . n" ) ;
a C r e a t u r e . w r i t e ( "Y é s t a s e e s c r i b e " ) ;
a C r e a t u r e . w r i t e ( " d e s p u é s . n" ) ;
}
La secuencia de caracteres n dentro de una cadena significa un salto de
línea. Por lo tanto, este código escribirá dos líneas, una que dirá «Esta línea se
escribe primero.» y otra que dirá «Y ésta se escribe después.».
29. Primeros pasos con BeanShell 29
La función end()
Si probamos a ejecutar nuestra aventura de ejemplo con el código que hemos
añadido, veremos que la salida es algo similar a esto:
> inventario
Esta línea se escribe primero.
Y ésta se escribe después.
No tienes nada.
> asdasf
Esta línea se escribe primero.
Y ésta se escribe después.
No entiendo...
Es decir, nuestra aventura está diciéndole escribiéndole al jugador el texto
cada vez que escribe algo, pero después está siguiendo el procesado normal que
hace AGE (por ejemplo, mostrar el inventario si lo que escribió fue «inventario»).
Si en lugar de esto se quiere que el método que hemos definido sustituya al
procesado por defecto de AGE, es decir, que cuando un jugador escriba algo se
le escriba el texto dado y nada más, podemos utilizar la función end():
v o i d parseCommand ( M o b i l e a C r e a t u r e , S t r i n g v e r b , S t r i n g a r g s )
{
a C r e a t u r e . w r i t e ( " E s t a l í n e a s e e s c r i b e p r i m e r o . n" ) ;
a C r e a t u r e . w r i t e ( "Y é s t a s e e s c r i b e " ) ;
a C r e a t u r e . w r i t e ( " d e s p u é s . n" ) ;
end ( ) ;
}
De esta forma nuestra salida será
> inventario
Esta línea se escribe primero.
Y ésta se escribe después.
> asdasf
Esta línea se escribe primero.
Y ésta se escribe después.
Podemos utilizar la función end() para interrumpir el procesado normal del
AGE en cualquier momento de la mayoría de los métodos. La plantilla que el
PUCK genera de cada método da información explícita sobre si se puede usar
o no la función end().
A end() le hemos llamado función, y no método, porque no está asocia-
da a un objeto. Un método se invoca sobre un objeto, con la sintaxis obje-
to.método(parámetros),1 mientras que una función se invoca sin más, con la
sintaxis función(parámetros).
Variables y asignaciones
En BeanShell, como en otros muchos lenguajes de programación, una varia-
ble es un nombre simbólico que se asocia a un determinado valor u objeto en
1 O a veces sobre una clase, con la sintaxis Clase.método(parámetros). Los métodos que se
invocan sobre una clase se llaman métodos estáticos
30. 30 Uso del lenguaje BeanShell
memoria, que pueden cambiar a lo largo de la ejecución del código. Los pará-
metros de los métodos, que vimos con anterioridad, son un tipo particular de
variables que almacenan los datos y objetos que se pasan como entradas a un
método; pero como programadores también podemos crear otras variables para
almacenar todo tipo de información temporal que se utilice en el interior de un
método.
Una variable tiene tres atributos esenciales: su nombre, su tipo de dato y su
valor. El nombre es simplemente una palabra que el programador escoge para
referirse a la variable, mientras que el tipo de dato describe qué valores puede
contener la variable. Los tipos de datos son los mismos que hemos visto en la
sección sobre los parámetros de los métodos, que pueden ser tipos básicos o
clases.
Un ejemplo de uso de variables podría ser el siguiente: si estamos escribiendo
un código para describir todos los objetos que un jugador lleva en su inventario,
probablemente usaremos una variable para llevar cuenta de cuántos objetos
llevamos descritos (que será de tipo int, pues el número de objetos descritos
en cada momento es un número entero) y otra variable donde almacenaremos
temporalmente cada objeto al consultarlo en el inventario para describirlo (que
será de la clase Item, que representa las cosas en AGE).
Para crear una nueva variable, se utiliza una sintaxis como ésta:
int index ;
donde int es el tipo de datos de la variable que queremos declarar, e index es
su nombre.
Para asignarle un nuevo valor a una variable ya declarada, lo hacemos de la
siguiente manera:
index = 0;
Con este código estamos haciendo que la variable entera index tome como
valor el número entero 0. Es importante tener en cuenta que cada variable sólo
puede tomar valores legales según su tipo de datos. Por ejemplo, a una variable
entera le podemos dar el valor 0; pero no le podemos dar el valor «Hola» o 3,5,
porque éstos no son números enteros.
Aquí vemos cómo podemos asignar valores a variables de distintos tipos:
int index ;
d o u b l e number ;
boolean c o n d i t i o n ;
char l e t t e r ;
S t r i n g name ;
Room aRoom ;
Item anItem ;
Mobile aCreature ;
i n d e x = −4;
number = 7 . 2 3 ;
c o n d i t i o n = t r u e ; // o f a l s e
l e t t e r = ’ a ’ ; // l o s v a l o r e s de t i p o c a r á c t e r s e e s c r i b e n
// e n t r e c o m i l l a s s i m p l e s
name = " P a b l o P i c a s s o " ; // l o s v a l o r e s de t i p o c a d e n a ( S t r i n g )
// s e e s c r i b e n e n t r e c o m i l l a s d o b l e s
aRoom = room ( " S a l a d e l t r o n o " ) ; // room ( ) e s una f u n c i ó n a l a que
// l e pasamos una c a d e n a y n o s d e v u e l v e l a
31. Primeros pasos con BeanShell 31
// h a b i t a c i ó n que t i e n e e s a c a d e n a como
nombre ú n i c o
a n I t e m = i t e m ( " B o l í g r a f o " ) ; // i t e m ( ) e s a n á l o g o a room ( ) ,
// p e r o con una c o s a
a C r e a t u r e = m o b i l e ( " Juan " ) ; // m o b i l e ( ) e s a n á l o g o a room ( ) ,
// p e r o con una c r i a t u r a
Este proceso se puede abreviar asignando un valor inicial a las variables a la
vez que las creamos, de la siguiente manera:
i n t i n d e x = −4;
d o u b l e number = 7 . 2 3 ;
boolean c o n d i t i o n = true ;
char l e t t e r = ’ a ’ ;
S t r i n g name = " P a b l o P i c a s s o " ;
Room aRoom = room ( " S a l a d e l t r o n o " ) ;
Item anItem = item ( " B o l í g r a f o " ) ;
M o b i l e a C r e a t u r e = m o b i l e ( " Juan " ) ;
A las variables también se les puede asignar el valor de otra variable, o el
valor que devuelve una expresión (que puede contener operaciones o llamadas a
métodos o funciones):
int index = 7;
i n t i n d e x 2 = i n d e x ; // c o p i a m o s e l v a l o r de i n d e x a i n d e x 2 ,
// a h o r a i n d e x 2 v a l e 7
i n t i n d e x 3 = i n d e x 1 + i n d e x 2 ; // l a e x p r e s i ó n i n d e x 1 + i n d e x 2
// d e v u e l v e l a suma de i n d e x 1 e i n d e x 2 , a h o r a i n d e x 3
v a l e 14
i n d e x 3 = i n d e x 3 + 1 ; // i n c r e m e n t a m o s i n d e x 3 en 1
I t e m arma = i t e m ( " Espada " ) ; // ob t e n e mo s e l o b j e t o de nombre
// ú n i c o " Espada " y l o almacenamos en l a v a r i a b l e
arma
i n t pesoArma = arma . g e t W e i g h t ( ) ; // g e t W e i g h t ( ) e s un método de l a
// c l a s e I t e m que n o s d e v u e l v e e l p e s o de una c o s a .
Aquí ponemos
// e l p e s o de n u e s t r a e s p a d a en l a v a r i a b l e pesoArma
. Nótese
// que podemos h a c e r e s t o p o r q u e e l t i p o de r e t o r n o
del
// método g e t W e i g h t ( ) e s i n t , y e l t i p o de l a
variable
// pesoArma t a m b i é n e s i n t . En g e n e r a l no podemos
asignar
// a una v a r i a b l e un v a l o r que no s e a de s u t i p o
i n t d o b l e P e s o A r m a = arma . g e t W e i g h t ( ) ∗ 2 ; // e l ∗ e s e l o p e r a d o r
// de m u l t i p l i c a c i ó n . Como vemos , s e pueden c o m b i n a r
llamadas
// a métodos con o p e r a c i o n e s p a r a f o r m a r una
e x p r e s i ó n que
// d e v u e l v e un v a l o r , v a l o r que s e pu e de d e s p u é s
m e t e r en una v a r i a b l e
arma = 7 ; // e s t o f a l l a r í a porque intentamos a s i g n a r a l a
// v a r i a b l e arma , de t i p o Item , e l v a l o r 7 , de t i p o
int .
//No c o i n c i d e n l o s t i p o s , a s í que l a a s i g n a c i ó n no
s e p ue de
// l l e v a r a cabo
32. 32 Uso del lenguaje BeanShell
Operaciones con los tipos básicos
En la sección anterior hemos visto algunos ejemplos de cómo se hacen opera-
ciones, como por ejemplo una suma. Veamos aquí una lista algo más exhaustiva
de operaciones útiles.
Con int:
int a = 7;
int b = 4;
int c;
c = a + b; // suma a y b ( c v a l d r í a 1 1 )
c = a − b; // r e s t a a menos b ( c v a l d r í a 3 )
c = a ∗ b; // m u l t i p l i c a a p o r b ( c v a l d r í a 2 8 )
c = a / b; // d i v i s i ó n e n t e r a de a e n t r e b ( c v a l d r í a 1 )
c = a %b; // r e s t o de l a d i v i s i ó n e n t e r a de a e n t r e b ( c v a l d r í a 3 )
.
a++; // i n c r e m e n t a l a v a r i a b l e a ( e s e q u i v a l e n t e a p o n e r a = a + 1 )
a−−; // d e c r e m e n t a l a v a r i a b l e a ( e s como p o n e r a = a − 1 )
a ∗= 3 ; // a b r e v i a t u r a de a = a ∗ 3 . E s t o s e p ue d e h a c e r
análogamente
// con t o d o s l o s o p e r a d o r e s de l o s t i p o s b á s i c o s que
aparecen
// en e s t a s e c c i ó n .
Con double:
d o u b l e a = 7 . 0 ; // n ó t e s e e l . 0 p a r a i n d i c a r que e l v a l o r s e
// c o n s i d e r a un d o u b l e y no un i n t , aunque m a t e m á t i c a m e n t e
// s e a e l mismo v a l o r
double b = 4 . 0 ;
double c ;
c = a + b ; // suma a y b ( c v a l d r í a 1 1 . 0 )
c = a − b ; // r e s t a a menos b ( c v a l d r í a 3 . 0 )
c = a ∗ b ; // m u l t i p l i c a a p o r b ( c v a l d r í a 2 8 . 0 )
c = a / b ; // d i v i s i ó n con d e c i m a l e s de a e n t r e b ( c v a l d r í a 1 . 7 5 )
Con cadenas:
S t r i n g a = " Juan " ;
S t r i n g b = " Pepe " ;
S t r i n g c = a + b ; // l a " suma " de c a d e n a s e s s u
// c o n c a t e n a c i ó n : l a c a d e n a " JuanPepe "
int a = 7;
double b = 3 . 0 ;
S t r i n g m e n s a j e = " La v a r i a b l e a v a l e " + a + " y l a v a r i a b l e b vale
" + b;
// l a s c a d e n a s t a m b i é n s e pueden c o n c a t e n a r con o t r o s tipos
de d a t o s ,
// en c u y o c a s o e s o s v a l o r e s s e c o n v i e r t e n a c a d e n a s . En
este
// c a s o m e n s a j e q u e d a r í a como " La v a r i a b l e a v a l e 7 y la
variable b
// v a l e 3 . 0 "
Con booleanos:
boolean s i = true ;
b o o l e a n no = f a l s e ;
boolean r e s u l t a d o ;
33. Primeros pasos con BeanShell 33
r e s u l t a d o = ! s i ; // l a ! e s e l o p e r a d o r n o t ( n e g a c i ó n ) .
// A p l i c a d o a t r u e , d e v u e l v e f a l s e , y a p l i c a d o a f a l s e ,
// d e v u e l v e t r u e .
r e s u l t a d o = s i | | no ; // o p e r a d o r o r l ó g i c o .
// D e v u e l v e t r u e s i a l menos uno de l o s d o s o p e r a n d o s
// e s t r u e ( en e s t e c a s o d e v o l v e r í a t r u e ) .
r e s u l t a d o = s i && no ; // o p e r a d o r and l ó g i c o .
// D e v u e l v e t r u e s ó l o cuando l o s d o s o p e r a n d o s s o n
// t r u e ( en e s t e c a s o d e v o l v e r í a f a l s e ) .
Los operadores se pueden combinar en expresiones complejas, utilizando pa-
réntesis si es necesario para definir el orden:
i n t d = a + b + c ; // suma de a , b y c
d = ( a + b ) ∗ c ; // sumar a y b , y m u l t i p l i c a r e l r e s u l t a d o p o r c
S t r i n g j u a n e s = a + a + a ; // " J u a n J u a n J u a n "
j u a n e s += j u a n e s ; // " J u a n J u a n J u a n J u a n J u a n J u a n "
b o o l e a n c o m p l e j o = ( s i && s i ) | | ( no | | no ) ; // e s t o da t r u e
2.1.4. La estructura condicional (if)
Hasta ahora, hemos visto cómo crear un método, escribir cosas en la pan-
talla o ventana del jugador y crear o actualizar variables. Estas instrucciones
se pueden combinar en secuencias para que se ejecuten unas después de otras;
pero con lo que de momento hemos visto, nuestro método siempre hará exac-
tamente lo mismo, una secuencia de operaciones predeterminadas. Esto limita
mucho el conjunto de cosas que podemos hacer. Para crear métodos más intere-
santes, necesitamos de alguna forma poder hacer que se comporten de manera
diferente según las circunstancias: por ejemplo, que el método parseCommand
que estamos definiendo actúe de manera distinta según el verbo que ha escrito
el jugador, o según la habitación en la que está, si ha realizado o no con an-
terioridad una determinada acción, o cualquier otra circunstancia que se nos
ocurra.
Para conseguir estos comportamientos diferentes según las circunstancias,
podemos utilizar la estructura condicional, llamada if. La estructura condicional
nos permite definir un código que sólo se ejecuta si se cumple una determina-
da condición (por ejemplo, que el jugador esté en la habitación «cocina»), y,
opcionalmente, otro código que sólo se ejecuta si no se cumple la condición:
Mobile jugador = aCreature ;
i f ( e q u a l s ( room ( " C o c i n a " ) , j u g a d o r . getRoom ( ) ) )
j u g a d o r . w r i t e ( "En e s t o s momentos e s t á s en l a c o c i n a . n" ) ;
else
j u g a d o r . w r i t e ( "En e s t o s momentos e s t á s en o t r a p a r t e que no e s l a
c o c i n a . n" ) ;
En general, la sintaxis de la instrucción if para ejecutar un código si y sólo
si se cumple una determinada condición es la siguiente:
if ( condición ) cuerpo
Donde el código de cuerpo sólo se ejecutará en el caso de que condición sea
cierta. En concreto, condición tiene que ser siempre una expresión que devuelva
un valor de tipo boolean (true o false). El código de cuerpo se ejecutará si el
valor de condición es true, y no se ejecutará si dicho valor es false.
34. 34 Uso del lenguaje BeanShell
En el caso de que el cuerpo del if esté formado por varias instrucciones, se
deben delimitar dichas instrucciones con llaves, como si fueran el cuerpo de un
método:
i f ( e q u a l s ( room ( " C o c i n a " ) , j u g a d o r . getRoom ( ) ) )
{
j u g a d o r . w r i t e ( "En e s t o s momentos e s t á s en l a c o c i n a . n" ) ;
j u g a d o r . w r i t e ( "Te dan g a n a s de p o n e r t e a c o c i n a r a l g o . n" ) ;
}
Si el cuerpo del if está formado por una única instrucción, se puede poner la
instrucción sin más, sin llaves:
i f ( e q u a l s ( room ( " C o c i n a " ) , j u g a d o r . getRoom ( ) ) )
j u g a d o r . w r i t e ( "En e s t o s momentos e s t á s en l a c o c i n a . n" ) ;
O bien ponerla con llaves como antes, que en este caso es equivalente:
i f ( e q u a l s ( room ( " C o c i n a " ) , j u g a d o r . getRoom ( ) ) )
{
j u g a d o r . w r i t e ( "En e s t o s momentos e s t á s en l a c o c i n a . n" ) ;
}
La sintaxis de la estructura if para ejecutar un código si se cumple una
condición y otro distinto si no se cumple es la siguiente:
if ( condición ) cuerpo1 else cuerpo2
De nuevo, cuerpo1 y cuerpo2 tienen que ir necesariamente entre llaves si
tienen más de una instrucción, y pueden ir sin llaves si tienen sólo una.
i f ( e q u a l s ( room ( " C o c i n a " ) , j u g a d o r . getRoom ( ) ) )
{
j u g a d o r . w r i t e ( "En e s t o s momentos e s t á s en l a c o c i n a . n" ) ;
j u g a d o r . w r i t e ( "Te dan g a n a s de p o n e r t e a c o c i n a r a l g o . n" ) ;
}
else
j u g a d o r . w r i t e ( "No e s t á s en l a c o c i n a . n" ) ;
Una estructura «if–else» se puede combinar con otras estructuras «if–else»
para dar una estructura «if–else if–else if...–else», como ésta:
i f ( e q u a l s ( v e r b , " comer " ) )
j u g a d o r . w r i t e ( " Has p u e s t o e l v e r b o comer . n" ) ;
else i f ( equals ( verb , " beber " ) )
j u g a d o r . w r i t e ( " Has p u e s t o e l v e r b o b e b e r . n" ) ;
else i f ( equals ( verb , " dormir " ) )
j u g a d o r . w r i t e ( " Has p u e s t o e l v e r b o d o r m i r . n" ) ;
else
j u g a d o r . w r i t e ( "No e n t i e n d o e l v e r b o que h a s p u e s t o . n" ) ;
Este código escribe una cosa u otra en la consola del jugador según el valor
del parámetro verb del método.
Comparaciones
Para decidir entre una rama u otra de una estructura condicional, necesita-
mos que nuestro código utilice alguna expresión o variable de tipo boolean, de
modo que ejecute la rama del if si esta expresión tiene un valor verdadero, y la
rama del else (o nada) si su valor es falso.
35. Primeros pasos con BeanShell 35
¿Cómo obtenemos expresiones de tipo boolean que sean relevantes para de-
finir nuestros métodos if? Existen muchas maneras, dependiendo de lo que uno
quiera hacer; pero una de las más básicas y comunes son las comparaciones.
Muchas veces queremos ejecutar un código si un valor es igual a otro (por ejem-
plo, si el valor de una variable es igual a un valor dado). Esto se consigue con
la función de comparación de igualdad equals.2
La comparación de igualdad de AGE es una función equals al que se le pasan
dos parámetros que pueden ser de cualquier tipo (tipos básicos u objetos). La
función equals devuelve true si los dos parámetros que se le han pasado tienen
el mismo valor, y false si son distintos.
Así, por ejemplo, podríamos hacer lo siguiente:
e q u a l s ( 3 , 4 ) ; // d e v u e l v e f a l s e
e q u a l s ( 3 , 3 ) ; // d e v u e l v e t r u e
e q u a l s ( 2 + 2 , 4 ) ; // d e v u e l v e t r u e
e q u a l s ( 2 + 2 , 5 ) ; // d e v u e l v e f a l s e
e q u a l s ( " F u l a n i t o " , " M e n g a n i t o " ) ; // d e v u e l v e f a l s e
String f = " Fulanito " ;
e q u a l s ( " F u l a n i t o " , f ) ; // d e v u e l v e t r u e
S t r i n g g = " Fula " ;
String h = " nito " ;
e q u a l s ( f , g ) ; // d e v u e l v e f a l s e
e q u a l s ( f , h ) ; // d e v u e l v e f a l s e
e q u a l s ( f , g+h ) ; // d e v u e l v e t r u e
e q u a l s ( room ( " C o c i n a " ) , room ( " C o c i n a " ) ) ; // d e v u e l v e t r u e
e q u a l s ( room ( " C o c i n a " ) , room ( " Baño " ) ) ; // d e v u e l v e f a l s e ( s a l v o que no
e x i s t a n h a b i t a c i o n e s l l a m a d a s C o c i n a n i Baño , en c u y o c a s o s e
c o n s i d e r a n i g u a l e s p o r q u e n i n g u n a de l a s d o s e x i s t e )
Podemos ver un uso práctico de una comparación de igualdad en conjun-
ción con una estructura condicional si, en el método parseCommand visto con
anterioridad, queremos hacer que se responda de una manera determinada sólo
cuando el jugador escribe un determinado verbo. Por ejemplo:
v o i d parseCommand ( M o b i l e a C r e a t u r e , S t r i n g v e r b , S t r i n g a r g s )
{
i f ( e q u a l s ( v e r b , " comer " ) )
{
a C r e a t u r e . w r i t e ( "No p u e d e s comer a h o r a , que e s t á s a r é g i m e n . n"
);
end ( ) ;
}
}
Con esto conseguimos que cuando el jugador use el verbo comer, se le diga
que está a régimen, sin cambiar el comportamiento de los otros verbos:
> inventario
Tienes una manzana.
> comer la manzana
No puedes comer ahora, que estás a régimen.
2 Nota para programadores: BeanShell tiene otras comparaciones de igualdad por defecto,
que son el método equals de la clase Object y el operador ==. La función equals que se cubre
aquí es una función de más alto nivel creada a propósito para simplificar las comparaciones
en AGE, cubriendo los casos de uso más comunes en las aventuras. Los programadores que
tengan conocimientos de Java o de BeanShell pueden utilizar en su lugar las comparaciones
tradicionales con el método equals de Object y con ==, que por supuesto funcionan en AGE
como en Java.
36. 36 Uso del lenguaje BeanShell
Nótese que el end() está dentro del cuerpo del if, con lo cual sólo se impide
que AGE ejecute sus comportamientos estándar si realmente se usa el verbo
comer. Si hubiésemos puesto el end() fuera del cuerpo del if, haríamos que dejara
de funcionar el comando «inventario» (y cualquier otro) porque el end() se
ejecutaría siempre e impediría que AGE ejecutase sus comportamientos por
defecto. Si no hubiésemos puesto un end() en absoluto, ni siquiera dentro del if,
entonces al usar el verbo «comer» se imprimiría nuestro mensaje pero después
se seguiría procesando la entrada como si tal cosa, cosa que provocaría efectos
no deseados:
> comer la manzana
No puedes comer ahora, que estás a régimen.
¿Cómo? ¿Comer?
En este caso, imprimimos el mensaje pero luego AGE procesa el comando, y
como por defecto no lo entiende (no está definido por defecto en AGE qué pasa
al comer algo), da un mensaje de error. Hay que tener cuidado, pues, de usar
los end() en los momentos en que se necesitan.
Si en lugar de comprobar si dos cosas son iguales queremos comprobar si
son distintas, podemos utilizar el operador ! visto antes, que niega un valor
booleano. Así, equals(verb,"comer") devuelve true si y sólo si el parámetro
verb tiene como valor «comer»; mientras que !equals(verb,"comer") devuelve
true sólo si el valor del parámetro no es «comer».
En el caso de trabajar con números (sean int o double), a menudo nos intere-
sará hacer comparaciones de superioridad y de inferioridad, en lugar de las de
igualdad. Es decir, querremos saber si un número es mayor o menor que otro.
Esto se hace con los operadores <, >, <= y >=:
boolean b ;
b = ( 4 > 3 ) ; // d e v u e l v e t r u e
b = ( 4 >= 3 ) ; // d e v u e l v e f a l s e
b = ( 4 >= 4 ) ; // d e v u e l v e t r u e
b = ( 4 . 2 > 3 . 5 ) ; // d e v u e l v e t r u e
b = ( 4 < 3 ) ; // d e v u e l v e t r u e
b = ( 3 < 4 ) ; // d e v u e l v e t r u e ;
i f ( x >= 0 ) a C r e a t u r e . w r i t e ( " E q u i s e s mayor que c e r o . n" ) ; // p a r a
e s t o t e n d r e m o s que h a b e r d e c l a r a d o a n t e s l a v a r i a b l e x
2.1.5. Los bucles
La estructura condicional (if–else) que hemos visto es una de las principales
estructuras de control, es decir, maneras de gestionar qué camino va siguiendo un
programa en BeanShell para ejecutar sus instrucciones. Las otras estructuras de
control importantes que necesitaremos para gestionar el flujo de los programas
son los bucles.
Como hemos visto, una estructura condicional nos permite escoger entre un
código a ejecutar y otro según si se da o no una determinada condición. Este
código que se ejecuta lo hace únicamente una vez (salvo que estemos llamando
al if varias veces desde código externo a él, claro).
Los bucles nos permiten ejecutar un bloque de código varias veces, repitién-
dose mientras una condición determinada se cumpla. El código del cuerpo del