![]() | ![]() | ![]() | El lenguaje de los Datos |
Hasta ahora, todos los programas de ejemplo han consistido sólo en una
secuencia de rutinas, cada una de ellas con enmarcadas entre [ y
]. Estas rutinas no tienen forma de compartir información entre
ellas, excepto por el paso de parámetros. Esta estructura realmente no
es la más adecuada para un programa largo cuya tarea sea simular algo
complicado (como el mundo de un juego de aventuras): sería útil tener
algún tipo de almacén central de información, al cual pudieran tener
acceso todas las rutinas, cuando fuera necesario.
Este tipo de información disponible para todas las rutinas, se denomina "global", en contraposición a la información "local" de cada rutina. (Como se verá en la sección sobre objetos, hay una opción intermedia entre "global" y "local", en la cual la información está disponible sólo para un grupo de rutinas que trabajan juntas en una parte del programa).
Las estructuras de datos se añaden a los programas en Inform usando
palabras especiales llamadas "directivas", en medio de las
definiciones de las rutinas. Es importante distinguir entre las
directivas, que indican a Inform que haga algo "ahora" (normalmente
crear algún tipo de almacén de información), y las instrucciones que
ocurren dentro de las rutinas, que simplemente son traducidas de algún
modo, pero no ejecutadas hasta que el programa haya sido totalmente
compilado y se esté ejecutando en un intérprete Z. De hecho, ya
conocemos una directiva, aunque no lo habíamos dicho: se trata de la
directiva [, que significa "traduce el siguiente conjunto de
instrucciones, hasta que aparezca un ]". En total, Inform tiene 38
directivas que son las siguientes:
Abbreviate Array Attribute Class Constant Default
Dictionary End Endif Extend Fake_action Global
Ifdef Ifndef Ifnot Ifv3 Ifv5 Iftrue
Iffalse Import Include Link Lowstring Message
Nearby Object Property Release Replace Serial
Switches Statusline Stub System_file Trace Verb
Version [
La mayoría de estas son demasiado técnicas y no son usadas por muchos
programadores (por ejemplo Trace, Stub, Default, System_file,
Abbreviate, y Dictionary). Otras sirven para controlar detalles
sobre qué debe ser compilado y qué no (estas son Ifdef, Ifndef,
Ifnot, Ifv3, Ifv5, Iftrue e Iffalse). Estas directivas que
no son demasiado importantes se tratan en el
capítulo sobre compilador.
Esto nos deja tan solo 9 directivas básicas para la creación de estructuras de datos, y estas son las que interesa conocer ahora:
Array Attribute Class Constant Extend Global
Object Property Verb
Es habitual escribir estas directivas con la primera letra en
mayúsculas, de este modo se distinguen fácilmente de las
instrucciones. Las directivas Attribute, Class, Object y
Property se tratarán en la sección sobre objetos.
La directiva más simple que tiene un efecto "global", es decir, que
no tiene efecto sólamente dentro de una rutina, es la directiva
Constant. El siguiente programa, un juego de azar bastante poco
satisfactorio, muestra un uso típico de Constant
Constant PUNTUACION_MAXIMA = 100;
[ Main;
print "Has obtenido ", random(PUNTUACION_MAXIMA),
" puntos, de un máximo de ", PUNTUACION_MAXIMA, ".^";
];
|
El valor de la puntuación máxima se usa dos veces en la rutina
Main. Por supuesto, el programa sería el mismo si quitamos la
definición de la Constant y sustituimos PUNTUACION_MAXIMA por 100
las dos veces que aparece en Main. La ventaja de usar Constant es
que hace muy sencillo cambiar el valor, por ejemplo 50 en lugar de
100, puesto que basta con cambiar una sola línea del programa. Además
hace el código fuente mucho más legible, puesto que de alguna forma
"explica" lo que significa el número 100.
Si no se especifica ningún valor para una constante, como por ejemplo en la línea
Constant DEBUG; |
entonces la constante es creada con un valor igual a 0.
Como se ha comentado antes, hasta ahora las únicas variables
permitidas eran "variables locales", propiedad privada de las
rutinas en que se declaraban. Una "variable global" es una variable
que es accesible desde cualquier rutina. Una vez que una variable
global ha sido declarada, se usa de la misma forma que una variable
local. La directiva para declarar una variable global es Global:
Global puntuacion = 36; |
Esto crea una variable llamada puntuacion, la cual, cuando el
programa comienza, tiene el valor 36. El valor de puntuacion puede
ser alterado o usado en cualquier punto, por debajo de la línea en la
que la variable es definida.
Un "array" es una colección indexada de variables (globales), que sirve para mantener una serie de números organizados en una secuenca. Permite definir reglas generales sobre cómo tratar a un grupo de variables. Por ejemplo, la directiva
Array mazo_de_cartas --> 52; |
crea en realidad 52 variables, cuyos nombres serían
mazo_de_cartas-->0 mazo_de_cartas-->1 ... mazo_de_cartas-->51
Hay dos tipos básicos de arrays: "arrays tipo word" (que se declaran
y utilizan poniendo -->, como el ejemplo anterior) y "arrays tipo
byte" (para los que se pone -> de forma análoga). Los elementos de
un "array tipo word" pueden contener cualquier número. En cambio los
elementos de un "array tipo byte" sólo pueden contener números entre
0 y 255 (ambos inclusive). La única ventaja de un "array tipo byte"
es que ahorra memoria, pero se recomienda a los principiantes que usen
siempre "arrays tipo word".
Además de lo anterior, Inform proporciona un tipo de array especial, que tiene una pequeña estructura prefijada: este tipo de arrays guarda en su elemento 0 el número total de elementos que contiene el array. Un "array tipo word" con esta propiedad, se denomina una "tabla", mientras que un "array tipo byte" con esta propiedad se denomina una "cadena" (string). Existen directivas para declarar arrays así. Por ejemplo, el siguiente array
Array continentes table 5; |
tiene de hecho 6 elementos (aunque su declaración diga
5). Esto es porque el elemento 0 (continentes-->0) almacena
el número 5. Los restantes elementos (desde continentes-->1 hasta
continentes-->6) quedan libres para almacenar en ellos lo que el
programador quiera. (El programa puede cambiar el valor que está
almacenado en el elemento 0, pero esto no hará que el array cambie de
tamaño). En cuanto a los arrays tipo string, como se ha dicho,
almacenan elementos que sólo pueden variar entre 0 y
255. Habitualmente se usan para almacenar letras, ya que cada letras
internamente se codifica mediante un código que siempre está entre 0 y
255. Veamos un ejemplo:
Array clave string "PELIGRO";
Array numero_telefono string "1978-345-2160";
...
PrintString(clave);
...
PrintString(numero_telefono);
...
[ PrintString el_array i;
for (i=1: i<=el_array->0: i++)
print (char) el_array->i;
];
|
La ventaja de los arrays tipo string, como vemos, es que se pueden
escribir rutinas genéricas, como PrintString, que funcionarán para
arrays de cualquier tamaño (puesto que averiguan el tamaño real
consultando el elemento 0).
Recapitulando, Inform proporciona cuatro tipos de array:
--> -> table string
![]() | He escrito una descripción mucho más detallada, aunque tal vez demasiado técnica, sobre los tipos de array y su manejo, . Si eres el tipo de persona que te gusta comprender hasta el último detalle, tal vez te interese leer este documento. |
También hay cuatro formas de inicializar un array, es decir, cargar
los valores que han de tener inicialmente sus elementos. (Por tanto, la
directiva Array admite en total 16 formas distintas de escribirse,
según los cuatro tipos de array y las cuatro formas de
inicialización).
En todos los ejemplos anteriores, los elementos del array contienen 0 cuando el programa empieza (puesto que no hemos especificado otro valor inicial para ellos). Esta es la primera forma de especificar los valores iniciales (es decir, no especificarlos en absoluto y dejar que todos valgan cero). La segunda forma consite en dar una lista de valores en la declaración. Por ejemplo:
Array primos --> 2 3 5 7 11 13; |
es un "array tipo word", que tendrá 6 elementos (llamados
primos-->0, primos--1, etc. hasta primos-->5). El valor inicial
de primos-->0 será 2, el de primos-->1 será 3, etc. Observar que
en esta forma de declarar el array no especificamos mediante un número
cuántos elementos tiene, sino que Inform contará cuántos valores
iniciales hemos escrito (6, en el ejemplo) y creará un array con
el espacio justo para ellos. Observar también que la lista de valores
iniciales va separada por simples espacios.
La tercera forma de crear un array permite suministrar un texto como
su valor inicial. Esto sólo tiene sentido en "arrays tipo byte",
puesto que ese texto será convertido en una secuencia de códigos, cada
uno de ellos de 1 byte. Ya hemos visto un par de ejemplos antes (los
arrays clave y numero_telefono). Veamos otro ejemplo:
Array nombre_jugador -> "Francisco Botas"; |
que prepara un array tipo byte, lo mismo que si hubieramos usado esta otra directiva:
Array nombre_jugador -> 'F' 'r' 'a' 'n' 'c' 'i' 's' 'c' 'o' ' ' 'B' 'o' 't' 'a' 's'; |
y en ambos casos se creará un array de 15 elementos, que es
el número de letras suministradas (contando el espacio, por
supuesto). Obsérvese que al haber utilizado la directiva -> y no
string, el elemento 0 del array no contiene su longitud, sino que
contiene el código de la letra F, que es el primer elemento
suministrado en la declaración9.
![]() ![]() |
La cuarta forma de inicializar un array ha sido desterrada de
Inform. Era una forma obsoleta. Las nuevas versiones del compilador
aún admiten esa forma por compatibilidad, pero no debe usarse
más. Consistía en suministrar la lista de valores delimitada por [
y ], con comas entre los valores.
|
![]() |
Es responsabilidad del programador el asegurarse de que no se
intenta leer o escribir en elementos inexistentes del array, es
decir, con un índice superior al tamaño del array (por ejemplo
mazo_de_cartas-->1000). Este tipo de error es famoso por ocasionar
que los programas fallen de forma impredecible, y muy difícil de
diagnosticar. He aquí por ejemplo un programa erróneo:
Este programa debería imprimir "5", puesto que ese es el valor de
|
Este programa simula la mezcla de una baraja española de 40 cartas. Los naipes son representados mediante números que van desde 0 (que representa el As de Oros) hasta 39 (el Rey de Bastos). El mazo de cartas puede imaginarse como un array de 40 posiciones. La posición 0 representa la carta superior del mazo, y la posición 39 la carta inferior. Por tanto, el elemento
mazo_de_cartas-->i
representa la carta en la posición i-ésima. Empezamos
generando un mazo de cartas nuevo, como recién salido de fábrica, es
decir, con todas sus cartas en orden. Esto significa que la carta de
valor i está en la posición i del mazo. Por tanto, el mazo tiene
el As de Oros como carta superior y el Rey de Bastos como carta
inferior.
La rutina Intercambia sirve para intercambiar dos cartas del
mazo al azar. LLamando a esta rutina repetidas veces será como
mezclaremos la baraja. Después imprimiremos los nombres de las cartas
una vez mezcladas, para lo cual definimos una regla nueva para
print, llamada (naipe).
Constant MEZCLAS = 100;
Array mazo_de_cartas --> 40;
[ Intercambia x y z;
! Inicialmente x e y valen ambas cero
while (x==y) ! Elegimos al azar dos números diferentes
{ x = random(40) - 1; y = random(40) - 1;
}
! Ahora intercambiamos los contenidos de las posiciones
! x e y del array (necesitamos una variable intermedia
! para ello)
z = mazo_de_cartas-->x;
mazo_de_cartas-->x = mazo_de_cartas-->y;
mazo_de_cartas-->y = z;
];
[ naipe n;
switch(n%10)
{ 0: print "As";
1 to 7: print n%10 + 1;
10: print "Sota";
11: print "Caballo";
12: print "Rey";
}
print " de ";
switch(n/10)
{ 0: print "Oros";
1: print "Copas";
2: print "Espadas";
3: print "Bastos";
}
];
[ Main i;
! Crear el paquete ordenado "de fábrica"
for (i=0:i<52:i++) mazo_de_cartas-->i = i;
! Intercambiar cartas unas cuantas veces
for (i=0:i<MEZCLAS:i++) Intercambia();
print "El mazo ha sido mezclado. Ahora contiene:^";
for (i=0:i<52:i++)
print (naipe) mazo_de_cartas-->i, "^";
];
|
Observa cómo se ha programado la regla (naipe) para que escriba el
nombre de la carta i. Observa también que 100 mezclas es lo justo
para dejar la baraja lo bastante mezclada. Si lo cambiamos por 1000 el
programa tardaría demasiado; si lo cambiamos por 10, el resultado
sería muy sospechoso.
![]() |
El ejemplo anterior hace la mezcla de una forma muy simple, pero hay métodos más eficientes. He aquí uno inventado por Dylan Thurston, que consigue una mezcla perfecta con tan sólo 51 intercambios (no parte de una baraja ordenada, sino que va construyendo sobre la marcha la baraja ya mezclada).
|
![]() |
Todos los programas Inform contienen automáticamente siete estructuras de datos especiales, cada una de un tipo diferente: el árbol de objetos, la gramática, la tabla de acciones, el número de versión, el número de serie, la "bandera de línea de estado" y el diccionario. Estas estructuras de datos están diseñadas expresamente para juegos de aventuras y (excepto el árbol de objetos) pueden ser ignoradas para otros tipos de programa. Por ello la mayoría de estas estructuras se tratan en el capítulo sobre la librería InformATE.
|
![]() ![]() |
De lo dicho, parece desprenderse que el diccionario es algo en donde
se van metiendo palabras, sin poder recuperarlas despues. La utilidad
aparece cuando la instrucción read intenta interpretar un texto
escrito por el jugador:
Hay que resaltar que la instrucción Lo que hace es separar el texto que se ha leido en una secuencia de palabras, en la cual las comas y los puntos cuentan como una palabra más. (Se muestra un ejemplo en la sección sobre interpretación de los sustantivos). Antes de ejecutar
buffer_de_parsing->0
un número que indique el máximo número de palabras
que queremos interpretar (si hay más palabras, las restantes se
ignoran). El número de palabras realmente encontradas será guardado
por
buffer_de_parsing->1
(observar que en ambos casos usamos
buffer_de_parsing-->(n*2-1)
podemos encontrar el "valor de diccionario" de la palabra n-ésima (para n=1,2,3...). Este "valor de diccionario" es un número entre 1 y 65535 que identifica a la palabra. Si la n-ésima palabra que escribió el jugador no estaba en el diccionario, entonces ese elemento toma el valor 0. Además, los elementos
buffer_de_parsing->(n*4)
buffer_de_parsing->(n*4+1)
contienen el número de letras de la palabra n-ésima, y el índice en el cual se halla la primera letra de esa palabra, dentro del "array de texto". Por ejemplo,
(Observa que para acceder a la palabra n-ésima, habría que acceder a
|
![]() | ![]() | ![]() | El lenguaje de los Datos |